khjhs60199 commited on
Commit
ea8c7be
·
verified ·
1 Parent(s): 62fddb6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +113 -81
app.py CHANGED
@@ -38,6 +38,10 @@ class NewsApp:
38
  self.is_crawling = False
39
  self.is_initialized = False
40
 
 
 
 
 
41
  # 在背景初始化重型組件
42
  self._initialize_components()
43
 
@@ -82,25 +86,53 @@ class NewsApp:
82
  """更新進度信息"""
83
  timestamp = datetime.now().strftime('%H:%M:%S')
84
  self.current_progress = f"[{timestamp}] {message}"
 
85
  logger.info(f"進度更新: {message}")
86
 
87
- def get_progress(self) -> str:
88
- """獲取當前進度"""
89
- return self.current_progress
 
 
 
90
 
91
- def get_latest_news(self, category: str = "all", limit: int = 50) -> str:
92
  """獲取最新新聞並格式化顯示"""
93
  try:
 
 
 
 
 
 
 
 
 
 
 
94
  news_data = self.db.get_recent_news(category=category, limit=limit)
95
  if not news_data:
96
- return "📰 暫無新聞資料,請先執行爬蟲任務"
 
 
 
 
97
 
98
- return format_news_for_display(news_data)
99
 
100
  except Exception as e:
101
  logger.error(f"獲取新聞時發生錯誤: {e}")
102
  return f"❌ 獲取新聞時發生錯誤: {str(e)}"
103
 
 
 
 
 
 
 
 
 
 
104
  def manual_crawl(self) -> str:
105
  """手動觸發爬蟲"""
106
  if not self.is_initialized:
@@ -246,19 +278,14 @@ def api_manual_crawl():
246
  @flask_app.route('/api/progress', methods=['GET'])
247
  def api_get_progress():
248
  """獲取爬蟲進度API"""
 
249
  return jsonify({
250
- 'progress': app.get_progress(),
251
  'is_crawling': app.is_crawling,
252
- 'is_initialized': app.is_initialized
 
253
  })
254
 
255
- # 定期更新函數
256
- def periodic_update():
257
- """定期更新進度和新聞"""
258
- while True:
259
- time.sleep(3) # 每3秒更新一次
260
- yield [app.get_progress(), app.get_latest_news("all")]
261
-
262
  # 創建 Gradio 介面
263
  def create_interface():
