#!/usr/bin/env python3 """ 뉴스 AI 분석 모듈 (참고용 알림) - 네이버 금융 뉴스 크롤링 - Claude/GPT API로 요약 및 관련 업종 추출 - Mattermost로 참고용 알림만 전송 (실제 매매 판단 안 함!) """ import os import requests from bs4 import BeautifulSoup from datetime import datetime import logging logger = logging.getLogger("NewsAnalyzer") try: import anthropic ANTHROPIC_AVAILABLE = True except ImportError: ANTHROPIC_AVAILABLE = False logger.warning("⚠️ anthropic 미설치! pip install anthropic") class NewsAnalyzer: """뉴스 AI 분석기""" def __init__(self, api_key: str = None): self.api_key = api_key or os.environ.get("ANTHROPIC_API_KEY", "") if not self.api_key: logger.warning("⚠️ ANTHROPIC_API_KEY 없음 - 뉴스 분석 불가") self.client = None elif not ANTHROPIC_AVAILABLE: logger.error("❌ anthropic 라이브_러리 미설치!") self.client = None else: self.client = anthropic.Anthropic(api_key=self.api_key) logger.info("✅ Claude API 초기화 완료") def crawl_naver_finance_news(self, max_news: int = 5): """ 네이버 금융 주요 뉴스 크롤링 Returns: [{'title': '...', 'link': '...', 'date': '...'}, ...] """ try: url = "https://finance.naver.com/news/news_list.naver?mode=LSS2D§ion_id=101§ion_id2=258" headers = {'User-Agent': 'Mozilla/5.0'} response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser') news_items = soup.select('.newsList .articleSubject a') news_list = [] for item in news_items[:max_news]: title = item.get('title', '').strip() link = "https://finance.naver.com" + item.get('href', '') if title: news_list.append({ 'title': title, 'link': link, 'date': datetime.now().strftime('%Y-%m-%d %H:%M') }) logger.info(f"📰 네이버 금융 뉴스 {len(news_list)}건 크롤링 완료") return news_list except Exception as e: logger.error(f"❌ 뉴스 크롤링 실패: {e}") return [] def analyze_news_with_claude(self, news_list: list) -> dict: """ Claude API로 뉴스 분석 Args: news_list: [{'title': '...', 'link': '...', 'date': '...'}, ...] Returns: { 'summary': '오늘의 주요 이슈 요약', 'sectors': ['반도체', 'AI', '자동차'], 'sentiment': 'positive/neutral/negative', 'recommended_stocks': [{'code': '005930', 'name': '삼성전자', 'reason': '...'}] } """ if not self.client or not news_list: return None try: # 뉴스 제목들을 하나로 합치기 news_titles = "\n".join([f"- {item['title']}" for item in news_list]) prompt = f"""다음은 오늘의 주요 금융 뉴스 제목들입니다: {news_titles} 이 뉴스들을 분석하여 다음 정보를 JSON 형식으로 제공해주세요: 1. summary: 오늘의 주요 이슈를 2-3문장으로 요약 2. sectors: 관련 업종 리스트 (최대 3개, 예: ["반도체", "AI", "자동차"]) 3. sentiment: 전반적 시장 분위기 (positive/neutral/negative) 4. recommended_stocks: 관련 주요 종목 (최대 3개) - code: 종목코드 (6자리) - name: 종목명 - reason: 추천 이유 (한 줄) 반드시 유효한 JSON 형식으로만 응답하세요. 설명 없이 JSON만 출력하세요. """ message = self.client.messages.create( model="claude-sonnet-4-5", max_tokens=1024, messages=[{"role": "user", "content": prompt}] ) # JSON 파싱 import json result_text = message.content[0].text.strip() # JSON 코드 블록 제거 (```json ... ``` 형태) if result_text.startswith('```'): result_text = result_text.split('```')[1] if result_text.startswith('json'): result_text = result_text[4:] result_text = result_text.strip() result = json.loads(result_text) logger.info(f"✅ Claude 분석 완료") logger.info(f" 요약: {result.get('summary', '')[:50]}...") logger.info(f" 업종: {', '.join(result.get('sectors', []))}") logger.info(f" 분위기: {result.get('sentiment', 'unknown')}") return result except Exception as e: logger.error(f"❌ Claude 분석 실패: {e}") return None def format_analysis_for_mattermost(self, analysis: dict, news_list: list) -> str: """ Mattermost 알림 메시지 포맷 Returns: 마크다운 포맷 메시지 """ if not analysis: return None msg = "## 📰 AI 뉴스 분석 (참고용)\n\n" # 요약 msg += f"**📌 오늘의 이슈**\n{analysis.get('summary', '요약 없음')}\n\n" # 관련 업종 sectors = analysis.get('sectors', []) if sectors: msg += f"**🏢 관련 업종**\n" msg += ", ".join([f"`{s}`" for s in sectors]) + "\n\n" # 시장 분위기 sentiment = analysis.get('sentiment', 'neutral') sentiment_emoji = { 'positive': '😊 긍정적', 'neutral': '😐 중립', 'negative': '😰 부정적' } msg += f"**💭 시장 분위기**: {sentiment_emoji.get(sentiment, '알 수 없음')}\n\n" # 추천 종목 stocks = analysis.get('recommended_stocks', []) if stocks: msg += f"**📊 관련 종목**\n" for stock in stocks: msg += f"- `{stock.get('code', '')}` {stock.get('name', '')}: {stock.get('reason', '')}\n" msg += "\n" # 뉴스 링크 if news_list: msg += f"**🔗 주요 뉴스**\n" for news in news_list[:3]: msg += f"- [{news['title']}]({news['link']})\n" msg += "\n" # 경고 문구 msg += "---\n" msg += "⚠️ **주의**: 이 분석은 참고용입니다. 최종 매수 판단은 ML 모델 + 기술적 지표로 이루어집니다.\n" return msg # 사용 예시 if __name__ == "__main__": analyzer = NewsAnalyzer() # 뉴스 크롤링 news = analyzer.crawl_naver_finance_news(max_news=5) if news: print("\n📰 크롤링된 뉴스:") for item in news: print(f" - {item['title']}") # Claude 분석 if analyzer.client: analysis = analyzer.analyze_news_with_claude(news) if analysis: # Mattermost 메시지 생성 message = analyzer.format_analysis_for_mattermost(analysis, news) print("\n" + message)