안녕하세요, 저는 3년째 AI API 통합 업무를 맡고 있는 개발자입니다. 오늘은 초보자도 쉽게 따라할 수 있는 AI 뉴스 요약 시스템을 만들어보겠습니다. 이 튜토리얼을 마치면 RSS 피드, 웹 스크래핑, SNS 포스트 등 여러 출처의 뉴스를 자동으로 수집하여 핵심 내용만 요약해주는 시스템을 구축할 수 있습니다.

이 시스템이 하는 일

HolySheep AI를 사용하면 단일 API 키로 여러 AI 모델을 조합할 수 있어 비용을 크게 절감할 수 있습니다. 예를 들어, 초안 요약은 저렴한 DeepSeek V3.2(마일리지당 $0.42)에 처리하고, 최종 검수는 Claude Sonnet 4.5($15/MTok)로 수행하는 하이브리드 전략도 가능합니다.

사전 준비물

1단계: 프로젝트 환경 설정

먼저 프로젝트 폴더를 만들고 필요한 라이브러리를 설치합니다. 터미널에서 다음 명령어를 실행하세요.

# 프로젝트 폴더 생성 및 이동
mkdir news-summary-system
cd news-summary-system

가상환경 생성 (권장)

python -m venv venv

Windows에서는:

venv\Scripts\activate

macOS/Linux에서는:

source venv/bin/activate

필수 라이브러리 설치

pip install requests feedparser beautifulsoup4 python-dateutil

설치 확인

pip list | grep -E "requests|feedparser|beautifulsoup4"

위 명령어 실행 시 다음과 같은 결과가 나오면 성공입니다.

beautifulsoup4 4.12.x
feedparser 6.x.x
requests 2.31.x
python-dateutil 2.8.x

2단계: HolySheep AI 클라이언트 설정

이제 HolySheep AI API에 연결하는 기본 클라이언트를 만들어보겠습니다. HolySheep AI는 다양한 AI 모델을 단일 엔드포인트에서 제공하므로, 코드 변경 없이 모델을 교체할 수 있습니다.

# holysheep_client.py
import requests
import json
from typing import Optional, List, Dict

class HolySheepAIClient:
    """HolySheep AI 게이트웨이 클라이언트 - 다중 AI 모델 통합 지원"""
    
    def __init__(self, api_key: str, base_url: str = "https://api.holysheep.ai/v1"):
        self.api_key = api_key
        self.base_url = base_url.rstrip('/')
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
    
    def summarize(self, text: str, model: str = "deepseek/deepseek-chat-v3-0324") -> str:
        """
        텍스트를 AI 모델로 요약합니다.
        
        Args:
            text: 요약할 원본 텍스트
            model: 사용할 모델 (기본값: DeepSeek V3.2)
                   사용 가능한 모델:
                   - deepseek/deepseek-chat-v3-0324 (최저가: $0.42/MTok)
                   - anthropic/claude-3-5-sonnet-latest (고품질: $15/MTok)
                   - openai/gpt-4o-mini (가성비: $3.5/MTok)
        
        Returns:
            요약된 텍스트
        """
        endpoint = f"{self.base_url}/chat/completions"
        
        prompt = f"""다음 뉴스 기사의 핵심 내용을 3문장으로 요약해주세요.
중요한 사실, 인물, 장소, 수치를 포함하고 간결하게 작성하세요.

---
{text}
---

핵심 요약:"""
        
        payload = {
            "model": model,
            "messages": [
                {"role": "system", "content": "당신은 정확한 뉴스 요약 전문가입니다."},
                {"role": "user", "content": prompt}
            ],
            "temperature": 0.3,  # 일관된 결과를 위해 낮은 온도 사용
            "max_tokens": 500
        }
        
        try:
            response = requests.post(endpoint, headers=self.headers, json=payload, timeout=30)
            response.raise_for_status()
            
            result = response.json()
            return result['choices'][0]['message']['content'].strip()
        
        except requests.exceptions.Timeout:
            raise Exception("API 요청 시간 초과 (30초). 네트워크 연결을 확인하세요.")
        except requests.exceptions.RequestException as e:
            raise Exception(f"API 요청 실패: {str(e)}")
        except KeyError:
            raise Exception("응답 형식이 올바르지 않습니다. API 키를 확인하세요.")
    
    def extract_keywords(self, text: str) -> List[str]:
        """
        텍스트에서 핵심 키워드를 추출합니다.
        """
        endpoint = f"{self.base_url}/chat/completions"
        
        prompt = f"""다음 텍스트에서 가장 중요한 키워드 5개를 추출해주세요.
키워드는 쉼표로 구분하여 나열하세요.

---
{text}
---

핵심 키워드:"""
        
        payload = {
            "model": "deepseek/deepseek-chat-v3-0324",
            "messages": [
                {"role": "user", "content": prompt}
            ],
            "temperature": 0.1,
            "max_tokens": 50
        }
        
        response = requests.post(endpoint, headers=self.headers, json=payload, timeout=30)
        response.raise_for_status()
        
        result = response.json()
        keywords_text = result['choices'][0]['message']['content'].strip()
        
        return [k.strip() for k in keywords_text.split(',')]


