17.Python으로 뉴스 요약 자동화하기 — 크롤링부터 GPT 요약, 이메일 발송까지
이 글에서는 뉴스 RSS 피드를 파싱해 기사를 수집하고, OpenAI API로 자동 요약한 뒤, 결과를 CSV로 저장하고 이메일로 발송하는 전 과정을 — 이전 시리즈에서 배운 내용을 모두 연결해 하나의 완성된 자동화 파이프라인으로 구축해 드리겠습니다.
이 프로젝트의 구조 — 세 가지 기술을 하나로 잇기
이 글은 단순한 입문 가이드가 아닙니다. 이전 시리즈에서 배운 세 가지 핵심 기술을 하나의 실전 프로젝트로 연결합니다.
[뉴스 RSS 수집] → [GPT 요약] → [CSV 저장 + 이메일 발송]
(크롤링 편) (OpenAI 편) (CSV 편 + 신규)
비유하자면 신문사 인턴을 고용한 것과 같습니다. 인턴이 매일 아침 지정된 언론사 사이트를 돌며 기사를 수집하고(크롤링), 핵심을 3줄로 정리해(GPT 요약), 깔끔한 표로 만들어 팀장에게 이메일로 보내주는(CSV + 이메일) 구조입니다. 코드를 한 번 만들어두면 이 인턴은 매일 쉬지 않고 같은 일을 반복합니다.
전체 파이프라인 흐름
- RSS 피드 파싱 — 주요 언론사의 RSS로 최신 기사 제목·링크·날짜 수집
- 본문 크롤링 — 각 기사 링크에 접속해 본문 텍스트 추출
- GPT 요약 — OpenAI API로 기사마다 3줄 요약 생성
- CSV 저장 — 수집 일시, 제목, 요약, 링크를 파일로 저장
- 이메일 발송 — 요약본을 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 datetimeimport feedparserimport requestsimport 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에 등록하면 컴퓨터를 열지 않아도 매일 자동으로 실행됩니다.
마무리 — 핵심 요약
✅ 뉴스 요약 자동화 파이프라인 체크리스트
- RSS 피드 수집 —
feedparser로 기사 목록 파싱, 언론사별 RSS URL 확인 - 본문 크롤링 —
requests + BeautifulSoup으로 본문 추출,time.sleep(1)필수 - GPT 요약 —
gpt-4o-mini+temperature=0.3, 입력 텍스트는 3000자로 제한 - CSV 저장 —
utf-8-sig인코딩, 날짜를 파일명에 포함해 일별 관리 - 이메일 발송 — Gmail 앱 비밀번호 사용, HTML 형식으로 가독성 확보
- 스케줄러 등록 —
schedule라이브러리로 매일 지정 시간에 자동 실행
이 프로젝트를 확장하는 방법
이 파이프라인은 뉴스 요약 외에도 응용 범위가 넓습니다.
- 키워드 필터링 추가 — 특정 키워드가 포함된 기사만 선별해 발송
- Slack 연동 — 이메일 대신 팀 슬랙 채널로 자동 발송
- Streamlit 대시보드 연결 — 수집된 CSV를 실시간 웹 대시보드로 시각화
- 카테고리 분류 추가 — GPT로 기사를 "정책/기술/시장" 등으로 분류 후 정리
- 감성 분석 추가 — 각 기사의 긍정/부정 톤을 분석해 시장 동향 파악
댓글
댓글 쓰기