파이썬 뉴스 봇 만들기 - 매일 아침 관심 뉴스 요약 메일 받기
아침에 출근하면 제일 먼저 뭐 하세요? 저는 예전에 커피 한 잔 들고 자리에 앉으면 포털 검색창부터 열었거든요. "인공지능" 검색해서 밤새 나온 기사 훑고, 다시 "환율" 쳐서 훑고, 관심 있는 회사 이름 또 쳐서 훑고. 이걸 매일 반복하다 보니까 어느 날 문득 이런 생각이 들더라고요.
이거, 컴퓨터가 대신 해주면 안 되나?
그래서 이번엔 진짜 그걸 만들어볼 거예요. 내가 정해둔 키워드의 최신 뉴스를 알아서 긁어와서, 보기 좋게 정리한 다음, 내 메일함으로 딱 보내주는 나만의 뉴스 봇이요. 아침에 메일함만 열면 오늘 챙겨야 할 뉴스가 카드처럼 착착 정리돼 있는 거죠.
그런데 여기서 좀 놀라운 얘기를 하나 할게요. 이걸 만드는 데 필요한 재료가 사실 다 우리 손에 있어요. 새로 배울 게 거의 없거든요.
오늘은 새 부품을 사는 게 아니라, 있던 걸 조립하는 날
이 편은 지금까지 웹·메일 파트에서 배운 걸 하나로 합치는 미니 프로젝트예요. 레고 조립이라고 생각하시면 딱 맞아요. 그동안 상자별로 부품을 사뒀잖아요. 이번엔 설명서 보고 그걸 한 덩어리로 끼워 맞추는 거예요.
어떤 부품이 들어가는지 미리 보여드릴게요.
- 뉴스 수집 — 18에서 배운 네이버 검색 API. 키워드 주면 관련 뉴스를 긁어오죠.
- 예쁘게 정리 — 14에서 배운 HTML 메일. 밋밋한 글자를 카드 모양으로 꾸미는 거요.
- 메일 발송 — 13에서 배운 smtplib. 내 메일함으로 쏘는 부분이에요.
- (옵션) 슬랙 알림 — 19에서 배운 웹훅. "오늘 뉴스 도착!" 신호 보내기.
보이시죠? 하나도 새로운 게 없어요. 이미 다 해본 것들이에요. 오늘은 이걸 순서대로 이어붙이기만 하면 돼요.
전체 흐름은 이렇게 흘러갈 거예요.
키워드 정하기 → 뉴스 수집 → HTML로 조립 → 메일 발송
이 네 단계를 함수 네 개로 나눠서 하나씩 만들어볼게요. 한 덩어리씩 보면 하나도 안 어려워요.
Step 1. 관심 키워드 목록 정하기
제일 먼저, 내가 어떤 뉴스를 받고 싶은지 정해야겠죠. 이건 그냥 리스트 하나면 끝이에요.
KEYWORDS = ["인공지능", "부동산", "환율"] # 내가 받고 싶은 뉴스 키워드
이게 이 프로젝트의 진짜 재미 포인트예요. 여기만 바꾸면 완전히 내 것이 되거든요. 응원하는 야구팀 이름을 넣어도 되고, 다니는 회사 이름을 넣어도 되고, 요즘 꽂힌 관심사를 넣어도 돼요. 나머지 코드는 그대로 두고 이 줄만 바꾸면 나만의 뉴스 봇이 되는 거죠.
뉴스 자동 수집: 네이버 검색 API로 키워드 뉴스 긁기
이제 키워드 하나를 받아서 관련 뉴스를 가져오는 부분이에요. 18에서 만들어봤던 그 검색 코드, 기억나시죠? 거의 그대로 가져다 쓸 거예요.
import requests # 네이버 API 호출 (18에서 배운 거)
def clean(text): # 제목에 섞여 나오는 <b> 강조 태그를 지웁니다
return text.replace("<b>", "").replace("</b>", "")
def collect_news(keyword, count=3): # 키워드 하나의 뉴스를 수집
url = "https://openapi.naver.com/v1/search/news.json" # 뉴스 검색 주소
headers = {"X-Naver-Client-Id": NAVER_ID, # 내 앱 아이디
"X-Naver-Client-Secret": NAVER_SECRET} # 내 앱 비밀키
params = {"query": keyword, "display": count, "sort": "date"} # 검색어, 개수, 최신순
try:
res = requests.get(url, headers=headers, params=params, timeout=10) # 요청 보내기
items = res.json().get("items", []) # 결과 뉴스 목록 꺼내기
return [{"title": clean(i["title"]), # 제목
"desc": clean(i["description"]), # 요약 설명
"link": i["link"]} for i in items] # 링크
except Exception as e: # 이 키워드만 실패해도 전체는 안 멈추게
print(f"[{keyword}] 수집 실패: {e}")
return [] # 실패하면 빈 목록 반환
여기서 잠깐 짚고 넘어갈 게 두 개 있어요.
하나는 clean 함수예요. 네이버 검색 결과를 받아보면 제목에 <b> 같은 태그가 껴 있을 때가 있거든요. 검색어를 굵게 강조하려고 붙는 거예요. 이게 그대로 화면에 나오면 지저분하니까 .replace로 지워주는 거죠. 이것도 18에서 겪어보셨을 거예요.
또 하나는 try/except. 뉴스 수집하다가 인터넷이 잠깐 끊기거나 하면 오류가 나잖아요. 그때 이걸 안 감싸두면 프로그램 전체가 그 자리에서 멈춰버려요. 그래서 "이 키워드 하나가 실패해도 나머지는 계속 가자"는 안전장치로 감싸둔 거예요. 실패하면 그냥 빈 목록을 돌려주고 넘어가죠.
뉴스 요약 메일 예쁘게 만들기 (HTML 조립)
수집한 뉴스를 이제 보기 좋게 꾸며야죠. 14에서 배운 HTML 메일이 여기서 나와요.
혹시 HTML 조립이라는 말이 좀 어렵게 느껴지시나요? 사실 별거 없어요. HTML도 결국 그냥 긴 문자열이에요. 뉴스가 5건이면, 카드 모양 HTML 조각을 5번 만들어서 쭉 이어붙이는 거예요. 표를 손으로 하나하나 그리는 게 아니라, 반복문으로 찍어내는 거죠.
from datetime import date # 오늘 날짜
def build_html(news_by_keyword): # 수집 결과를 HTML로 조립 (14)
today = date.today().isoformat() # 오늘 날짜 (2026-08-03 형태)
parts = [f'<h2>오늘의 뉴스 요약 ({today})</h2>'] # 맨 위 제목
for keyword, items in news_by_keyword.items(): # 키워드마다
parts.append(f'<h3 style="color:#333;">🔎 {keyword}</h3>') # 키워드 소제목
if not items: # 이 키워드에 뉴스가 하나도 없으면
parts.append('<p style="color:#888;">새 뉴스가 없어요.</p>')
continue # 다음 키워드로 넘어감
for n in items: # 뉴스 하나 = 카드 한 장
parts.append(
f'<div style="border:1px solid #eee;border-radius:8px;'
f'padding:12px;margin:8px 0;">'
f'<a href="{n["link"]}" style="font-weight:bold;'
f'color:#1a73e8;text-decoration:none;">{n["title"]}</a>'
f'<p style="color:#666;font-size:13px;margin:6px 0 0;">'
f'{n["desc"]}</p></div>')
return "".join(parts) # 조각들을 한 문자열로 합치기
여기가 이중 반복문이라 처음엔 좀 헷갈릴 수 있어요. 저도 처음엔 for가 두 번 겹치니까 머리가 뱅뱅 돌더라고요.
이렇게 생각해보세요. 바깥쪽 for는 폴더(키워드)를 하나씩 여는 거예요. 안쪽 for는 그 폴더 안에 든 파일(뉴스)을 하나씩 꺼내는 거고요. "인공지능" 폴더 열어서 뉴스 3개 꺼내고, "부동산" 폴더 열어서 또 3개 꺼내고. 그렇게 도는 거죠.
그리고 style="..."처럼 각 태그에 색이랑 여백을 직접 써준 거 보이시죠? 이걸 인라인 스타일이라고 하는데요. 14에서도 강조했지만, 메일 프로그램들은 <style> 블록이나 외부 CSS를 잘 무시해버려요. 그래서 꾸미기는 각 태그 안에 직접 박아넣는 게 안전해요.
if not items: 이 부분도 눈여겨보세요. 특정 키워드에 새 뉴스가 하나도 안 잡히면, 빈 칸이 아니라 "새 뉴스가 없어요"라는 문구를 넣어주는 거예요. 이걸 안 해두면 어느 날 뉴스가 비었을 때 텅 빈 메일이 와서 당황하실 수 있거든요.
내 메일함으로 발송하기
조립한 HTML을 이제 메일로 쏠 차례예요. 13이랑 14에서 배운 게 여기서 딱 만나요.
import smtplib # 메일 발송 (13)
from email.mime.text import MIMEText # HTML 메일 본문 (14)
def send_email(html): # HTML 메일 발송
msg = MIMEText(html, "html") # 두 번째 인자 "html"이 핵심!
msg["Subject"] = f"오늘의 뉴스 요약 ({date.today().isoformat()})" # 제목에 날짜
msg["From"] = EMAIL # 보내는 사람 (내 메일)
msg["To"] = EMAIL # 받는 사람 (역시 내 메일)
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server: # 메일 서버 접속
server.login(EMAIL, APP_PW) # 앱 비밀번호로 로그인
server.send_message(msg) # 발송!
print("메일 발송 완료!")
# 실행 결과:
# 메일 발송 완료!
MIMEText(html, "html")에서 두 번째 자리에 "html"을 꼭 써야 해요. 이걸 빠뜨리면 카드가 예쁘게 안 보이고 <div> 같은 태그가 글자 그대로 좍 나와버려요. 14에서 한 번쯤 겪어보셨을 실수죠.
로그인에 쓰는 APP_PW는 메일 로그인 비밀번호가 아니라 따로 발급받은 앱 비밀번호예요. 이거 13에서 배웠는데도 다시 헷갈리는 분들이 많더라고요. 평소 로그인 비번을 그대로 넣으면 SMTPAuthenticationError 나면서 로그인이 안 돼요. 2단계 인증 켜고 앱 비밀번호를 따로 발급받아야 해요.
참고로 위 예시는 지메일 기준(smtp.gmail.com, 465)이에요. 13에서 네이버 메일로 실습하셨다면 그때 쓴 서버 주소로 바꾸시면 돼요.
비밀값은 한 군데 모아서 관리하기
그런데 위 코드들 보면 NAVER_ID, EMAIL, APP_PW 같은 값들이 갑자기 나오죠. 이것들은 다 .env 파일에 따로 빼둔 비밀값이에요.
이번 프로젝트는 좀 특별해요. 비밀값이 무려 세 종류나 되거든요. 네이버 앱 아이디랑 비밀키, 내 메일 주소랑 앱 비밀번호, 그리고 (옵션이지만) 슬랙 주소까지. 이걸 코드에 그대로 적어두면 나중에 실수로 남한테 코드를 보여줬을 때 큰일 나잖아요.
그래서 이렇게 맨 위에서 한 번에 불러와요.
import os # 환경변수 읽기
from dotenv import load_dotenv # .env 불러오기
load_dotenv() # .env 파일 값 로딩
NAVER_ID = os.getenv("NAVER_CLIENT_ID") # 네이버 앱 아이디
NAVER_SECRET = os.getenv("NAVER_CLIENT_SECRET") # 네이버 앱 비밀키
EMAIL = os.getenv("MY_EMAIL") # 내 메일 주소
APP_PW = os.getenv("EMAIL_APP_PW") # 메일 앱 비밀번호
비밀값이 여러 개인 프로젝트는 아마 이번이 처음이실 거예요. 이렇게 한 곳에 모아 관리하는 습관, 지금부터 들여두면 나중에 정말 편해요.
마지막 조립: main 함수로 하나로 잇기
이제 부품이 다 나왔어요. 레고로 치면 마지막 조립 단계예요. main 함수에서 수집 → 조립 → 발송을 순서대로 불러주기만 하면 돼요.
def main(): # 전체 조립
news_by_keyword = {} # 키워드별 뉴스를 담을 곳
for kw in KEYWORDS: # 키워드마다 수집
news_by_keyword[kw] = collect_news(kw)
total = sum(len(v) for v in news_by_keyword.values()) # 전체 뉴스 개수
if total == 0: # 하나도 없으면
print("오늘은 보낼 뉴스가 없습니다.")
return # 발송 안 하고 끝
html = build_html(news_by_keyword) # HTML 조립
send_email(html) # 발송
if __name__ == "__main__": # 이 파일을 직접 실행하면
main() # main 실행!
# 실행 결과:
# 메일 발송 완료!
total == 0 처리가 하나 더 들어갔어요. 모든 키워드에서 뉴스가 하나도 안 잡혔으면 굳이 빈 메일을 보낼 필요 없잖아요. 그럴 땐 그냥 "보낼 뉴스가 없습니다" 찍고 조용히 끝내는 거예요.
이제 이 파일을 실행하면요. 잠깐 사이에 뉴스를 다 긁어와서, 카드로 예쁘게 만들어서, 내 메일함으로 쏴줘요. 메일함 열어보면 오늘의 뉴스가 착 정리돼 있는 거죠. 직접 만든 봇이 일하는 거 보면 은근히 뿌듯하실 거예요.
잠깐, "요약 메일이라며 왜 요약을 안 해줘요?"
여기서 솔직하게 하나 짚고 갈게요. 이거 안 짚으면 나중에 "어? 요약해준다더니?" 하고 실망하실 수 있거든요.
지금 이 봇이 카드에 넣는 짧은 설명문 있잖아요. 그거, 우리가 만든 요약이 아니에요. 네이버 뉴스가 원래부터 딸려서 보내주는 description이라는 설명문을 그냥 그대로 가져다 쓴 거예요.
그러니까 지금 우리 봇은 정확히 말하면 요약 봇이 아니라 정리 봇이에요. 뉴스를 모아서 예쁘게 붙여주긴 하는데, 긴 기사를 읽고 세 줄로 줄여주진 못하거든요.
진짜 AI 요약, 그러니까 긴 기사를 통째로 읽고 핵심만 세 줄로 딱 줄여주는 건 바로 다음 파트에서 배울 거예요. 그때 이 봇이 정리 봇에서 진짜 요약 봇으로 진화합니다. 지금은 그 전 단계라고 생각해주세요.
여기서 더 나아가면
지금 이 봇, 완성은 됐는데 아직 아쉬운 점이 하나 있죠. "매일 아침"이라고 했는데 사실 내가 손으로 실행해야 뉴스가 와요. 버튼을 눌러야 도는 기계인 셈이죠.
이걸 진짜 매일 정해진 시간에 알아서 돌게 하는 건 스케줄링이라고 하는데, 이것도 다음에 배울 파트에서 다뤄요. 그때가 되면 아침에 일어나기도 전에 메일이 도착해 있을 거예요.
옵션으로 슬랙 알림을 붙이고 싶으시면, 19에서 배운 웹훅을 send_email 뒤에 한 줄만 추가하면 돼요.
import requests
# 메일 발송 후 슬랙에도 도착 신호 보내기 (19)
requests.post(os.getenv("SLACK_WEBHOOK_URL"),
json={"text": f"오늘 뉴스 {total}건 메일 발송 완료 :newspaper:"})
이러면 메일이랑 슬랙 알림을 동시에 받을 수 있어요.
직접 해보세요
읽기만 하면 안 남아요. 딱 두 개만 해보세요.
KEYWORDS리스트를 내 관심사로 바꿔서 실행해보기. 응원하는 팀, 좋아하는 회사, 요즘 관심 있는 주제 뭐든 좋아요. 이게 진짜 내 봇이 되는 순간이에요.- 키워드별 뉴스 개수
count를 3에서 5로 늘려보기. 힌트는collect_news(keyword, count=3)에서 이3을 어디서 바꿀지 찾아보는 거예요.
여유가 되시면 위에 있는 슬랙 옵션 한 줄까지 붙여서 메일이랑 알림을 동시에 받아보세요. 막히는 부분 있으면 댓글로 남겨주시고요.
다음 편으로
여기까지 오셨으면 웹·메일 파트를 완주하신 거예요. 뉴스를 긁어오고, 예쁘게 꾸미고, 메일로 쏘고. 따로따로 배웠던 조각들이 하나의 시스템으로 딱 맞물리는 걸 직접 보셨잖아요. 이게 진짜 프로그래밍의 재미거든요.
그런데 아까 말했듯이 우리 봇은 아직 정리만 할 뿐이에요. 긴 기사를 보면 "이거 세 줄로 요약해줘" 하고 싶잖아요. 다음 편부터는 드디어 AI한테 그 일을 시켜볼 거예요. ChatGPT API를 파이썬으로 불러서 긴 글을 진짜로 요약하는 거죠. 여기서부터 AI·운영 파트가 시작돼요.
제 글이 도움이 되셨다면 댓글 & 공감 부탁드려요 😀
'Application > Python' 카테고리의 다른 글
| [Python] 22. 파이썬으로 AI 문서 요약과 번역 봇 만들기 - 긴 문서도 통째로 (0) | 2026.08.07 |
|---|---|
| [Python] 21. 파이썬으로 ChatGPT API 호출하기 - AI에게 텍스트 처리 자동으로 시키기 (0) | 2026.08.04 |
| [Python] 19. 파이썬 알림 봇 만들기 - 슬랙과 카카오톡으로 실시간 알림 받기 (1) | 2026.07.31 |
| [Python] 18. 네이버·구글 검색 결과, 파이썬으로 자동 수집하는 가장 쉬운 방법 (0) | 2026.07.30 |
| [Python] 17. requests로 안 되던 사이트, 파이썬 Selenium으로 긁어보기 (0) | 2026.07.27 |