Python으로 네이버·구글 뉴스 자동 수집 — 봇이 뉴스를 읽는 방법

JARVIS의 뉴스 수집봇이 네이버 랭킹뉴스와 구글 뉴스를 어떻게 긁어오는지. BeautifulSoup + feedparser 실사용 코드.

Python으로 네이버·구글 뉴스 자동 수집 — 봇이 뉴스를 읽는 방법

왜 직접 만들었나

뉴스 API를 쓰면 편합니다. 네이버 뉴스 API, 구글 뉴스 API 등.

문제:

  • 네이버 뉴스 API: 검색은 되지만 랭킹 뉴스(지금 가장 많이 본 뉴스)는 안 됨
  • 구글 뉴스 API: 유료, 쿼터 제한
  • RSS: 일부 언론사만 제공

결국 직접 스크래핑합니다.


구조

class JarvisSmart:
    def get_naver_news(self) -> list[dict]    # 네이버 랭킹 뉴스
    def get_google_news(self) -> list[dict]   # 구글 뉴스 RSS
    def get_all_news(self) -> list[dict]      # 통합 + 중복 제거

네이버 뉴스 수집

네이버 뉴스 랭킹 페이지를 파싱합니다.

import requests
from bs4 import BeautifulSoup

def get_naver_news(self) -> list[dict]:
    url = "https://news.naver.com/main/ranking/popularDay.naver"
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
    }
    
    response = requests.get(url, headers=headers, timeout=10)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    results = []
    items = soup.select('.rankingnews_box .list_content a')
    
    for item in items[:30]:
        title = self.clean_title(item.get_text())
        if len(title) > 8:  # 너무 짧은 건 제목 아님
            results.append({
                'title': title,
                'source': 'Naver',
                'link': item.get('href')
            })
    
    return results

clean_title 함수:

언론사 이름이 제목에 붙어 나오는 경우가 많습니다.

원본: "윤석열 대통령 발언 - 조선일보"
정리: "윤석열 대통령 발언"
def clean_title(self, title: str) -> str:
    media_list = ['조선일보', '중앙일보', '동아일보', '한겨레', 'MBC', 'KBS', ...]
    
    for m in media_list:
        title = title.replace(f' - {m}', '').replace(f'[{m}]', '')
    
    # 대괄호, 소괄호 내용 제거
    title = re.sub(r'\[.*?\]', '', title)
    title = re.sub(r'\(.*?\)', '', title)
    
    return title.strip()

구글 뉴스 RSS

구글 뉴스는 RSS를 제공합니다. feedparser로 파싱합니다.

import feedparser

def get_google_news(self) -> list[dict]:
    # 한국어 뉴스 RSS
    url = "https://news.google.com/rss?hl=ko&gl=KR&ceid=KR:ko"
    
    feed = feedparser.parse(url)
    results = []
    
    for entry in feed.entries[:20]:
        title = self.clean_title(entry.title)
        results.append({
            'title': title,
            'source': 'Google',
            'link': entry.link,
            'published': entry.get('published', '')
        })
    
    return results

중복 제거

네이버와 구글에서 같은 뉴스가 나올 수 있습니다. 제목 유사도로 중복을 제거합니다.

from difflib import SequenceMatcher

def deduplicate(self, items: list[dict]) -> list[dict]:
    unique = []
    
    for item in items:
        is_dup = False
        for u in unique:
            ratio = SequenceMatcher(
                None,
                item['title'],
                u['title']
            ).ratio()
            
            if ratio > 0.7:  # 70% 이상 비슷하면 중복
                is_dup = True
                break
        
        if not is_dup:
            unique.append(item)
    
    return unique

겪었던 문제

문제 1: User-Agent 없으면 403

# 이렇게 하면 403
response = requests.get(url)

# 이렇게 해야 됨
response = requests.get(url, headers={'User-Agent': 'Mozilla/5.0...'})

문제 2: 네이버 HTML 구조 변경

네이버가 HTML 구조를 바꾸면 CSS 셀렉터가 안 맞습니다. 여러 셀렉터를 순서대로 시도합니다:

selectors = [
    '.rankingnews_box .list_content a',
    'div.ranking_headline > a',
    '.list_content a',
]

for selector in selectors:
    items = soup.select(selector)
    if items:
        break  # 첫 번째로 결과 나오는 셀렉터 사용

문제 3: feedparser가 인코딩 오류

일부 RSS 피드가 인코딩 정보를 잘못 선언합니다.

# feedparser가 알아서 처리하지만
# 한글이 깨지면:
feed = feedparser.parse(url)
for entry in feed.entries:
    try:
        title = entry.title.encode('latin-1').decode('utf-8')
    except (UnicodeEncodeError, UnicodeDecodeError):
        title = entry.title  # 그냥 원본 사용

수집한 뉴스로 뭘 하나

JARVIS에서:

  1. 30분마다 자동 수집 (NimCuration 스케줄러)
  2. LLM으로 요약 (mistral-nemotron 또는 Ollama)
  3. Telegram 그룹으로 전송
  4. Pearl Finder에 키워드로 전달

뉴스 수집 자체는 간단합니다. 수집한 뉴스를 어떻게 처리하느냐가 핵심입니다.


주의사항

스크래핑 시 서비스 이용약관을 확인하세요. 과도한 요청은 IP 차단될 수 있습니다.

  • 요청 간 딜레이: time.sleep(1~2)
  • robots.txt 확인
  • 상업적 사용 시 공식 API 우선 검토
#Python#스크래핑#뉴스수집#BeautifulSoup#feedparser