17.Python으로 뉴스 요약 자동화하기 — 크롤링부터 GPT 요약, 이메일 발송까지

저도 예전에는 트렌드를 놓치지 않으려고 매일 아침 수십 개의 IT 뉴스 사이트를 북마크해 두고 일일이 들어가서 읽었어요. 그런데 정작 제목에 낚여 영양가 없는 글을 읽느라 시간을 버리거나, 정독하다 지쳐 진짜 중요한 핵심 뉴스는 놓치기 일쑤였습니다. '이 짓을 매일 해야 하나' 회의감이 들 때쯤 파이썬 크롤러와 GPT API를 엮어봤습니다. 매일 아침 지정된 시간에 주요 뉴스들을 싹 긁어와 핵심만 딱 3줄로 요약해 주는 자동화 봇을 만든 거죠. 이제는 출근길에 폰으로 요약본만 슥 봐도 하루 흐름이 다 보입니다. 정보에 매몰되던 아침이 완전히 달라졌어요.

이 글에서는 뉴스 RSS 피드를 파싱해 기사를 수집하고, OpenAI API로 자동 요약한 뒤, 결과를 CSV로 저장하고 이메일로 발송하는 전 과정을 — 이전 시리즈에서 배운 내용을 모두 연결해 하나의 완성된 자동화 파이프라인으로 구축해 드리겠습니다.


이 프로젝트의 구조 — 세 가지 기술을 하나로 잇기

이 글은 단순한 입문 가이드가 아닙니다. 이전 시리즈에서 배운 세 가지 핵심 기술을 하나의 실전 프로젝트로 연결합니다.

[뉴스 RSS 수집] → [GPT 요약] → [CSV 저장 + 이메일 발송]

  (크롤링 편)      (OpenAI 편)    (CSV 편 + 신규)

비유하자면 신문사 인턴을 고용한 것과 같습니다. 인턴이 매일 아침 지정된 언론사 사이트를 돌며 기사를 수집하고(크롤링), 핵심을 3줄로 정리해(GPT 요약), 깔끔한 표로 만들어 팀장에게 이메일로 보내주는(CSV + 이메일) 구조입니다. 코드를 한 번 만들어두면 이 인턴은 매일 쉬지 않고 같은 일을 반복합니다.

전체 파이프라인 흐름

  1. RSS 피드 파싱 — 주요 언론사의 RSS로 최신 기사 제목·링크·날짜 수집
  2. 본문 크롤링 — 각 기사 링크에 접속해 본문 텍스트 추출
  3. GPT 요약 — OpenAI API로 기사마다 3줄 요약 생성
  4. CSV 저장 — 수집 일시, 제목, 요약, 링크를 파일로 저장
  5. 이메일 발송 — 요약본을 HTML 형식으로 자동 이메일 전송

환경 설정 — 필요한 라이브러리 한 번에 설치

pip install feedparser requests beautifulsoup4 openai python-dotenv

라이브러리 역할
feedparser RSS/Atom 피드 파싱
requests 기사 본문 페이지 요청
beautifulsoup4 HTML에서 본문 텍스트 추출
openai GPT API 호출
python-dotenv API 키 환경 변수 관리

.env 파일을 만들어 API 키를 저장합니다.

OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

EMAIL_ADDRESS=your_email@gmail.com

EMAIL_PASSWORD=your_app_password

⚠️ Gmail 사용 시 일반 비밀번호가 아닌 앱 비밀번호를 생성해 사용해야 합니다. Google 계정 → 보안 → 2단계 인증 활성화 → 앱 비밀번호 생성 순서로 진행합니다.


STEP 1 — RSS 피드로 뉴스 기사 수집하기

RSS는 뉴스 사이트가 최신 기사 목록을 자동으로 제공하는 표준 형식입니다. 크롤링보다 훨씬 안정적이고 서버 부담도 없습니다. 대부분의 주요 언론사가 RSS를 공개 제공합니다.

from datetime import datetime
import feedparser
import requests
import urllib3

# SSL 경고 메시지 무시 설정
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# 차단 우회가 가능한 검증된 RSS 주소
RSS_FEEDS = {
    "연합뉴스 최근뉴스": "https://www.yna.co.kr/rss/news.xml",
    "ZDNet Korea": "https://zdnet.co.kr/rss/all.xml",
}


def fetch_rss_articles(feeds: dict, max_per_feed: int = 5) -> list:
    articles = []

    # 브라우저와 동일한 헤더 설정
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    }

    for source, url in feeds.items():
        try:
            # requests를 통한 보안 및 SSL 우회 요청
            response = requests.get(url, headers=headers, verify=False, timeout=10)
            response.raise_for_status()

            # 인코딩 강제 지정 후 feedparser로 전달
            response.encoding = "utf-8"
            feed = feedparser.parse(response.text)

        except Exception as e:
            print(f"[{source}] 네트워크 오류 또는 차단됨: {e}")
            continue

        if not feed.entries:
            print(f"[{source}] 피드 데이터를 가져오지 못했습니다. 구조를 확인하세요.")
            continue

        for entry in feed.entries[:max_per_feed]:
            articles.append(
                {
                    "source": source,
                    "title": entry.get("title", "제목 없음"),
                    "link": entry.get("link", ""),
                    "published": entry.get("published", str(datetime.now())),
                    "summary": entry.get(
                        "summary", entry.get("description", "")
                    ),
                }
            )
            print(f"[{source}] {entry.get('title', '')}")

    print(f"\n{len(articles)}개 기사 수집 완료\n")
    return articles