264
  with gr.Blocks(
@@ -292,61 +319,93 @@ def create_interface():
292
  with gr.Row():
293
  with gr.Column(scale=2):
294
  category_radio = gr.Radio(
295
- choices=["all", "us_stock", "tw_stock"],
 
 
 
 
296
  value="all",
297
- label="新聞分類",
298
  info="選擇要顯示的新聞類型"
299
  )
300
  with gr.Column(scale=1):
301
  refresh_btn = gr.Button("🔄 重新整理", variant="primary")
302
  manual_crawl_btn = gr.Button("🚀 手動爬取", variant="secondary")
303
 
304
- # 進度顯示
305
  progress_display = gr.Textbox(
306
- label="📊 系統狀態與進度",
307
- value=app.get_progress(),
308
  interactive=False,
309
  elem_classes=["progress-box"],
310
- lines=2
311
  )
312
 
313
- news_display = gr.HTML(label="新聞內容")
 
 
 
314
  crawl_result = gr.Textbox(label="爬取結果", visible=False)
315
 
 
 
 
316
  # 更新函數
317
- def update_all():
318
- """更新所有信息"""
319
- return app.get_progress(), app.get_latest_news("all")
 
 
 
 
320
 
321
  def refresh_news(category):
322
- return app.get_latest_news(category)
 
 
323
 
324
- def handle_manual_crawl():
 
325
  result = app.manual_crawl()
326
- # 爬取完成後自動刷新新聞
327
- news = app.get_latest_news("all")
328
  return result, news
329
 
330
- # 使用定時器替代 every 參數
331
- timer = gr.Timer(value=3) # 每3秒觸發一次
332
- timer.tick(
333
- fn=update_all,
334
- outputs=[progress_display, news_display]
335
  )
336
 
337
  # 綁定事件
338
- refresh_btn.click(refresh_news, inputs=[category_radio], outputs=[news_display])
 
 
 
 
 
339
  manual_crawl_btn.click(
340
- handle_manual_crawl,
 
341
  outputs=[crawl_result, news_display]
342
  ).then(
343
  lambda: gr.update(visible=True),
344
  outputs=[crawl_result]
345
  )
346
- category_radio.change(refresh_news, inputs=[category_radio], outputs=[news_display])
 
 
 
 
 
347
 
348
  # 初始載入
349
- interface.load(update_all, outputs=[progress_display, news_display])
 
 
 
 
350
 
351
  with gr.Tab("📊 統計資訊"):
352
  stats_display = gr.Markdown()
@@ -368,6 +427,9 @@ def create_interface():
368
 
369
  **參數:**
370
  - `category`: 新聞分類 (可選,默認: all)
 
 
 
371
  - `limit`: 返回數量 (可選,默認: 50)
372
 
373
  **響應示例:**
@@ -421,22 +483,9 @@ def create_interface():
421
  response = requests.get('http://localhost:5000/api/news?category=us_stock&limit=10')
422
  us_news = response.json()
423
 
424
- # 檢查系統狀態
425
- response = requests.get('http://localhost:5000/api/progress')
426
- status = response.json()
427
- ```
428
-
429
- **JavaScript:**
430
- ```javascript
431
- // 獲取新聞
432
- fetch('/api/news?category=tw_stock')
433
- .then(response => response.json())
434
- .then(data => console.log(data));
435
-
436
- // 觸發爬蟲
437
- fetch('/api/crawl', {method: 'POST'})
438
- .then(response => response.json())
439
- .then(data => console.log(data));
440
  ```
441
  """)
442
 
@@ -447,40 +496,23 @@ def create_interface():
447
  ### ⚡ 即時處理
448
  - **實時分析**: 每篇文章爬取完成立即進行情緒分析
449
  - **即時存檔**: 分析完成後立即保存到SQLite資料庫
450
- - **進度追蹤**: 實時顯示爬蟲和分析進度
451
- - **去重檢查**: 存檔前檢查標題相似度避免重複
452
 
453
  ### 📊 情緒分析
454
  - **模型**: `uer/roberta-base-finetuned-jd-binary-chinese`
455
  - **分類**: 正面 (綠色) / 負面 (紅色) / 中性 (灰色)
456
  - **準確性**: 針對中文金融新聞優化
457
- - **後台載入**: 模型在背景載入,不阻塞介面啟動
458
 
459
- ### 🕷️ 新聞爬蟲
460
- - **來源**: 鉅亨網 (cnyes.com)
461
- - **分類**: 美股、台股新聞
462
  - **頻率**: 每30分鐘自動更新
463
- - **反爬蟲**: 隨機延遲、User-Agent輪換
464
-
465
- ### 💾 資料管理
466
- - **儲存**: SQLite 本地資料庫 (news.db)
467
- - **索引**: 針對URL、分類、時間、情緒建立索引
468
- - **保留期**: 自動清理兩週前的資料
469
- - **效能**: 優化查詢,支持並發存取
470
-
471
- ### 🔌 API接口
472
- - **REST API**: 提供完整的RESTful API
473
- - **JSON格式**: 標準JSON響應格式
474
- - **錯誤處理**: 完善的錯誤處理和狀態碼
475
- - **跨域支持**: 支持CORS跨域請求
476
-
477
- ---
478
-
479
- 💡 **使用提示**:
480
- - 首次啟動會在背景下載模型,請耐心等待
481
- - 系統狀態會顯示初始化進度
482
- - 建議使用SSD硬碟提升SQLite性能
483
- - API接口可用於整合其他應用系統
484
  """)
485
 
486
  return interface
 
38
  self.is_crawling = False
39
  self.is_initialized = False
40
 
41
+ # 上次新聞更新時間,用於防止無意義的刷新
42
+ self.last_news_update = 0
43
+ self.last_progress_update = 0
44
+
45
  # 在背景初始化重型組件
46
  self._initialize_components()
47
 
 
86
  """更新進度信息"""
87
  timestamp = datetime.now().strftime('%H:%M:%S')
88
  self.current_progress = f"[{timestamp}] {message}"
89
+ self.last_progress_update = time.time()
90
  logger.info(f"進度更新: {message}")
91
 
92
+ def get_progress(self) -> tuple:
93
+ """獲取當前進度和是否需要更新"""
94
+ current_time = time.time()
95
+ # 只有在進度真的有更新時才返回新內容
96
+ needs_update = (current_time - self.last_progress_update) < 5 # 5秒內的更新才顯示
97
+ return self.current_progress, needs_update
98
 
99
+ def get_latest_news(self, category: str = "all", limit: int = 50, force_refresh: bool = False) -> str:
100
  """獲取最新新聞並格式化顯示"""
101
  try:
102
+ # 檢查是否需要刷新(避免無意義的閃爍)
103
+ current_time = time.time()
104
+ if not force_refresh and (current_time - self.last_news_update) < 10:
105
+ # 10秒內不重複查詢,除非強制刷新
106
+ pass
107
+
108
+ self.last_news_update = current_time
109
+
110
+ # 記錄分類選擇
111
+ logger.info(f"獲取新聞 - 分類: {category}, 限制: {limit}")
112
+
113
  news_data = self.db.get_recent_news(category=category, limit=limit)
114
  if not news_data:
115
+ return f"📰 暫無 {self._get_category_name(category)} 新聞資料,請先執行爬蟲任務"
116
+
117
+ # 添加分類標題
118
+ category_title = f"📊 當前顯示: {self._get_category_name(category)} ({len(news_data)} 篇)"
119
+ formatted_news = format_news_for_display(news_data)
120
 
121
+ return f"<div style='background: #e3f2fd; padding: 10px; margin-bottom: 15px; border-radius: 5px; text-align: center; font-weight: bold;'>{category_title}</div>{formatted_news}"
122
 
123
  except Exception as e:
124
  logger.error(f"獲取新聞時發生錯誤: {e}")
125
  return f"❌ 獲取新聞時發生錯誤: {str(e)}"
126
 
127
+ def _get_category_name(self, category: str) -> str:
128
+ """獲取分類中文名稱"""
129
+ category_names = {
130
+ "all": "所有新聞",
131
+ "us_stock": "美股新聞",
132
+ "tw_stock": "台股新聞"
133
+ }
134
+ return category_names.get(category, category)
135
+
136
  def manual_crawl(self) -> str:
137
  """手動觸發爬蟲"""
138
  if not self.is_initialized:
 
278
  @flask_app.route('/api/progress', methods=['GET'])
279
  def api_get_progress():
280
  """獲取爬蟲進度API"""
281
+ progress, needs_update = app.get_progress()
282
  return jsonify({
283
+ 'progress': progress,
284
  'is_crawling': app.is_crawling,
285
+ 'is_initialized': app.is_initialized,
286
+ 'needs_update': needs_update
287
  })
288
 
 
 
 
 
 
 
 
289
  # 創建 Gradio 介面
290
  def create_interface():
291
  with gr.Blocks(
 
319
  with gr.Row():
320
  with gr.Column(scale=2):
321
  category_radio = gr.Radio(
322
+ choices=[
323
+ ("所有新聞", "all"),
324
+ ("美股新聞", "us_stock"),
325
+ ("台股新聞", "tw_stock")
326
+ ],
327
  value="all",
328
+ label="📋 新聞分類",
329
  info="選擇要顯示的新聞類型"
330
  )
331
  with gr.Column(scale=1):
332
  refresh_btn = gr.Button("🔄 重新整理", variant="primary")
333
  manual_crawl_btn = gr.Button("🚀 手動爬取", variant="secondary")
334
 
335
+ # 進度顯示 - 只在有更新時顯示
336
  progress_display = gr.Textbox(
337
+ label="📊 系統狀態",
338
+ value=app.current_progress,
339
  interactive=False,
340
  elem_classes=["progress-box"],
341
+ lines=1
342
  )
343
 
344
+ news_display = gr.HTML(
345
+ label="新聞內容",
346
+ value=app.get_latest_news("all", force_refresh=True)
347
+ )
348
  crawl_result = gr.Textbox(label="爬取結果", visible=False)
349
 
350
+ # 狀態變量,用於追蹤當前選擇的分類
351
+ current_category = gr.State("all")
352
+
353
  # 更新函數
354
+ def update_progress_only():
355
+ """只更新進度,不更新新聞"""
356
+ progress, needs_update = app.get_progress()
357
+ if needs_update or app.is_crawling:
358
+ return progress
359
+ else:
360
+ return gr.update() # 不更新
361
 
362
  def refresh_news(category):
363
+ """刷新新聞內容"""
364
+ logger.info(f"刷新新聞 - 用戶選擇分類: {category}")
365
+ return app.get_latest_news(category, force_refresh=True), category
366
 
367
+ def handle_manual_crawl(current_cat):
368
+ """處理手動爬蟲"""
369
  result = app.manual_crawl()
370
+ # 爬取完成後自動刷新當前分類的新聞
371
+ news = app.get_latest_news(current_cat, force_refresh=True)
372
  return result, news
373
 
374
+ # 進度更新定時器 - 降低頻率,減少閃爍
375
+ progress_timer = gr.Timer(value=10) # 每10秒檢查一次進度
376
+ progress_timer.tick(
377
+ fn=update_progress_only,
378
+ outputs=[progress_display]
379
  )
380
 
381
  # 綁定事件
382
+ refresh_btn.click(
383
+ refresh_news,
384
+ inputs=[category_radio],
385
+ outputs=[news_display, current_category]
386
+ )
387
+
388
  manual_crawl_btn.click(
389
+ handle_manual_crawl,
390
+ inputs=[current_category],
391
  outputs=[crawl_result, news_display]
392
  ).then(
393
  lambda: gr.update(visible=True),
394
  outputs=[crawl_result]
395
  )
396
+
397
+ category_radio.change(
398
+ refresh_news,
399
+ inputs=[category_radio],
400
+ outputs=[news_display, current_category]
401
+ )
402
 
403
  # 初始載入
404
+ interface.load(
405
+ refresh_news,
406
+ inputs=[gr.State("all")],
407
+ outputs=[news_display, current_category]
408
+ )
409
 
410
  with gr.Tab("📊 統計資訊"):
411
  stats_display = gr.Markdown()
 
427
 
428
  **參數:**
429
  - `category`: 新聞分類 (可選,默認: all)
430
+ - `all`: 所有新聞
431
+ - `us_stock`: 美股新聞
432
+ - `tw_stock`: 台股新聞
433
  - `limit`: 返回數量 (可選,默認: 50)
434
 
435
  **響應示例:**
 
483
  response = requests.get('http://localhost:5000/api/news?category=us_stock&limit=10')
484
  us_news = response.json()
485
 
486
+ # 獲取台股新聞
487
+ response = requests.get('http://localhost:5000/api/news?category=tw_stock&limit=10')
488
+ tw_news = response.json()
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  ```
490
  """)
491
 
 
496
  ### ⚡ 即時處理
497
  - **實時分析**: 每篇文章爬取完成立即進行情緒分析
498
  - **即時存檔**: 分析完成後立即保存到SQLite資料庫
499
+ - **智能刷新**: 避免無意義的頁面閃爍
500
+ - **分類顯示**: 支援美股、台股、全部新聞分類查看
501
 
502
  ### 📊 情緒分析
503
  - **模型**: `uer/roberta-base-finetuned-jd-binary-chinese`
504
  - **分類**: 正面 (綠色) / 負面 (紅色) / 中性 (灰色)
505
  - **準確性**: 針對中文金融新聞優化
 
506
 
507
+ ### 🕷️ 新聞來源
508
+ - **美股**: https://news.cnyes.com/news/cat/us_stock
509
+ - **台股**: https://news.cnyes.com/news/cat/tw_stock_news
510
  - **頻率**: 每30分鐘自動更新
511
+
512
+ ### 📱 介面優化
513
+ - **防閃爍**: 智能更新機制,減少不必要的刷新
514
+ - **分類標示**: 清楚顯示當前查看的新聞分類
515
+ - **狀態追蹤**: 實時顯示系統運行狀態
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
  """)
517
 
518
  return interface