[NewsLetter] HTML 구조가 상이한 경우의 크롤러 개발

이승준·2025년 5월 19일
post-thumbnail

구상

기후 관련 뉴스를 수집하기 위한 사이트로 다음 기후 위기 뉴스 를 설정했습니다. 뉴스 페이지에서 우리가 학습해야 할 최신 뉴스 탭의 스크립트는 다음과 같은 형태를 띄었어요.

최신 뉴스 탭에 존재하는 각 기사의 링크에 접속해 제목과 본문 내용을 수집하고, 이전에 구축해놓은 요약 및 카드뉴스 적재 파이프라인의 입력으로 사용하려고 계획했습니다.
그런데, 여기서 문제가 하나 발생합니다. 각 기사가 획일화 된 형태가 아니어서 스크립트의 구조가 모두 달라 제목과 본문이 포함된 태그가 달라 크롤러가 인식하는 태그 지정이 어려웠어요.

이를 해결하기 위해 두 가지 해결책을 구상했는데요,
1. 기사들을 탐색해 인식이 필요한 태그의 목록을 구축하고, 크롤러가 이를 탐색하도록 구현
2. 머신러닝 등의 방식을 이용한 방식으로 목표 텍스트를 인식하는 라이브러리 탐색

첫 번째 방식은 탐색 결과 태그 목록이 너무 방대해졌고, 이로 인해 탐색 시간에 대한 Overhead 가 우려되었습니다. 또한 이후 작성되는 기사들에 대한 구조도 알 수 없기 때문에 Production Level 을 고려했을 때도 좋지 못한 선택지로 보였습니다. 이런 이유로 다른 방식으로 목표 텍스트를 인식하는 라이브러리를 탐색하게 되었습니다.

Newspaper3k

newspaper3k 는 다양한 뉴스 웹사이트에서 제목과 본문을 추출하는 라이브러리입니다. 위와 같이 HTML 구조가 제각각인데도 꽤 정확하게 작동하는데요, 이유는 그 내부에 "규칙 기반 + 머신러닝 기반의 복합 전략" 이 사용되기 때문입니다.
공식 문서 는 여기 있어요.

제목 추출

newspaper3k 보통 제목이 많이 포함되어 있는 <title>, <h1> 등의 태그들을 학습한 상태입니다. 이 내용을 바탕으로 제목이 포함되어 있을만한 태그들을 모두 탐색한 후, 가장 신뢰도가 높은 텍스트를 선택해 제목으로 간주합니다.
주로 탐색하는 태그들은 다음과 같습니다.

<title>기사 제목</title>
<meta property="og:title" content="기사 제목">
<h1 class="headline">기사 제목</h1>

본문 추출

본문은 제목보다는 형태가 제각각이기 때문에, 태그 탐색 보다는 다른 방식을 선택하게 됩니다. 기사 본문은 다른 부분보다 텍스트를 가장 많이 포함 한다는 특성을 이용하는 것이죠. 그래서 글이 가장 많은 블럭을 다음과 같은 기준들로 탐색하게 됩니다.

기준설명
텍스트 밀도p, br 등 문단 수 많고, 텍스트 길이 긴 블록 선호
링크 밀도광고나 메뉴는 <a> 태그가 너무 많음 → 패널티
HTML 구조<div>, <article> 구조 분석해서 "메인 콘텐츠 후보" 식별
시각적 중심h1, p, strong 등이 가까이 있는 위치 선호
기계 학습 모델 (예)내부에서 학습된 가중치 기반으로 스코어 계산 (readability 기술 활용)

Crawler 개발

라이브러리까지 정했으니, 크롤러를 개발할 차례입니다. 우선 탐색할 뉴스의 목록을 가져오는 데에는 스크롤, 클릭 등과 같은 동작이 필요하지 않아 bs4 라이브러리를 사용했습니다.

import requests
from bs4 import BeautifulSoup

url = "https://news.daum.net/climatecrisis"
resp = requests.get(url)
soup = BeautifulSoup(resp.text, "html.parser") # 지정된 링크에서 받은 응답을 soup 객체로 파싱

# <ul> 중 최신 기사를 담은 리스트만 선별
recents = soup.find("article", class_="content-article")

# 최신 기사 리스트 내 기사들의 리스트 수집
links = []
for li in recents.select("ul.list_newsbasic > li"):
    a = li.select_one("a")
    links.append(a["href"])

이제 각 기사에 접근해 newspaper3k 를 이용해 제목과 본문을 수집해 기사의 메타데이터를 구성합니다.
이 메타데이터는 json 형태로 정리해 이후 데이터베이스 적재를 편리하게 했습니다.

articles = []

for link in links:
    article = Article(link, language="ko")
    article.download()
    article.parse()

    metadata = {"title": article.title, "text": article.text, "url": link}
    print(f"{metadata['title']} saved successfully!")
    articles.append(metadata)


with open("articles.json", "w", encoding="utf-8") as f:
    json.dump(articles, f, ensure_ascii=False, indent=2)
profile
ML Engineer @ ABLY

0개의 댓글