사용 예시

if __name__ == "__main__": # API 키 설정 (실제 키로 교체하세요) client = HolySheepAIClient(api_key="YOUR_HOLYSHEEP_API_KEY") # 테스트용 뉴스 텍스트 sample_news = """ 서울특별시는 2024년 12월 15일 새로운 환경 정책 발표회를 가졌습니다. 이번 정책은 2030년까지 탄소 배출량을 40% 절감하는 것을 목표로 하고 있습니다. 시장은 전기버스 비중을 50%로 확대하고, 태양광 발전 시설을 확대할 계획이라고 밝혔습니다. 총 사업비는 약 5조 원이 투입될 예정이며, 민간 투자도 유도할 계획입니다. """ print("=" * 50) print("AI 뉴스 요약 시스템 테스트") print("=" * 50) try: summary = client.summarize(sample_news) print(f"\n📰 요약 결과:\n{summary}") keywords = client.extract_keywords(sample_news) print(f"\n🔑 핵심 키워드: {', '.join(keywords)}") except Exception as e: print(f"\n❌ 오류 발생: {e}")

3단계: 뉴스 수집 모듈 구현

이제 다양한 출처에서 뉴스를 수집하는 모듈을 만들겠습니다. RSS 피드, 일반 웹페이지, 그리고 JSON API를 지원합니다.

# news_collector.py
import requests
import feedparser
from bs4 import BeautifulSoup
from datetime import datetime, timedelta
from typing import List, Dict, Optional
from dateutil import parser as date_parser
import hashlib
import re

class NewsCollector:
    """다중 소스 뉴스 수집기"""
    
    def __init__(self):
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        })
        self.seen_hashes = set()  # 중복 기사 필터링용
    
    def collect_rss(self, feed_url: str, max_items: int = 10) -> List[Dict]:
        """
        RSS 피드에서 뉴스를 수집합니다.
        
        Args:
            feed_url: RSS 피드 URL
            max_items: 최대 수집 기사 수
        
        Returns:
            [{'title', 'url', 'content', 'published', 'source'}]
        """
        articles = []
        
        try:
            feed = feedparser.parse(feed_url)
            
            for entry in feed.entries[:max_items]:
                # 중복 체크
                content_hash = hashlib.md5(entry.link.encode()).hexdigest()
                if content_hash in self.seen_hashes:
                    continue
                
                #发布日期处理
                published = None
                if hasattr(entry, 'published_parsed') and entry.published_parsed:
                    try:
                        published = datetime(*entry.published_parsed[:6])
                    except:
                        pass
                
                # 본문 추출
                content = ""
                if hasattr(entry, 'summary'):
                    content = self._clean_html(entry.summary)
                elif hasattr(entry, 'description'):
                    content = self._clean_html(entry.description)
                
                articles.append({
                    'title': entry.get('title', '제목 없음'),
                    'url': entry.get('link', ''),
                    'content': content[:1000],  # 1000자로 제한
                    'published': published,
                    'source': feed.feed.get('title', feed_url)
                })
                
                self.seen_hashes.add(content_hash)
        
        except Exception as e:
            print(f"RSS 수집 오류 ({feed_url}): {e}")
        
        return articles
    
    def collect_webpage(self, url: str) -> Optional[Dict]:
        """
        일반 웹페이지에서 기사를 추출합니다.
        """
        try:
            response = self.session.get(url, timeout=10)
            response.raise_for_status()
            
            soup = BeautifulSoup(response.content, 'html.parser')
            
            # 제목 추출
            title = ""
            if soup.find('h1'):
                title = soup.find('h1').get_text(strip=True)
            elif soup.find('title'):
                title = soup.find('title').get_text(strip=True)
            
            # 본문 추출 (article 태그 우선)
            article = soup.find('article')
            if not article:
                article = soup.find('div', class_=re.compile(r'article|content|post'))
            
            content = ""
            if article:
                paragraphs = article.find_all('p')
                content = ' '.join([p.get_text(strip=True) for p in paragraphs[:10]])
            
            return {
                'title': title,
                'url': url,
                'content': content[:1000],
                'published': datetime.now(),
                'source': url
            }
        
        except Exception as e:
            print(f"웹페이지 수집 오류 ({url}): {e}")
            return None
    
    def collect_api(self, api_url: str, headers: Dict = None) -> List[Dict]:
        """
        JSON API에서 뉴스를 수집합니다.
        """
        articles = []
        
        try:
            response = self.session.get(api_url, headers=headers, timeout=10)
            response.raise_for_status()
            
            data = response.json()
            
            # 일반적인 구조 처리
            items = data if isinstance(data, list) else data.get('articles', data.get('items', []))
            
            for item in items[:10]:
                if isinstance(item, dict):
                    articles.append({
                        'title': item.get('title', ''),
                        'url': item.get('url', item.get('link', '')),
                        'content': item.get('description', item.get('content', ''))[:1000],
                        'published': self._parse_date(item.get('publishedAt', item.get('pubDate', ''))),
                        'source': item.get('source', {}).get('name', api_url) if isinstance(item.get('source'), dict) else api_url
                    })
        
        except Exception as e:
            print(f"API 수집 오류 ({api_url}): {e}")
        
        return articles
    
    def _clean_html(self, html_text: str) -> str:
        """HTML 태그 제거 및 정리"""
        if not html_text:
            return ""
        soup = BeautifulSoup(html_text, 'html.parser')
        return soup.get_text(strip=True)
    
    def _parse_date(self, date_str: str) -> Optional[datetime]:
        """다양한 날짜 형식 파싱"""
        if not date_str:
            return None
        try:
            return date_parser.parse(date_str)
        except:
            return None


