대규모 뉴스 스크래핑: 배포된 기사의 중복 제거 및 깨끗한 기사 추출
요약
본 글은 뉴스 웹사이트에서 수집된 원시 데이터를 깨끗하고 중복이 제거된 데이터셋으로 변환하는 파이프라인 구축 방법을 제시합니다. URL 정규화, 구조화된 스키마(schema.org)를 이용한 기사 본문 추출, 그리고 MinHash 기반 클러스터링을 통해 정확한 중복 제거가 가능합니다.
핵심 포인트
- 뉴스 데이터는 페이지 단위가 아닌 '기사(stories)' 개수로 집계해야 합니다.
- URL 정규화 시 추적 매개변수와 알려진 경로 변형을 반드시 제거해야 합니다.
- 페이지 본문 대신 schema.org NewsArticle 등 구조화된 데이터를 우선 추출합니다.
- MinHash를 활용한 근접 중복 클러스터링이 효과적인 중복 제거 방법입니다.
뉴스 스크래핑(News scraping)이란 뉴스 웹사이트에서 기사를 구조화된 기록(헤드라인, 본문 텍스트, 출판사, 섹션, 출판 날짜 및 URL)으로 자동 수집하는 것을 의미하며, 이는 미디어 모니터링, 시장 및 리스크 인텔리전스, 연구 및 학습 데이터셋 등 다양한 용도로 사용됩니다. 페이지를 가져오는 것(Fetching pages)은 쉬운 부분입니다. 어려운 부분은 하나의 기사가 여러 번 도착한다는 것입니다. 즉, 수십 개의 매체에서 재게시된 동일한 통신사 보고서, 세 개의 다른 URL에 있는 동일한 기사, 그리고 내비게이션, 관련 링크 및 캡션으로 감싸진 모든 복사본이 존재합니다.
이 글은 원시 뉴스 페이지를 깨끗하고 중복이 제거된 데이터셋으로 변환하는 파이프라인을 구축합니다. 여기에는 URL 정규화(URL canonicalisation), 기사 추출, 신뢰할 수 있는 날짜 처리, 그리고 MinHash를 사용한 근접 중복 클러스터링이 포함됩니다. 아래의 모든 스니펫은 샘플 페이지에 대해 실행되었습니다.
중복이 발생하는 원인
- 배포(Syndication): 통신사 및 와이어 기사는 여러 매체에서 재게시되며, 종종 가벼운 편집만 거치고 새로운 헤드라인이나 추가 단락이 붙습니다.
- URL 변형(URL variants): 동일한 기사가 모바일 버전, 인쇄 버전, 추적 매개변수(tracking parameters) 및 트레일링 슬래시 차이를 통해 접근 가능합니다.
- 업데이트(Updates): 개발 중인 기사는 하루 동안 수정되며, 때로는 새로운 URL에서 게시됩니다.
- 취합(Aggregation): 섹션 페이지, 주제 페이지 및 피드는 모두 동일한 기사로 연결되므로 크롤러는 이 기사를 여러 번 발견하게 됩니다.
원시 페이지 수를 세는 것은 커버리지를 과대평가합니다. '이 주제가 얼마나 많은 보도를 받았는가?'를 위해 구축된 데이터셋은 URL이 아닌, 기사(stories)의 개수를 세어야 합니다.
1단계: URL 정규화(canonicalise URLs)
무언가를 가져오거나 저장하기 전에 모든 URL을 하나의 정규 형식으로 줄여야 합니다. 페이지 자체에 rel="canonical" 링크가 있는 경우 이를 우선적으로 사용하고, 그렇지 않은 경우에는 추적 매개변수와 알려진 경로 변형(known path variants)을 제거합니다:
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
TRACKING = ("utm_", "fbclid", "gclid", "mc_", "ref", "cmpid")
...
이처럼 URL의 파라미터가 제거되면 https://www.Example-News.com/business/rates-hold/amp/?utm_source=x&id=5&fbclid=abc는 https://example-news.com/business/rates-hold?id=5로 변경됩니다. 이 과정만으로도 저장소에 도달하기 전에 상당량의 정확한 중복을 제거할 수 있습니다.
2단계: 페이지가 아닌 기사를 추출하라 (extract the article, not the page)
뉴스 페이지 전체는 대부분 기사 본문이 아닙니다. 먼저 구조화된 데이터(structured data)를 읽어야 합니다. 많은 발행처들이 헤드라인(headline), datePublished, dateModified, publisher, articleSection을 포함하는 schema.org NewsArticle 마크업을 사용합니다. 그런 다음 <article> 요소에서 본문을 추출하되, 내비게이션(navigation), 사이드바(asides), 그림(figures) 및 기타 상투적인 부가 정보(boilerplate)는 제거하고, 캡션이나 한 줄 홍보 문구와 같은 매우 짧은 단락은 제외해야 합니다:
import json
from datetime import datetime, timezone
from bs4 import BeautifulSoup
...
내비게이션, 사진 캡션, '관련 기사' 박스 및 푸터가 있는 샘플 페이지의 경우, 추출기는 헤드라인, 출판처(publication), 섹션, 언어와 오직 기사 자체의 단락만 포함된 51단어 분량의 본문을 반환했습니다.
3단계: 날짜를 정확하게 처리하라 (get the dates right)
날짜는 뉴스 데이터셋이 조용히 오류를 범하는 지점이며, 타이밍 자체가 분석의 핵심인 경우가 많습니다. 세 가지 규칙을 따릅니다:
- 구조화된 데이터에서 가져온 datePublished를 우선하고, 그다음 article:published_time 메타 태그를 사용하며, 마지막으로 눈에 보이는 텍스트에서 파싱한 내용은 최후의 수단으로만 사용합니다.
- UTC로 정규화(Normalise)해야 합니다. +01:00 시간대에 오전 9시 30분에 발행된 기사는 UTC 기준 오전 8시 30분이며, 여러 발행처의 현지 시간을 비교하면 의미 없는 순서가 도출됩니다.
- 신뢰할 수 없는 정보는 거부해야 합니다. to_utc 함수는 시간대 정보가 없거나 미래 날짜에 대해서는 None을 반환하므로, 단순히 잘못 정렬되는 것보다 플래그를 지정하여 처리할 수 있습니다.
dateModified는 별도로 보관합니다. 오늘 오후에 업데이트된 기사라도 처음 발행된 시점은 아침일 수 있기 때문입니다.
4단계: MinHash로 배포된 사본을 클러스터링하라 (cluster syndicated copies with MinHash)
정규(Canonical) URL은 정확한 중복을 포착합니다. 배포된 기사(Syndicated copies)의 경우, 매체들이 한 줄을 추가하거나 헤드라인을 변경하거나 출처를 첨부하기 때문에 유사 중복 감지(near-duplicate detection)가 필요합니다. 모든 기사를 다른 모든 기사와 비교하는 것은 제곱 시간 복잡도(quadratic)이며 대규모에서는 비실용적입니다. MinHash와 지역성 민감 해싱(Locality-Sensitive Hashing, LSH)을 사용하면 이 문제를 해결할 수 있습니다. 각 기사는 짧은 시그니처가 되며, 최소한 하나의 밴드(band)에서 시그니처가 충돌하는 경우에만 비교합니다.
import hashlib
import re
from collections import defaultdict
...
테스트 결과, "UPDATED:"를 접두사로 붙이고 출처 라인을 추가한 배포된 기사는 원본과 0.86의 추정 유사도를 보였으며 후보 쌍으로 반환되었습니다. 관련 없는 이야기는 0.0점을 기록하여 비교되지 않았습니다. 4개 행(rows)짜리 16개 밴드(bands)를 사용했을 때, 약 0.5 이상의 유사도를 가진 쌍이 후보가 될 가능성이 높으므로, 병합하기 전에 각 후보에 대해 추정 유사도(예: 최소 0.8 이상)로 확인해야 합니다. 이 임계값을 조정하려면 밴드와 행 분할을 미세 조정하십시오.
5단계: 기사를 유지하고, 배포 기록도 유지합니다.
확인된 유사 중복들을 스토리 클러스터(story clusters)로 그룹화합니다. 각 클러스터에 대해 정규 기사(canonical article)(일반적으로 가장 먼저 발행되었거나 식별 가능한 경우 원본 출처)를 선택하고, 해당 기사를 게재한 전체 매체 목록과 URL을 유지합니다.
복사본들을 버리지 마십시오. 어떤 출판사가 스토리를 다루었는지, 그리고 얼마나 빠르게 다루었는지는 미디어 모니터링에서 가장 유용한 신호 중 하나입니다. 한 시간 만에 40개 매체에서 보도된 이야기는 단 2개 매체가 보도한 이야기와는 다른 사건이기 때문입니다.
실용적인 참고 사항
- 페이지 샘플의 원본 HTML을 저장하여, 발행사들이 템플릿을 변경했을 때 추출 작업을 다시 실행할 수 있도록 합니다.
- 발행사별로 추출 품질을 모니터링합니다. 평균 본문 길이 또는 유효 날짜를 가진 기사의 비율이 갑자기 감소하는 것은 보통 레이아웃 변경을 의미합니다. 이를 데이터 품질 평가(assessing data quality) 프레임워크 내의 다른 측정 기준들과 함께 품질 점검으로 간주해야 합니다.
- 발행사들이 제공하는 피드 및 사이트맵을 통해 출처를 파악합니다. 이는 섹션 페이지를 반복적으로 크롤링하는 것보다 발행사와 사용자 모두에게 가볍습니다.
- 책임감 있게 수집합니다. 각 사이트의 robots.txt와 이용 약관을 존중하고, 요청 빈도를 낮게 유지하며, 페이월(paywall)을 우회하려는 시도를 하지 않습니다. 분석용 텍스트를 저장하고 전체 기사를 재배포하지 않도록 합니다.
마무리하며 (Wrapping up)
대규모 뉴스 스크래핑은 수집 문제인 동시에 중복 제거 문제입니다. 무언가를 저장하기 전에 URL을 정규화(canonicalize)하고, 페이지가 아닌 기사 자체를 추출하며, 날짜는 UTC로 표준화하고 신뢰할 수 없는 날짜는 거부한 다음, MinHash를 사용하여 배포된 사본들을 클러스터링하여 페이지가 아닌 '기사' 단위로 수를 세어야 합니다. 모든 기사에 대한 배포 목록을 유지하면 깨끗한 데이터셋과 각 기사가 얼마나 멀리 이동했는지에 대한 측정 기준을 모두 갖게 됩니다.
자주 묻는 질문 (FAQ)
중복된 뉴스 기사를 어떻게 감지하나요?
먼저 추적 매개변수 및 모바일 또는 인쇄본 변형을 제거하기 위해 URL을 정규화합니다. 그런 다음, 싱글링(shingling)과 지역성 민감 해싱(locality-sensitive hashing)을 사용해 작은 편집이 가해진 배포 사본과 같은 근접 중복(near-duplicates)을 감지하고, 이를 기사 클러스터로 그룹화하기 전에 추정 유사도 임계값으로 후보 쌍들을 확인합니다.
뉴스 스크래핑 데이터셋은 어떤 필드를 포함해야 하나요?
헤드라인, 본문 텍스트, 발행처(publication), 섹션, UTC로 표준화된 발행 날짜 및 수정 날짜, 언어, 정규화된 URL, 수집 시간, 그리고 중복 제거된 데이터셋의 경우 기사가 포함된 매체 목록과 함께 스토리 클러스터 ID를 포함해야 합니다.
뉴스 페이지에서 깨끗한 기사 텍스트를 추출하는 방법은 무엇인가요?
헤드라인, 날짜 및 발행사에 대한 schema.org NewsArticle 마크업과 같은 구조화된 데이터를 읽고, 내비게이션(navigation), 사이드바(asides), 그림(figures), 푸터(footers) 및 캡션과 같은 매우 짧은 단락을 제거한 후 article 요소에서 본문 텍스트를 추출해야 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기