articles = fetch_rss_articles(RSS_FEEDS)

[📸 스크린샷 삽입: RSS 수집 결과 — 기사 제목 목록이 터미널에 출력된 화면]


STEP 2 — 기사 본문 크롤링하기

RSS에는 기사 제목과 간략한 소개만 담겨 있는 경우가 많습니다. 더 정확한 요약을 위해 본문 전체를 가져옵니다.

import requests

from bs4 import BeautifulSoup

import time

def fetch_article_body(url: str, timeout: int = 10) -> str:

    try:

        headers = {"User-Agent": "Mozilla/5.0"}

        response = requests.get(url, headers=headers, timeout=timeout)

        response.raise_for_status()

        soup = BeautifulSoup(response.text, "html.parser")

        # 불필요한 태그 제거

        for tag in soup(["script", "style", "nav", "footer", "header", "aside"]):

            tag.decompose()

        # 본문 텍스트 추출 (p 태그 기준)

        paragraphs = soup.find_all("p")

        body = " ".join(p.get_text(strip=True) for p in paragraphs if len(p.get_text(strip=True)) > 30)

        return body[:3000]  # 토큰 절약을 위해 앞부분 3000자만 사용

    except Exception as e:

        print(f"본문 수집 실패 ({url}): {e}")

        return ""

# 각 기사에 본문 추가

for article in articles:

    article["body"] = fetch_article_body(article["link"])

    time.sleep(1)  # 서버 부하 방지

💡 본문을 3000자로 자르는 이유: GPT API는 입력 토큰에 비례해 비용이 발생합니다. 기사 전체를 넣을 필요 없이 앞부분만으로도 핵심 요약이 충분히 가능합니다.



STEP 3 — OpenAI API로 기사 자동 요약하기

from openai import OpenAI

from dotenv import load_dotenv

import os

load_dotenv()

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def summarize_article(title: str, body: str) -> str:

    # 본문이 없을 경우 RSS 요약으로 대체

    content = body if len(body) > 100 else title

    try:

        response = client.chat.completions.create(

            model="gpt-4o-mini",

            messages=[

                {

                    "role": "system",

                    "content": (

                        "당신은 IT 뉴스 요약 전문가입니다. "

                        "주어진 기사를 아래 형식으로 요약하세요.\n\n"

                        "- 핵심 내용 1줄\n"

                        "- 주요 사실 또는 수치\n"

                        "- 시사점 또는 전망\n\n"

                        "각 항목은 한 문장으로 작성하고, 반드시 한국어로 출력하세요."

                    )

                },

                {

                    "role": "user",

                    "content": f"제목: {title}\n\n본문: {content}"

                }

            ],

            max_tokens=300,

            temperature=0.3

        )

        return response.choices[0].message.content.strip()

    except Exception as e:

        return f"요약 실패: {e}"

# 전체 기사 요약

for article in articles:

    article["gpt_summary"] = summarize_article(article["title"], article["body"])

    print(f"✅ 요약 완료: {article['title'][:40]}...")

    time.sleep(0.5)  # API 호출 간격




STEP 4 — CSV로 저장하기

import csv

from datetime import datetime

def save_to_csv(articles: list, filename: str = None) -> str:

    if not filename:

        today = datetime.now().strftime("%Y%m%d")

        filename = f"news_summary_{today}.csv"

    with open(filename, "w", encoding="utf-8-sig", newline="") as f:

        fieldnames = ["수집일시", "출처", "제목", "GPT요약", "링크"]

        writer = csv.DictWriter(f, fieldnames=fieldnames)

        writer.writeheader()

        for article in articles:

            writer.writerow({

                "수집일시": datetime.now().strftime("%Y-%m-%d %H:%M"),

                "출처": article.get("source", ""),

                "제목": article.get("title", ""),

                "GPT요약": article.get("gpt_summary", ""),

                "링크": article.get("link", ""),

            })

    print(f"CSV 저장 완료: {filename}")

    return filename

csv_file = save_to_csv(articles)



STEP 5 — 이메일로 자동 발송하기

수집·요약된 뉴스를 HTML 형식으로 이메일로 발송합니다.

import smtplib

from email.mime.multipart import MIMEMultipart

from email.mime.text import MIMEText

from datetime import datetime

import os

from dotenv import load_dotenv

load_dotenv()