사용 예시

if __name__ == "__main__": collector = NewsCollector() print("=" * 50) print("뉴스 수집기 테스트") print("=" * 50) # 테스트용 RSS 피드 test_feeds = [ ("BBC 뉴스", "https://feeds.bbci.co.uk/news/world/rss.xml"), ("한국 뉴스", "https://www.yna.co.kr/rss/news.xml") ] for name, url in test_feeds: print(f"\n📡 {name} 수집 중...") articles = collector.collect_rss(url, max_items=3) print(f" → {len(articles)}개 기사 수집됨") for i, article in enumerate(articles[:2], 1): print(f"\n [{i}] {article['title'][:50]}...") print(f" {article['url'][:60]}...")

4단계: 뉴스 요약 시스템 통합

이제 수집기와 AI 클라이언트를 결합하여 완전한 뉴스 요약 시스템을 만들겠습니다.

# news_summary_system.py
import time
import json
from datetime import datetime
from holysheep_client import HolySheepAIClient
from news_collector import NewsCollector

class NewsSummarySystem:
    """AI 기반 뉴스 요약 시스템 - 완전한 통합 버전"""
    
    def __init__(self, api_key: str):
        self.ai_client = HolySheepAIClient(api_key)
        self.collector = NewsCollector()
        self.news_db = []  # 처리된 뉴스 저장
    
    def add_feed(self, name: str, url: str, feed_type: str = "rss"):
        """수집 소스 추가"""
        self.feeds = getattr(self, 'feeds', [])
        self.feeds.append({
            'name': name,
            'url': url,
            'type': feed_type
        })
    
    def run(self, summary_model: str = "deepseek/deepseek-chat-v3-0324"):
        """전체 시스템 실행"""
        print("=" * 60)
        print(f"🤖 AI 뉴스 요약 시스템 시작 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
        print("=" * 60)
        
        all_articles = []
        
        # 1단계: 뉴스 수집
        print("\n📡 1단계: 뉴스 수집 중...")
        for feed in getattr(self, 'feeds', []):
            print(f"   • {feed['name']} 처리 중...")
            
            if feed['type'] == 'rss':
                articles = self.collector.collect_rss(feed['url'], max_items=5)
            elif feed['type'] == 'web':
                article = self.collector.collect_webpage(feed['url'])
                articles = [article] if article else []
            elif feed['type'] == 'api':
                articles = self.collector.collect_api(feed['url'])
            else:
                articles = []
            
            all_articles.extend(articles)
            print(f"     → {len(articles)}개 수집")
            time.sleep(0.5)  # 서버 부하 방지
        
        print(f"\n   총 {len(all_articles)}개 기사 수집 완료")
        
        # 2단계: AI 요약 처리
        print("\n🤖 2단계: AI 요약 처리 중...")
        processed_count = 0
        
        for i, article in enumerate(all_articles, 1):
            if not article.get('content'):
                continue
            
            try:
                # 요약 생성
                summary = self.ai_client.summarize(
                    article['content'], 
                    model=summary_model
                )
                
                # 키워드 추출
                keywords = self.ai_client.extract_keywords(article['content'])
                
                # 처리 결과 저장
                processed_article = {
                    **article,
                    'summary': summary,
                    'keywords': keywords,
                    'processed_at': datetime.now().isoformat()
                }
                
                self.news_db.append(processed_article)
                processed_count += 1
                
                print(f"\n   [{processed_count}/{len(all_articles)}] {article['title'][:40]}...")
                print(f"       📝 요약: {summary[:100]}...")
                print(f"       🔑 키워드: {', '.join(keywords)}")
                
                # API 호출 간 딜레이 (_RATE_LIMIT 방지)
                time.sleep(1.0)
                
            except Exception as e:
                print(f"\n   ⚠️ 처리 실패: {article.get('title', 'Unknown')[:30]}...")
                print(f"      오류: {e}")
                continue
        
        # 3단계: 결과 출력
        print("\n" + "=" * 60)
        print(f"✅ 처리 완료: {processed_count}개 기사 요약")
        print("=" * 60)
        
        return self.news_db
    
    def save_results(self, filename: str = "news_summary_results.json"):
        """결과를 JSON 파일로 저장"""
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump(self.news_db, f, ensure_ascii=False, indent=2)
        print(f"\n💾 결과를 {filename}에 저장했습니다.")
    
    def get_top_news(self, count: int = 5):
        """상위 뉴스 반환"""
        return self.news_db[:count]


메인 실행 코드

if __name__ == "__main__": # HolySheep AI API 키 설정 API_KEY = "YOUR_HOLYSHEEP_API_KEY" # 시스템 초기화 system = NewsSummarySystem(API_KEY) # 수집 소스 설정 system.add_feed("BBC 세계뉴스", "https://feeds.bbci.co.uk/news/world/rss.xml", "rss") system.add_feed("한국연합뉴스", "https://www.yna.co.kr/rss/news.xml", "rss") system.add_feed("TechCrunch", "https://techcrunch.com/feed/", "rss") # 시스템 실행 # DeepSeek V3.2 사용 ($0.42/MTok - 저렴한 비용) results = system.run(summary_model="deepseek/deepseek-chat-v3-0324") # 결과 저장 system.save_results() # 상위 뉴스 출력 print("\n" + "=" * 60) print("🏆 주요 뉴스 TOP 5") print("=" * 60) for i, news in enumerate(system.get_top_news(5), 1): print(f"\n{i}. {news['title']}") print(f" 출처: {news['source']}") print(f" 요약: {news.get('summary', 'N/A')[:150]}...")

5단계: 실시간 자동 업데이트 설정

시스템을 정기적으로 실행하도록 스케줄러를 설정해보겠습니다.

# scheduler.py
import time
import schedule
from datetime import datetime
from news_summary_system import NewsSummarySystem

def job_news_update():
    """정기 뉴스 업데이트 작업"""
    print(f"\n{'='*60}")
    print(f"🕐 자동 업데이트 실행: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"{'='*60}")
    
    system = NewsSummarySystem(API_KEY)
    
    # 수집 소스 설정
    system.add_feed("BBC 세계뉴스", "https://feeds.bbci.co.uk/news/world/rss.xml", "rss")
    system.add_feed("한국연합 뉴스", "https://www.yna.co.kr/rss/news.xml", "rss")
    system.add_feed("TechCrunch", "https://techcrunch.com/feed/", "rss")
    system.add_feed("NASA 뉴스", "https://www.nasa.gov/news-release/feed/", "rss")
    
    # 실행 (DeepSeek V3.2 사용 - 비용 최적화)
    results = system.run(summary_model="deepseek/deepseek-chat-v3-0324")
    
    # 결과 저장
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S