ginipick commited on
Commit
6085299
·
verified ·
1 Parent(s): 04168dc

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +444 -0
app.py ADDED
@@ -0,0 +1,444 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ AI 뉴스 & 허깅페이스 트렌딩 분석 시스템
4
+ - AI Times 뉴스 크롤링 및 카테고리 분류
5
+ - 허깅페이스 모델/스페이스 트렌딩 정보 수집
6
+ - Fireworks AI (Qwen) 를 통한 뉴스 분석
7
+ - Brave Search를 통한 팩트 체크
8
+ """
9
+
10
+ import requests
11
+ from bs4 import BeautifulSoup
12
+ import json
13
+ from datetime import datetime
14
+ from typing import List, Dict, Optional
15
+ import time
16
+ import re
17
+
18
+
19
+ class AINewsAnalyzer:
20
+ def __init__(self, fireworks_api_key: str, brave_api_key: str):
21
+ """
22
+ Args:
23
+ fireworks_api_key: Fireworks AI API 키
24
+ brave_api_key: Brave Search API 키
25
+ """
26
+ self.fireworks_api_key = fireworks_api_key
27
+ self.brave_api_key = brave_api_key
28
+
29
+ # 뉴스 카테고리 정의
30
+ self.categories = {
31
+ "산업동향": ["산업", "기업", "투자", "인수", "파트너십", "시장"],
32
+ "기술혁신": ["기술", "모델", "알고리즘", "개발", "연구", "논문"],
33
+ "제품출시": ["출시", "공개", "발표", "서비스", "제품"],
34
+ "정책규제": ["규제", "정책", "법", "정부", "제재"],
35
+ "보안이슈": ["보안", "취약점", "해킹", "위험", "프라이버시"],
36
+ }
37
+
38
+ self.huggingface_data = {
39
+ "models": [],
40
+ "spaces": []
41
+ }
42
+
43
+ self.news_data = []
44
+
45
+ def fetch_aitimes_news(self, urls: List[str]) -> List[Dict]:
46
+ """AI Times 뉴스 크롤링"""
47
+ all_news = []
48
+
49
+ for url in urls:
50
+ try:
51
+ print(f"📰 뉴스 크롤링 중: {url}")
52
+ response = requests.get(url, headers={
53
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
54
+ })
55
+ soup = BeautifulSoup(response.content, 'html.parser')
56
+
57
+ # 뉴스 기사 추출 (실제 구조에 맞게 조정 필요)
58
+ articles = []
59
+
60
+ # 제목과 링크가 있는 a 태그 찾기
61
+ for link in soup.find_all('a', href=True):
62
+ if '/news/articleView.html' in link['href']:
63
+ title = link.get_text(strip=True)
64
+ article_url = link['href']
65
+
66
+ if not article_url.startswith('http'):
67
+ article_url = 'https://www.aitimes.com' + article_url
68
+
69
+ # 날짜 추출 (형제 요소에서)
70
+ date_text = ""
71
+ parent = link.parent
72
+ if parent:
73
+ date_elem = parent.find(text=re.compile(r'\d{2}-\d{2}'))
74
+ if date_elem:
75
+ date_text = date_elem.strip()
76
+
77
+ if title and len(title) > 10:
78
+ articles.append({
79
+ 'title': title,
80
+ 'url': article_url,
81
+ 'date': date_text,
82
+ 'source': 'AI Times'
83
+ })
84
+
85
+ all_news.extend(articles[:10]) # 상위 10개만
86
+ time.sleep(1) # 크롤링 예의
87
+
88
+ except Exception as e:
89
+ print(f"❌ 크롤링 오류: {e}")
90
+
91
+ return all_news
92
+
93
+ def fetch_huggingface_trending(self) -> Dict:
94
+ """허깅페이스 트렌딩 모델 및 스페이스 수집"""
95
+ print("🤗 허깅페이스 트렌딩 정보 수집 중...")
96
+
97
+ # 모델 트렌딩
98
+ try:
99
+ models_url = "https://huggingface.co/api/models"
100
+ params = {
101
+ 'sort': 'trending',
102
+ 'limit': 30
103
+ }
104
+
105
+ response = requests.get(models_url, params=params, timeout=10)
106
+ if response.status_code == 200:
107
+ models = response.json()
108
+
109
+ for model in models[:30]:
110
+ self.huggingface_data['models'].append({
111
+ 'name': model.get('id', 'Unknown'),
112
+ 'downloads': model.get('downloads', 0),
113
+ 'likes': model.get('likes', 0),
114
+ 'task': model.get('pipeline_tag', 'N/A'),
115
+ 'url': f"https://huggingface.co/{model.get('id', '')}"
116
+ })
117
+
118
+ print(f"✅ {len(self.huggingface_data['models'])}개 트렌딩 모델 수집 완료")
119
+
120
+ except Exception as e:
121
+ print(f"❌ 모델 수집 오류: {e}")
122
+
123
+ # 스페이스 트렌딩 (웹 크롤링)
124
+ try:
125
+ spaces_url = "https://huggingface.co/spaces"
126
+ response = requests.get(spaces_url, headers={
127
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
128
+ }, timeout=10)
129
+
130
+ soup = BeautifulSoup(response.content, 'html.parser')
131
+
132
+ # 스페이스 링크 추출
133
+ space_count = 0
134
+ for link in soup.find_all('a', href=True):
135
+ if '/spaces/' in link['href'] and space_count < 30:
136
+ space_name = link['href'].replace('/spaces/', '')
137
+ if '/' in space_name and len(space_name) > 3:
138
+ title = link.get_text(strip=True)
139
+ if title:
140
+ self.huggingface_data['spaces'].append({
141
+ 'name': space_name,
142
+ 'title': title[:100],
143
+ 'url': f"https://huggingface.co{link['href']}"
144
+ })
145
+ space_count += 1
146
+
147
+ print(f"✅ {len(self.huggingface_data['spaces'])}개 트렌딩 스페이스 수집 완료")
148
+
149
+ except Exception as e:
150
+ print(f"❌ 스페이스 수집 오류: {e}")
151
+
152
+ return self.huggingface_data
153
+
154
+ def categorize_news(self, news_list: List[Dict]) -> List[Dict]:
155
+ """뉴스 카테고리 분류"""
156
+ for news in news_list:
157
+ title = news['title'].lower()
158
+ news['category'] = "기타"
159
+
160
+ for category, keywords in self.categories.items():
161
+ if any(keyword in title for keyword in keywords):
162
+ news['category'] = category
163
+ break
164
+
165
+ return news_list
166
+
167
+ def analyze_with_qwen(self, text: str, instruction: str) -> str:
168
+ """Fireworks AI Qwen 모델을 사용한 분석"""
169
+ url = "https://api.fireworks.ai/inference/v1/chat/completions"
170
+
171
+ payload = {
172
+ "model": "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507",
173
+ "max_tokens": 4096,
174
+ "top_p": 1,
175
+ "top_k": 40,
176
+ "presence_penalty": 0,
177
+ "frequency_penalty": 0,
178
+ "temperature": 0.6,
179
+ "messages": [
180
+ {
181
+ "role": "system",
182
+ "content": "당신은 AI 뉴스를 초등학생도 이해할 수 있게 쉽게 설명하는 전문가입니다."
183
+ },
184
+ {
185
+ "role": "user",
186
+ "content": f"{instruction}\n\n뉴스: {text}"
187
+ }
188
+ ]
189
+ }
190
+
191
+ headers = {
192
+ "Accept": "application/json",
193
+ "Content-Type": "application/json",
194
+ "Authorization": f"Bearer {self.fireworks_api_key}"
195
+ }
196
+
197
+ try:
198
+ response = requests.post(url, headers=headers, data=json.dumps(payload), timeout=30)
199
+
200
+ if response.status_code == 200:
201
+ result = response.json()
202
+ return result['choices'][0]['message']['content']
203
+ else:
204
+ return f"분석 실패 (상태 코드: {response.status_code})"
205
+
206
+ except Exception as e:
207
+ return f"분석 오류: {str(e)}"
208
+
209
+ def fact_check_with_brave(self, query: str) -> List[Dict]:
210
+ """Brave Search를 통한 팩트 체크"""
211
+ url = "https://api.search.brave.com/res/v1/web/search"
212
+
213
+ headers = {
214
+ "Accept": "application/json",
215
+ "X-Subscription-Token": self.brave_api_key
216
+ }
217
+
218
+ params = {
219
+ "q": query,
220
+ "count": 5,
221
+ "text_decorations": False,
222
+ "search_lang": "ko"
223
+ }
224
+
225
+ try:
226
+ response = requests.get(url, headers=headers, params=params, timeout=10)
227
+
228
+ if response.status_code == 200:
229
+ data = response.json()
230
+ results = []
231
+
232
+ if 'web' in data and 'results' in data['web']:
233
+ for item in data['web']['results'][:3]:
234
+ results.append({
235
+ 'title': item.get('title', ''),
236
+ 'description': item.get('description', ''),
237
+ 'url': item.get('url', '')
238
+ })
239
+
240
+ return results
241
+ else:
242
+ return []
243
+
244
+ except Exception as e:
245
+ print(f"❌ Brave Search 오류: {e}")
246
+ return []
247
+
248
+ def generate_report(self, news_list: List[Dict], analyze_news: bool = True) -> str:
249
+ """종합 리포트 생성"""
250
+ report = []
251
+ report.append("=" * 80)
252
+ report.append("📊 AI 뉴스 & 허깅페이스 트렌딩 종합 리포트")
253
+ report.append(f"📅 생성일시: {datetime.now().strftime('%Y년 %m월 %d일 %H:%M')}")
254
+ report.append("=" * 80)
255
+ report.append("")
256
+
257
+ # 1. 카테고리별 뉴스 분석
258
+ report.append("📰 === AI TIMES 뉴스 분석 ===")
259
+ report.append("")
260
+
261
+ categorized_news = {}
262
+ for news in news_list:
263
+ category = news.get('category', '기타')
264
+ if category not in categorized_news:
265
+ categorized_news[category] = []
266
+ categorized_news[category].append(news)
267
+
268
+ for category, articles in categorized_news.items():
269
+ report.append(f"📌 [{category}] ({len(articles)}건)")
270
+ report.append("-" * 80)
271
+
272
+ for i, article in enumerate(articles[:5], 1): # 카테고리당 5개만
273
+ report.append(f"{i}. {article['title']}")
274
+ report.append(f" 🔗 {article['url']}")
275
+ report.append(f" 📅 {article.get('date', 'N/A')}")
276
+
277
+ # LLM 분석 (선택적)
278
+ if analyze_news and i <= 2: # 각 카테고리 상위 2개만 분석
279
+ print(f"🤖 LLM 분석 중: {article['title'][:50]}...")
280
+
281
+ instruction = """이 뉴스를 다음 형식으로 분석해주세요:
282
+ 1. 핵심 내용 (2-3문장, 초등학생 수준)
283
+ 2. 왜 중요한가? (1-2문장)
284
+ 3. 당신이 해야 할 행동 (1-2개 항목)
285
+
286
+ 간결하고 명확하게 작성해주세요."""
287
+
288
+ analysis = self.analyze_with_qwen(article['title'], instruction)
289
+ report.append(f"\n 🤖 AI 분석:")
290
+ for line in analysis.split('\n'):
291
+ if line.strip():
292
+ report.append(f" {line.strip()}")
293
+
294
+ # 팩트 체크 (선택적)
295
+ fact_check = self.fact_check_with_brave(article['title'][:100])
296
+ if fact_check:
297
+ report.append(f"\n ✅ 팩트 체크 (Brave Search):")
298
+ for fc in fact_check[:2]:
299
+ report.append(f" • {fc['title']}")
300
+ report.append(f" {fc['url']}")
301
+
302
+ time.sleep(2) # API 레이트 리밋 고려
303
+
304
+ report.append("")
305
+
306
+ report.append("")
307
+
308
+ # 2. 허깅페이스 트렌딩
309
+ report.append("🤗 === 허깅페이스 트렌딩 TOP 30 ===")
310
+ report.append("")
311
+
312
+ # 모델
313
+ report.append("🔥 트렌딩 모델 TOP 30")
314
+ report.append("-" * 80)
315
+ for i, model in enumerate(self.huggingface_data['models'][:30], 1):
316
+ report.append(f"{i:2d}. {model['name']}")
317
+ report.append(f" 📊 다운로드: {model['downloads']:,} | ❤️ 좋아요: {model['likes']:,}")
318
+ report.append(f" 🏷️ Task: {model['task']}")
319
+ report.append(f" 🔗 {model['url']}")
320
+ report.append("")
321
+
322
+ report.append("")
323
+
324
+ # 스페이스
325
+ report.append("🚀 트렌딩 스페이스 TOP 30")
326
+ report.append("-" * 80)
327
+ for i, space in enumerate(self.huggingface_data['spaces'][:30], 1):
328
+ report.append(f"{i:2d}. {space['name']}")
329
+ report.append(f" 📝 {space['title']}")
330
+ report.append(f" 🔗 {space['url']}")
331
+ report.append("")
332
+
333
+ # 3. 종합 요약
334
+ report.append("=" * 80)
335
+ report.append("📈 종합 요약")
336
+ report.append("=" * 80)
337
+ report.append(f"• 총 뉴스 수집: {len(news_list)}건")
338
+ report.append(f"• 카테고리 수: {len(categorized_news)}개")
339
+ report.append(f"• 트렌딩 모델: {len(self.huggingface_data['models'])}개")
340
+ report.append(f"• 트렌딩 스페이스: {len(self.huggingface_data['spaces'])}개")
341
+ report.append("")
342
+
343
+ return '\n'.join(report)
344
+
345
+ def run_full_analysis(self, news_urls: List[str], analyze_with_llm: bool = True) -> str:
346
+ """전체 분석 실행"""
347
+ print("🚀 AI 뉴스 & 허깅페이스 트렌딩 분석 시작...")
348
+ print("")
349
+
350
+ # 1. 뉴스 수집
351
+ news_list = self.fetch_aitimes_news(news_urls)
352
+ print(f"✅ 총 {len(news_list)}건의 뉴스 수집 완료")
353
+ print("")
354
+
355
+ # 2. 뉴스 카테고리 분류
356
+ categorized_news = self.categorize_news(news_list)
357
+ print("✅ 뉴스 카테고리 분류 완료")
358
+ print("")
359
+
360
+ # 3. 허깅페이스 트렌딩 수집
361
+ self.fetch_huggingface_trending()
362
+ print("")
363
+
364
+ # 4. 리포트 생성
365
+ print("📝 리포트 생성 중...")
366
+ report = self.generate_report(categorized_news, analyze_news=analyze_with_llm)
367
+
368
+ print("")
369
+ print("✅ 분석 완료!")
370
+
371
+ return report
372
+
373
+ def save_report(self, report: str, filename: str = None):
374
+ """리포트 저장"""
375
+ if filename is None:
376
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
377
+ filename = f"ai_news_report_{timestamp}.txt"
378
+
379
+ with open(filename, 'w', encoding='utf-8') as f:
380
+ f.write(report)
381
+
382
+ print(f"💾 리포트 저장 완료: {filename}")
383
+
384
+
385
+ # ==================== 사용 예시 ====================
386
+
387
+ def main():
388
+ """메인 실행 함수"""
389
+
390
+ # API 키 설정
391
+ FIREWORKS_API_KEY = "YOUR_FIREWORKS_API_KEY" # 여기에 Fireworks API 키 입력
392
+ BRAVE_API_KEY = "YOUR_BRAVE_API_KEY" # 여기에 Brave Search API 키 입력
393
+
394
+ # AI Times 뉴스 URL
395
+ news_urls = [
396
+ "https://www.aitimes.com/news/articleList.html?sc_multi_code=S2&view_type=sm", # AI 산업
397
+ "https://www.aitimes.com/news/articleList.html?sc_section_code=S1N24&view_type=sm" # AI 기술
398
+ ]
399
+
400
+ # 분석기 초기화
401
+ analyzer = AINewsAnalyzer(
402
+ fireworks_api_key=FIREWORKS_API_KEY,
403
+ brave_api_key=BRAVE_API_KEY
404
+ )
405
+
406
+ # 전체 분석 실행
407
+ # analyze_with_llm=False로 설정하면 LLM 분석 없이 빠르게 수집만 함
408
+ report = analyzer.run_full_analysis(
409
+ news_urls=news_urls,
410
+ analyze_with_llm=True # LLM 분석 활성화 (시간이 오래 걸림)
411
+ )
412
+
413
+ # 결과 출력
414
+ print("\n" + "=" * 80)
415
+ print(report)
416
+
417
+ # 파일 저장
418
+ analyzer.save_report(report)
419
+
420
+
421
+ if __name__ == "__main__":
422
+ main()
423
+
424
+
425
+ # ==================== 사용 팁 ====================
426
+ """
427
+ 1. API 키 설정:
428
+ - Fireworks AI: https://fireworks.ai/
429
+ - Brave Search: https://brave.com/search/api/
430
+
431
+ 2. 빠른 테스트 (LLM 분석 없이):
432
+ analyzer.run_full_analysis(news_urls, analyze_with_llm=False)
433
+
434
+ 3. 특정 카테고리만 분석:
435
+ categorized_news에서 원하는 카테고리 필터링
436
+
437
+ 4. 크롤링 주기 조정:
438
+ time.sleep() 값을 조정하여 속도/안정성 균형
439
+
440
+ 5. 결과 활용:
441
+ - JSON으로 저장: json.dumps(analyzer.huggingface_data)
442
+ - 데이터베이스 저장
443
+ - 대시보드 연동
444
+ """