def build_html(articles: list) -> str:

    today = datetime.now().strftime("%Y년 %m월 %d일")

    rows = ""

    for a in articles:

        summary_html = a.get("gpt_summary", "").replace("\n", "<br>")

        rows += f"""

        <tr>

            <td style="padding:12px; border-bottom:1px solid #eee;">

                <strong><a href="{a['link']}" style="color:#1a73e8; text-decoration:none;">{a['title']}</a></strong>

                <br><small style="color:#888;">{a['source']}</small>

                <br><br>{summary_html}

            </td>

        </tr>"""

    return f"""

    <html><body style="font-family: Arial, sans-serif; max-width:700px; margin:auto;">

        <h2 style="color:#333; border-bottom:2px solid #1a73e8; padding-bottom:8px;">

            📰 {today} IT 뉴스 요약

        </h2>

        <table width="100%" cellpadding="0" cellspacing="0">{rows}</table>

        <p style="color:#aaa; font-size:12px; margin-top:20px;">

            이 메일은 Python 자동화 시스템이 발송했습니다.

        </p>

    </body></html>"""

def send_email(articles: list, to_address: str):

    from_address = os.getenv("EMAIL_ADDRESS")

    password = os.getenv("EMAIL_PASSWORD")

    today = datetime.now().strftime("%Y-%m-%d")

    msg = MIMEMultipart("alternative")

    msg["Subject"] = f"[자동발송] {today} IT 뉴스 요약 {len(articles)}건"

    msg["From"] = from_address

    msg["To"] = to_address

    html_content = build_html(articles)

    msg.attach(MIMEText(html_content, "html"))

    with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:

        server.login(from_address, password)

        server.sendmail(from_address, to_address, msg.as_string())

    print(f"이메일 발송 완료 → {to_address}")

send_email(articles, to_address="recipient@example.com")



STEP 6 — 전체 파이프라인 통합 + 스케줄러 연동

지금까지의 단계를 하나의 함수로 묶고, 매일 자동 실행되도록 스케줄러를 연결합니다.

pip install schedule

import schedule

import time

def run_pipeline():

    print(f"\n{'='*50}")

    print(f"뉴스 요약 파이프라인 시작: {datetime.now().strftime('%Y-%m-%d %H:%M')}")

    print('='*50)

    # 1. 수집

    articles = fetch_rss_articles(RSS_FEEDS, max_per_feed=5)

    # 2. 본문 크롤링

    for article in articles:

        article["body"] = fetch_article_body(article["link"])

        time.sleep(1)

    # 3. GPT 요약

    for article in articles:

        article["gpt_summary"] = summarize_article(article["title"], article["body"])

        time.sleep(0.5)

    # 4. CSV 저장

    save_to_csv(articles)

    # 5. 이메일 발송

    send_email(articles, to_address=os.getenv("EMAIL_ADDRESS"))

    print("\n파이프라인 완료.\n")

# 매일 오전 8시에 자동 실행

schedule.every().day.at("08:00").do(run_pipeline)

print("스케줄러 시작. 매일 오전 8시에 뉴스 요약을 발송합니다.")

print("종료하려면 Ctrl+C를 누르세요.\n")

while True:

    schedule.run_pending()

    time.sleep(60)

💡 실 운영 팁: 이 스크립트를 항상 켜진 서버나 Raspberry Pi에 올려두거나, Windows 작업 스케줄러 / macOS의 cron에 등록하면 컴퓨터를 열지 않아도 매일 자동으로 실행됩니다.


마무리 — 핵심 요약

✅ 뉴스 요약 자동화 파이프라인 체크리스트

  1. RSS 피드 수집feedparser로 기사 목록 파싱, 언론사별 RSS URL 확인
  2. 본문 크롤링requests + BeautifulSoup으로 본문 추출, time.sleep(1) 필수
  3. GPT 요약gpt-4o-mini + temperature=0.3, 입력 텍스트는 3000자로 제한
  4. CSV 저장utf-8-sig 인코딩, 날짜를 파일명에 포함해 일별 관리
  5. 이메일 발송 — Gmail 앱 비밀번호 사용, HTML 형식으로 가독성 확보
  6. 스케줄러 등록schedule 라이브러리로 매일 지정 시간에 자동 실행

이 프로젝트를 확장하는 방법

이 파이프라인은 뉴스 요약 외에도 응용 범위가 넓습니다.

  • 키워드 필터링 추가 — 특정 키워드가 포함된 기사만 선별해 발송
  • Slack 연동 — 이메일 대신 팀 슬랙 채널로 자동 발송
  • Streamlit 대시보드 연결 — 수집된 CSV를 실시간 웹 대시보드로 시각화
  • 카테고리 분류 추가 — GPT로 기사를 "정책/기술/시장" 등으로 분류 후 정리
  • 감성 분석 추가 — 각 기사의 긍정/부정 톤을 분석해 시장 동향 파악


댓글

이 블로그의 인기 게시물

1.Python 설치부터 실행까지 10분 만에 끝내기 — 초보자도 바로 따라 하는 완벽 가이드

30.Python pyautogui로 마우스와 키보드 자동화하기: VSCode 실전 가이드

29.Python subprocess로 외부 명령어 실행하기: VSCode 실전 활용법