
네이버 정치 뉴스 → TV 앵커 원고로 변환하는 자동화 툴 공개
“뉴스 기사를 보다 보면,
정작 핵심이 뭔지 헷갈릴 때가 많지 않으셨나요?”
특히 정치 뉴스는 하루에도 수십 건씩 쏟아지고,
내용도 복잡하고 길어 피로감을 주곤 합니다.
그래서 만들어 봤습니다.
🧠 ‘뉴스를 앵커가 읽는 스크립트처럼 바꿔주는 자동화 도구’
💻 GUI 기반 + GPT-4 활용 + 자동 요약 + 저장 기능까지 탑재
🧩 무엇을 할 수 있을까요?
이 프로그램은 매일 네이버 정치 뉴스에서 상위 10개의 뉴스를 자동으로 크롤링한 뒤,
댓글 수 기준으로 가장 주목받는 상위 5개를 골라 TV 앵커 스크립트 스타일로 변환해줍니다.
OpenAI GPT-4 API를 활용해,
- 복잡한 문장은 쉽게
- 긴 내용은 핵심만
- 딱 1분 안팎으로
- 중립적인 보도 톤으로
깔끔하게 정리된 스크립트를 생성합니다.
📺 화면 구성은 이렇게 생겼어요
- 진행 상태: 뉴스 수집 중, 완료 여부를 표시
- 진행 바: 시각적으로 수집 진행률 확인
- 미리보기 창: 완성된 스크립트를 바로 확인
- 실행/중지/재시도/저장 버튼: 작업 흐름을 직관적으로 컨트롤
모든 작업은 GUI로 구성돼 있어,
터미널이나 복잡한 명령 없이도 누구나 쉽게 사용 가능합니다.
💡 기술 스택 요약
- Tkinter: GUI 구성
- Requests + BeautifulSoup: 네이버 뉴스 크롤링
- OpenAI GPT-4 API: 뉴스 요약 및 앵커 스크립트 생성
- Threading: 비동기 크롤링 처리
- Datetime + OS: 결과 저장 폴더 및 파일명 자동 생성
📥 사용 방법
openai.api_key
에 본인의 GPT-4 API 키를 넣어주세요.- 실행하면 자동으로 뉴스가 수집되고, 가장 주목받는 5개 뉴스가 앵커 스크립트로 정리됩니다.
- 결과는 GUI에서 바로 확인하고, 버튼 하나로 저장할 수 있어요.
📝 저장 경로는
D:\영상\0. 정치시사\crawling
으로 설정되어 있으며,
자동으로 날짜 + 제목 조합으로 텍스트 파일로 저장됩니다.
🔐 전체 코드 공유
본 문 가장 아래 전체 코드를 확인하실 수 있습니다.
GPT API Key만 입력하면 바로 실행 가능!
💬 활용 예시
- 유튜브 정치 콘텐츠 제작자: 뉴스 원고 빠르게 확보
- AI 기반 콘텐츠 요약 블로그 운영자
- 뉴스 큐레이션 플랫폼 프로토타입 제작용
- 데이터 기반 자동 리포팅 테스트 환경
🧠 한 줄 요약
“복잡한 정치 뉴스를, 1분짜리 앵커 스크립트로 바꿔주는 자동화 도구”
정치 뉴스에 파묻혀 시간을 낭비하지 마세요.
중요한 소식만, 필요한 만큼만 전달받는 자동화 도구로 스마트한 하루를 시작해보세요.
📌 코드
import tkinter as tk
from tkinter import ttk, scrolledtext
import requests
from bs4 import BeautifulSoup
from datetime import datetime
import os
import time
import threading
from urllib.parse import urljoin
import openai
import json
class NewsCrawlerApp:
def __init__(self, root):
self.root = root
self.root.title("네이버 정치 뉴스 크롤러")
self.is_running = False
self.status_label = None
self.error_label = None
self.progress = None
self.start_button = None
self.stop_button = None
self.retry_button = None
self.save_button = None
self.preview = None
# OpenAI API 키 설정
openai.api_key = 'your-api-key-here'
self.setup_ui()
def setup_ui(self):
# 메인 프레임
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
# 상태 표시 레이블
self.status_label = ttk.Label(main_frame, text="대기 중...")
self.status_label.grid(row=0, column=0, columnspan=4, pady=5, sticky=tk.W)
# 에러 메시지 레이블
self.error_label = ttk.Label(main_frame, text="", foreground="red")
self.error_label.grid(row=1, column=0, columnspan=4, pady=5, sticky=tk.W)
# 프로그레스 바
self.progress = ttk.Progressbar(main_frame, length=300, mode='determinate')
self.progress.grid(row=2, column=0, columnspan=4, pady=5, sticky=(tk.W, tk.E))
# 버튼들
self.start_button = ttk.Button(main_frame, text="실행", command=self.start_crawling)
self.start_button.grid(row=3, column=0, padx=5, pady=5)
self.stop_button = ttk.Button(main_frame, text="중지", command=self.stop_crawling, state=tk.DISABLED)
self.stop_button.grid(row=3, column=1, padx=5, pady=5)
self.retry_button = ttk.Button(main_frame, text="재시도", command=self.retry_crawling, state=tk.DISABLED)
self.retry_button.grid(row=3, column=2, padx=5, pady=5)
self.save_button = ttk.Button(main_frame, text="저장", command=self.save_result, state=tk.DISABLED)
self.save_button.grid(row=3, column=3, padx=5, pady=5)
# 결과 미리보기
self.preview = scrolledtext.ScrolledText(main_frame, wrap=tk.WORD, width=60, height=20)
self.preview.grid(row=4, column=0, columnspan=4, pady=10, sticky=(tk.W, tk.E, tk.N, tk.S))
# 그리드 설정
main_frame.columnconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.columnconfigure(2, weight=1)
main_frame.columnconfigure(3, weight=1)
main_frame.rowconfigure(4, weight=1)
def start_crawling(self):
self.is_running = True
self.start_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.NORMAL)
self.save_button.config(state=tk.DISABLED)
self.retry_button.config(state=tk.DISABLED)
self.preview.delete(1.0, tk.END)
self.error_label.config(text="")
thread = threading.Thread(target=self.crawl_news)
thread.daemon = True
thread.start()
def stop_crawling(self):
self.is_running = False
self.status_label.config(text="작업이 중지되었습니다.")
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
self.retry_button.config(state=tk.NORMAL)
def retry_crawling(self):
self.start_crawling()
def process_news_content(self, title, content):
"""뉴스 내용을 앵커 스크립트로 변환"""
try:
prompt = f"""
다음 뉴스를 TV 앵커가 읽을 수 있는 스크립트로 변환해주세요.
조건:
1. 핵심 내용만 간추려서 1분 내외로 읽을 수 있게 만들어주세요
2. 중립적인 톤을 유지하세요
3. 인용구는 "~라고 전했습니다" 형식으로 변환하세요
4. 불필요한 세부 내용은 제외하세요
5. TV 뉴스 앵커 톤으로 자연스럽게 작성하세요
제목: {title}
내용: {content}
"""
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[
{"role": "system", "content": "당신은 전문 뉴스 편집자입니다. 뉴스 기사를 앵커가 읽을 수 있는 스크립트로 변환하는 작업을 합니다."},
{"role": "user", "content": prompt}
]
)
return response.choices[0].message.content.strip()
except Exception as e:
return f"[스크립트 변환 중 오류 발생: {str(e)}]"
def generate_script(self, news_items):
"""전체 스크립트 생성"""
script = "안녕하세요, 시청자 여러분.\n\n"
# 주요 뉴스 핵심 요약
script += "오늘 주요 정치 뉴스를 간단히 살펴보면,\n"
for i, news in enumerate(news_items, 1):
headline = news['title'].split('[')[0].strip()
script += f"{i}. {headline}\n"
script += "자세한 내용 전해드리겠습니다.\n\n"
# 각 뉴스 상세 내용
for i, news in enumerate(news_items, 1):
script += f"{'=' * 50}\n"
processed_content = self.process_news_content(news['title'], news['content'])
script += processed_content + "\n\n"
if i < len(news_items):
transition_phrases = [
"다음 소식입니다.",
"다른 소식을 전해드리겠습니다.",
"이어서 보시겠습니다.",
"다음 뉴스로 넘어가보겠습니다."
]
script += f"{transition_phrases[i % len(transition_phrases)]}\n\n"
# 마무리
script += "지금까지 주요 정치 뉴스였습니다.\n"
script += "시청자 여러분의 현명한 판단을 돕기 위해 최대한 객관적으로 전달하고자 했습니다.\n"
script += "더 자세한 내용은 각 언론사의 보도를 참고해 주시기 바랍니다.\n"
script += "시청해주셔서 감사합니다.\n"
return script
def extract_news_content(self, soup):
"""뉴스 본문에서 불필요한 내용 제거"""
content = ""
article = soup.select_one('#dic_area')
if article:
# 기자 정보, 사진 설명 등 제거
for tag in article.select('.reporter_area, .photo_desc, .source_area'):
tag.decompose()
# 본문 텍스트만 추출
content = article.get_text(strip=True)
# 불필요한 문구 제거
content = content.replace("▶ 네이버 메인에서 [연합뉴스] 구독하기", "")
content = content.replace("▶ 관련뉴스", "")
return content
def get_comment_count(self, soup):
try:
comment_count_elem = soup.select_one('span.u_cbox_count')
if comment_count_elem:
return int(comment_count_elem.text.replace(',', ''))
except:
pass
return 0
def crawl_news(self):
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
self.status_label.config(text="뉴스 수집 중...")
self.progress['value'] = 0
url = "https://news.naver.com/section/100"
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')
news_items = []
headlines = soup.select('a.sa_text_title')[:10]
for i, headline in enumerate(headlines):
if not self.is_running:
return
self.progress['value'] = (i + 1) * 10
news_url = urljoin(url, headline.get('href'))
news_response = requests.get(news_url, headers=headers)
news_soup = BeautifulSoup(news_response.text, 'html.parser')
title = headline.text.strip()
content = self.extract_news_content(news_soup)
comment_count = self.get_comment_count(news_soup)
if content: # 내용이 있는 경우만 추가
news_items.append({
'title': title,
'content': content,
'comment_count': comment_count
})
self.status_label.config(text=f"뉴스 수집 중... ({i + 1}/10)")
time.sleep(1)
# 댓글 많은 순으로 정렬
news_items.sort(key=lambda x: x['comment_count'], reverse=True)
top_5_news = news_items[:5]
# 스크립트 생성
script = self.generate_script(top_5_news)
self.preview.delete(1.0, tk.END)
self.preview.insert(tk.END, script)
self.status_label.config(text="스크립트 생성 완료")
self.save_button.config(state=tk.NORMAL)
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
self.progress['value'] = 100
except Exception as e:
self.error_label.config(text=f"오류 발생: {str(e)}")
self.retry_button.config(state=tk.NORMAL)
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
def save_result(self):
save_path = r"D:\영상\0. 정치시사\crawling"
if not os.path.exists(save_path):
os.makedirs(save_path)
now = datetime.now()
date_str = now.strftime("%Y%m%d_%H%M%S")
# 핵심 제목 생성
content = self.preview.get("1.0", tk.END).strip()
first_line = content.split('\n')[0]
title = first_line[:30] + "..." if len(first_line) > 30 else first_line
filename = os.path.join(save_path, f"{date_str}_{title}.txt")
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(self.preview.get("1.0", tk.END))
self.status_label.config(text=f"파일이 저장되었습니다: {filename}")
except Exception as e:
self.error_label.config(text=f"저장 중 오류 발생: {str(e)}")
if __name__ == "__main__":
root = tk.Tk()
app = NewsCrawlerApp(root)
root.mainloop()