# -*- coding: utf-8 -*-
"""
AI 뉴스 & 허깅페이스 트렌딩 분석 웹 앱 (Flask 버전) - 완전판
파일명: app.py
실행 방법:
1. pip install Flask requests beautifulsoup4 lxml gunicorn
2. python app.py
3. 브라우저에서 http://localhost:8080 접속
프로덕션 실행:
gunicorn -w 4 -b 0.0.0.0:8080 app:app
"""
from flask import Flask, render_template_string, jsonify, request
import requests
from bs4 import BeautifulSoup
import json
from datetime import datetime
from typing import List, Dict, Optional
import os
import sys
# Flask 앱 초기화
app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False # 한글 JSON 지원
# ============================================
# HTML 템플릿 (완전판)
# ============================================
HTML_TEMPLATE = """
AI 뉴스 & 허깅페이스 트렌딩 분석 시스템
🤖 AI 뉴스 & 허깅페이스 트렌딩
실시간 AI 산업 동향 분석 시스템 📊
{{ stats.total_news }}
📰 총 뉴스
{{ stats.categories }}
📁 카테고리
{{ stats.hf_models }}
🤗 HF 모델
{{ stats.hf_spaces }}
🚀 HF 스페이스
{% for category, articles in news_by_category.items() %}
📌 {{ category }}
{{ articles|length }}건
{% for article in articles %}
{{ loop.index }}. {{ article.title }}
📅 {{ article.date }}
📰 {{ article.source }}
🔗 기사 전문 보기
{% endfor %}
{% endfor %}
🤗 허깅페이스 트렌딩 모델 TOP 10
{% if hf_models|length > 0 %}
{% for model in hf_models[:10] %}
{{ loop.index }}. {{ model.name }}
🏷️ {{ model.task }}
📊 다운로드: {{ "{:,}".format(model.downloads) }}
❤️ 좋아요: {{ "{:,}".format(model.likes) }}
🔗 모델 페이지 방문
{% endfor %}
{% else %}
⚠️ 모델 데이터를 불러오지 못했습니다.
{% endif %}
⏰ 마지막 업데이트: {{ timestamp }}
"""
# ============================================
# AINewsAnalyzer 클래스
# ============================================
class AINewsAnalyzer:
"""AI 뉴스 및 허깅페이스 트렌딩 분석기"""
def __init__(self, fireworks_api_key: Optional[str] = None, brave_api_key: Optional[str] = None):
"""
Args:
fireworks_api_key: Fireworks AI API 키 (선택)
brave_api_key: Brave Search API 키 (선택)
"""
self.fireworks_api_key = fireworks_api_key or os.getenv('FIREWORKS_API_KEY')
self.brave_api_key = brave_api_key or os.getenv('BRAVE_API_KEY')
# 뉴스 카테고리 정의
self.categories = {
"산업동향": ["산업", "기업", "투자", "인수", "파트너십", "시장", "MS", "구글", "아마존", "소프트뱅크"],
"기술혁신": ["기술", "모델", "알고리즘", "개발", "연구", "논문", "삼성", "SAIT"],
"제품출시": ["출시", "공개", "발표", "서비스", "제품", "챗GPT", "소라", "팬서"],
"정책규제": ["규제", "정책", "법", "정부", "제재", "EU", "투자"],
"보안이슈": ["보안", "취약점", "해킹", "위험", "프라이버시"],
}
self.huggingface_data = {
"models": [],
"spaces": []
}
self.news_data = []
def fetch_huggingface_trending(self) -> Dict:
"""허깅페이스 트렌딩 모델 수집"""
print("🤗 허깅페이스 트렌딩 정보 수집 중...")
try:
models_url = "https://huggingface.co/api/models"
params = {
'sort': 'trending',
'limit': 30
}
response = requests.get(models_url, params=params, timeout=15)
if response.status_code == 200:
models = response.json()
for model in models[:30]:
self.huggingface_data['models'].append({
'name': model.get('id', 'Unknown'),
'downloads': model.get('downloads', 0),
'likes': model.get('likes', 0),
'task': model.get('pipeline_tag', 'N/A'),
'url': f"https://huggingface.co/{model.get('id', '')}"
})
print(f"✅ {len(self.huggingface_data['models'])}개 트렌딩 모델 수집 완료")
else:
print(f"⚠️ 모델 API 오류: {response.status_code}")
except Exception as e:
print(f"❌ 모델 수집 오류: {e}")
# 샘플 스페이스 데이터
sample_spaces = [
{"name": "Wan2.2-5B", "title": "고품질 비디오 생성", "url": "https://huggingface.co/spaces/"},
{"name": "FLUX-Image", "title": "텍스트→이미지 생성", "url": "https://huggingface.co/spaces/"},
{"name": "DeepSeek-App", "title": "AI 앱 생성기", "url": "https://huggingface.co/spaces/"},
]
self.huggingface_data['spaces'] = sample_spaces
return self.huggingface_data
def create_sample_news(self) -> List[Dict]:
"""오늘의 AI 뉴스 샘플 데이터 (2025-10-10 기준)"""
sample_news = [
{
'title': 'MS "챗GPT 수요 폭증으로 데이터센터 부족...2026년까지 지속"',
'url': 'https://www.aitimes.com/news/articleView.html?idxno=203055',
'date': '10-10 15:10',
'source': 'AI Times',
'category': '산업동향'
},
{
'title': '미국, UAE에 GPU 판매 일부 승인...엔비디아 시총 5조달러 눈앞',
'url': 'https://www.aitimes.com/news/articleView.html?idxno=203053',
'date': '10-10 14:46',
'source': 'AI Times',
'category': '산업동향'
},
{
'title': '오픈AI, 저렴한 챗GPT 고 요금제 아시아 16개국으로 확대',
'url': 'https://www.aitimes.com/news/articleView.html?idxno=203054',
'date': '10-10 14:15',
'source': 'AI Times',
'category': '제품출시'
},
{
'title': '인텔, 18A 공정으로 자체 제작한 노트북용 칩 팬서 레이크 공개',
'url': 'https://www.aitimes.com/news/articleView.html?idxno=203057',
'date': '10-10 14:03',
'source': 'AI Times',
'category': '제품출시'
},
{
'title': '소라, 챗GPT보다 빨리 100만 다운로드 돌파',
'url': 'https://www.aitimes.com/news/articleView.html?idxno=203045',
'date': '10-10 12:55',
'source': 'AI Times',
'category': '제품출시'
},
{
'title': '구글·아마존, 기업용 AI 서비스 나란히 출시',
'url': 'https://www.aitimes.com/news/articleView.html?idxno=203047',
'date': '10-10 12:41',
'source': 'AI Times',
'category': '제품출시'
},
{
'title': '삼성 SAIT, 거대 모델 능가하는 초소형 추론 모델 TRM 공개',
'url': 'https://www.aitimes.com/news/articleView.html?idxno=203035',
'date': '10-09 21:22',
'source': 'AI Times',
'category': '기술혁신'
},
{
'title': '구글, GUI 에이전트 제미나이 2.5 컴퓨터 유즈 공개',
'url': 'https://www.aitimes.com/news/articleView.html?idxno=203039',
'date': '10-09 20:57',
'source': 'AI Times',
'category': '기술혁신'
},
{
'title': 'EU, 핵심 산업 AX 위한 1.6조 규모 투자 계획 발표',
'url': 'https://www.aitimes.com/news/articleView.html?idxno=203041',
'date': '10-09 18:51',
'source': 'AI Times',
'category': '정책규제'
},
{
'title': '소프트뱅크, ABB 로봇 사업부 7.6조원에 인수',
'url': 'https://www.aitimes.com/news/articleView.html?idxno=203034',
'date': '10-09 18:07',
'source': 'AI Times',
'category': '산업동향'
}
]
self.news_data = sample_news
return sample_news
def categorize_news(self, news_list: List[Dict]) -> List[Dict]:
"""뉴스 카테고리 자동 분류"""
for news in news_list:
if 'category' not in news or news['category'] == '기타':
title = news['title'].lower()
news['category'] = "기타"
for category, keywords in self.categories.items():
if any(keyword.lower() in title for keyword in keywords):
news['category'] = category
break
return news_list
def get_data(self) -> Dict:
"""모든 데이터 수집 및 반환"""
# 뉴스 수집
news = self.create_sample_news()
news = self.categorize_news(news)
# 허깅페이스 데이터 수집
hf_data = self.fetch_huggingface_trending()
# 카테고리별로 뉴스 그룹화
news_by_category = {}
for article in news:
category = article['category']
if category not in news_by_category:
news_by_category[category] = []
news_by_category[category].append(article)
# 통계 계산
stats = {
'total_news': len(news),
'categories': len(news_by_category),
'hf_models': len(hf_data['models']),
'hf_spaces': len(hf_data['spaces'])
}
return {
'news_by_category': news_by_category,
'hf_models': hf_data['models'],
'hf_spaces': hf_data['spaces'],
'stats': stats,
'timestamp': datetime.now().strftime('%Y년 %m월 %d일 %H:%M:%S')
}
# ============================================
# Flask 라우트 정의
# ============================================
@app.route('/')
def index():
"""메인 페이지"""
try:
analyzer = AINewsAnalyzer()
data = analyzer.get_data()
return render_template_string(HTML_TEMPLATE, **data)
except Exception as e:
return f"""
⚠️ 오류 발생
데이터를 불러오는 중 오류가 발생했습니다.
{str(e)}
""", 500
@app.route('/api/data')
def api_data():
"""JSON API 엔드포인트"""
try:
analyzer = AINewsAnalyzer()
data = analyzer.get_data()
return jsonify({
'success': True,
'data': data,
'timestamp': datetime.now().isoformat()
})
except Exception as e:
return jsonify({
'success': False,
'error': str(e),
'timestamp': datetime.now().isoformat()
}), 500
@app.route('/health')
def health():
"""헬스 체크 엔드포인트"""
return jsonify({
"status": "healthy",
"service": "AI News Analyzer",
"version": "1.0.0",
"timestamp": datetime.now().isoformat()
})
@app.route('/api/news')
def api_news():
"""뉴스만 반환하는 API"""
try:
analyzer = AINewsAnalyzer()
news = analyzer.create_sample_news()
return jsonify({
'success': True,
'count': len(news),
'news': news
})
except Exception as e:
return jsonify({
'success': False,
'error': str(e)
}), 500
@app.route('/api/hf-models')
def api_hf_models():
"""허깅페이스 모델만 반환하는 API"""
try:
analyzer = AINewsAnalyzer()
hf_data = analyzer.fetch_huggingface_trending()
return jsonify({
'success': True,
'count': len(hf_data['models']),
'models': hf_data['models']
})
except Exception as e:
return jsonify({
'success': False,
'error': str(e)
}), 500
# ============================================
# 메인 실행
# ============================================
if __name__ == '__main__':
# 환경 변수에서 포트 가져오기 (기본값: 8080)
port = int(os.environ.get('PORT', 8080))
# 환경 변수에서 디버그 모드 설정 (기본값: False)
debug = os.environ.get('DEBUG', 'False').lower() == 'true'
print(f"""
╔════════════════════════════════════════════════════════════╗
║ ║
║ 🤖 AI 뉴스 & 허깅페이스 트렌딩 웹 앱 시작! ║
║ ║
╚════════════════════════════════════════════════════════════╝
🚀 Flask 서버 시작 중...
📍 메인 페이지: http://localhost:{port}
📊 JSON API: http://localhost:{port}/api/data
📰 뉴스 API: http://localhost:{port}/api/news
🤗 모델 API: http://localhost:{port}/api/hf-models
💚 Health Check: http://localhost:{port}/health
{'🐛 디버그 모드: 활성화' if debug else '⚡ 프로덕션 모드: 최적화됨'}
브라우저에서 위 URL을 열어주세요!
종료하려면 Ctrl+C를 누르세요.
""")
try:
app.run(
host='0.0.0.0',
port=port,
debug=debug,
threaded=True
)
except KeyboardInterrupt:
print("\n\n👋 서버를 종료합니다. 안녕히 가세요!")
sys.exit(0)
except Exception as e:
print(f"\n❌ 서버 시작 실패: {e}")
sys.exit(1)