""" 主程序入口 - 股吧人气指示器 """ import sys import logging import time from datetime import datetime from PySide6.QtWidgets import QApplication from PySide6.QtCore import QTimer, Signal, QObject from config_manager import ConfigManager from database import DatabaseManager from spider import SpiderManager from llm_analyzer import LLMAnalyzer from main_window import MainWindow class BackendWorker(QObject): """后台工作器 - 处理爬取和分析任务""" fetch_finished = Signal(list) analysis_finished = Signal(float) error_occurred = Signal(str) status_update = Signal(str) def __init__(self, config_manager: ConfigManager, db_manager: DatabaseManager, spider: SpiderManager, analyzer: LLMAnalyzer): super().__init__() self.config = config_manager self.db = db_manager self.spider = spider self.analyzer = analyzer self.running = False self.last_fetch_time = 0 self.fetch_interval = 60 # 默认60秒 self.no_new_content_count = 0 # 无新内容计数 def start(self): """启动后台任务""" self.running = True self._run_cycle() def stop(self): """停止后台任务""" self.running = False def _run_cycle(self): """运行一个周期""" if not self.running: return try: # 1. 爬取评论 self.status_update.emit("正在爬取评论...") comments = self.spider.fetch() if not comments: self.no_new_content_count += 1 interval = self.fetch_interval * (1 + min(self.no_new_content_count * 0.5, 2)) self.status_update.emit(f"无新内容,{int(interval)}秒后重试...") QTimer.singleShot(int(interval * 1000), self._run_cycle) return # 2. 写入数据库 self.status_update.emit(f"获取到 {len(comments)} 条评论...") new_ids = self.db.add_comments_batch(comments) if new_ids: self.no_new_content_count = 0 self.status_update.emit(f"新增 {len(new_ids)} 条评论") # 3. 获取未分析评论并分析 unanalyzed = self.db.get_unanalyzed_comments(limit=10) if unanalyzed: self.status_update.emit(f"开始分析 {len(unanalyzed)} 条评论...") self._analyze_comments(unanalyzed) else: self.no_new_content_count += 1 # 4. 更新指示器 self._update_indicator() except Exception as e: self.error_occurred.emit(f"运行错误: {str(e)}") # 安排下一次执行 if self.running: interval = self.fetch_interval * (1 + min(self.no_new_content_count * 0.5, 2)) QTimer.singleShot(int(interval * 1000), self._run_cycle) def _analyze_comments(self, comments): """分析评论""" for i, comment in enumerate(comments): if not self.running: break try: self.status_update.emit(f"分析 {i+1}/{len(comments)}...") score, label = self.analyzer.analyze(comment['content']) if score is not None: self.db.mark_analyzed(comment['id'], score, label) time.sleep(1.0) # 延迟,避免API限流 else: self.db.mark_analyzed(comment['id'], 50, "无法判断") except Exception as e: self.error_occurred.emit(f"分析失败: {str(e)}") self.db.mark_analyzed(comment['id'], 50, "分析异常") def _update_indicator(self): """更新指示器显示""" scores = self.db.get_all_scores() if not scores: return # 计算平均分 avg_score = sum(scores) / len(scores) # 根据阈值确定标签 thresholds = self.config.get('ui', 'thresholds', default={'cold': 30, 'warm': 70}) cold = thresholds.get('cold', 30) warm = thresholds.get('warm', 70) if avg_score < cold: label = "看跌" elif avg_score > warm: label = "看涨" else: label = "中性" self.analysis_finished.emit(avg_score) def manual_refresh(self): """手动刷新""" self.no_new_content_count = 0 self._run_cycle() def update_fetch_interval(self, interval: int): """更新爬取间隔""" self.fetch_interval = interval def setup_logging(log_path: str, level: str = "INFO"): """配置日志""" logging.basicConfig( level=getattr(logging, level.upper(), logging.INFO), format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(log_path, encoding='utf-8'), logging.StreamHandler() ] ) def main(): """主函数""" # 创建应用 app = QApplication(sys.argv) app.setQuitOnLastWindowClosed(False) # 允许最小化到托盘 # 加载配置 config = ConfigManager("config.json") # 配置日志 log_config = config.logging_config setup_logging(log_config.get('path', 'guba.log'), log_config.get('level', 'INFO')) # 初始化组件 db = DatabaseManager(config.database_config.get('path', 'guba.db')) spider = SpiderManager(config.spider_config) analyzer = LLMAnalyzer(config.llm_api_config) # 创建后台工作器 worker = BackendWorker(config, db, spider, analyzer) worker.update_fetch_interval(config.spider_config.get('fetch_interval', 60)) # 创建主窗口 window = MainWindow(config) window.show() # 连接信号 worker.status_update.connect(window.update_status) worker.analysis_finished.connect(window.update_indicator) worker.error_occurred.connect(lambda msg: window.show_message("错误", msg)) # 设置按钮回调 window.set_refresh_callback(worker.manual_refresh) window.set_config_callback(window.show_config) # 启动后台任务 worker.start() # 运行应用 sys.exit(app.exec()) if __name__ == "__main__": main()