Application/Python

[Python] 20. 파이썬 뉴스 봇 만들기 - 매일 아침 관심 뉴스 요약 메일 받기

devsalix 2026. 8. 3. 10:53
728x90

파이썬 뉴스 봇 만들기 - 매일 아침 관심 뉴스 요약 메일 받기

아침에 출근하면 제일 먼저 뭐 하세요? 저는 예전에 커피 한 잔 들고 자리에 앉으면 포털 검색창부터 열었거든요. "인공지능" 검색해서 밤새 나온 기사 훑고, 다시 "환율" 쳐서 훑고, 관심 있는 회사 이름 또 쳐서 훑고. 이걸 매일 반복하다 보니까 어느 날 문득 이런 생각이 들더라고요.

이거, 컴퓨터가 대신 해주면 안 되나?

그래서 이번엔 진짜 그걸 만들어볼 거예요. 내가 정해둔 키워드의 최신 뉴스를 알아서 긁어와서, 보기 좋게 정리한 다음, 내 메일함으로 딱 보내주는 나만의 뉴스 봇이요. 아침에 메일함만 열면 오늘 챙겨야 할 뉴스가 카드처럼 착착 정리돼 있는 거죠.

그런데 여기서 좀 놀라운 얘기를 하나 할게요. 이걸 만드는 데 필요한 재료가 사실 다 우리 손에 있어요. 새로 배울 게 거의 없거든요.


오늘은 새 부품을 사는 게 아니라, 있던 걸 조립하는 날

이 편은 지금까지 웹·메일 파트에서 배운 걸 하나로 합치는 미니 프로젝트예요. 레고 조립이라고 생각하시면 딱 맞아요. 그동안 상자별로 부품을 사뒀잖아요. 이번엔 설명서 보고 그걸 한 덩어리로 끼워 맞추는 거예요.

어떤 부품이 들어가는지 미리 보여드릴게요.

  • 뉴스 수집 — 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:"})

 

이러면 메일이랑 슬랙 알림을 동시에 받을 수 있어요.


직접 해보세요

읽기만 하면 안 남아요. 딱 두 개만 해보세요.

  1. KEYWORDS 리스트를 내 관심사로 바꿔서 실행해보기. 응원하는 팀, 좋아하는 회사, 요즘 관심 있는 주제 뭐든 좋아요. 이게 진짜 내 봇이 되는 순간이에요.
  2. 키워드별 뉴스 개수 count를 3에서 5로 늘려보기. 힌트는 collect_news(keyword, count=3)에서 이 3을 어디서 바꿀지 찾아보는 거예요.

여유가 되시면 위에 있는 슬랙 옵션 한 줄까지 붙여서 메일이랑 알림을 동시에 받아보세요. 막히는 부분 있으면 댓글로 남겨주시고요.


다음 편으로

여기까지 오셨으면 웹·메일 파트를 완주하신 거예요. 뉴스를 긁어오고, 예쁘게 꾸미고, 메일로 쏘고. 따로따로 배웠던 조각들이 하나의 시스템으로 딱 맞물리는 걸 직접 보셨잖아요. 이게 진짜 프로그래밍의 재미거든요.

그런데 아까 말했듯이 우리 봇은 아직 정리만 할 뿐이에요. 긴 기사를 보면 "이거 세 줄로 요약해줘" 하고 싶잖아요. 다음 편부터는 드디어 AI한테 그 일을 시켜볼 거예요. ChatGPT API를 파이썬으로 불러서 긴 글을 진짜로 요약하는 거죠. 여기서부터 AI·운영 파트가 시작돼요.


 


제 글이 도움이 되셨다면 댓글 & 공감 부탁드려요 😀

 

 
728x90