Files
aiagents-stock/news_flow_scheduler.py
T
2026-01-25 16:53:55 +08:00

384 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
新闻流量定时任务调度模块
实现三种定时任务:热点同步(30分钟)、预警生成(1小时)、深度分析(2小时)
"""
import schedule
import threading
import time
import logging
from datetime import datetime
from typing import Dict, List, Optional, Callable
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class NewsFlowScheduler:
"""新闻流量定时任务调度器"""
# 任务类型定义
TASK_TYPES = {
'sync_hotspots': {
'name': '热点同步',
'interval': 30, # 分钟
'description': '同步22个平台的热点数据',
},
'generate_alerts': {
'name': '预警生成',
'interval': 60, # 分钟
'description': '检查预警条件并生成预警',
},
'deep_analysis': {
'name': '深度分析',
'interval': 120, # 分钟
'description': '运行完整的AI分析',
},
}
def __init__(self):
"""初始化调度器"""
self.running = False
self.thread = None
self.lock = threading.Lock()
# 任务配置
self.task_enabled = {
'sync_hotspots': True,
'generate_alerts': True,
'deep_analysis': True,
}
# 任务间隔(分钟)
self.task_intervals = {
'sync_hotspots': 30,
'generate_alerts': 60,
'deep_analysis': 120,
}
# 上次运行时间
self.last_run_times = {}
# 依赖模块
self.engine = None
self.db = None
self.alert_system = None
self._init_dependencies()
logger.info("[新闻流量] 调度器初始化完成")
def _init_dependencies(self):
"""初始化依赖模块"""
try:
from news_flow_db import news_flow_db
self.db = news_flow_db
except Exception as e:
logger.warning(f"数据库模块初始化失败: {e}")
try:
from news_flow_alert import news_flow_alert_system
self.alert_system = news_flow_alert_system
except Exception as e:
logger.warning(f"预警系统初始化失败: {e}")
def _get_engine(self):
"""延迟加载引擎(避免循环导入)"""
if self.engine is None:
try:
from news_flow_engine import news_flow_engine
self.engine = news_flow_engine
except Exception as e:
logger.error(f"引擎模块初始化失败: {e}")
return self.engine
def start(self):
"""启动调度器"""
if self.running:
logger.warning("[新闻流量] 调度器已在运行")
return
with self.lock:
# 清除旧任务
self._clear_jobs()
# 注册任务
if self.task_enabled.get('sync_hotspots'):
interval = self.task_intervals.get('sync_hotspots', 30)
job = schedule.every(interval).minutes.do(self._run_sync_hotspots)
job.tag('news_flow', 'sync_hotspots')
logger.info(f"[新闻流量] 注册热点同步任务,间隔{interval}分钟")
if self.task_enabled.get('generate_alerts'):
interval = self.task_intervals.get('generate_alerts', 60)
job = schedule.every(interval).minutes.do(self._run_generate_alerts)
job.tag('news_flow', 'generate_alerts')
logger.info(f"[新闻流量] 注册预警生成任务,间隔{interval}分钟")
if self.task_enabled.get('deep_analysis'):
interval = self.task_intervals.get('deep_analysis', 120)
job = schedule.every(interval).minutes.do(self._run_deep_analysis)
job.tag('news_flow', 'deep_analysis')
logger.info(f"[新闻流量] 注册深度分析任务,间隔{interval}分钟")
# 启动调度线程
self.running = True
self.thread = threading.Thread(target=self._schedule_loop, daemon=True)
self.thread.start()
logger.info("[新闻流量] 调度器已启动")
def stop(self):
"""停止调度器"""
with self.lock:
self.running = False
self._clear_jobs()
logger.info("[新闻流量] 调度器已停止")
def _clear_jobs(self):
"""清除本模块的任务"""
jobs_to_remove = [job for job in schedule.jobs if 'news_flow' in job.tags]
for job in jobs_to_remove:
schedule.cancel_job(job)
def _schedule_loop(self):
"""调度循环"""
while self.running:
try:
schedule.run_pending()
except Exception as e:
logger.error(f"[新闻流量] 调度循环异常: {e}")
time.sleep(30) # 每30秒检查一次
def _log_task(self, task_name: str, task_type: str,
status: str, message: str = '',
duration: float = 0, snapshot_id: int = None):
"""记录任务日志"""
try:
if self.db:
self.db.save_scheduler_log(
task_name, task_type, status, message, duration, snapshot_id
)
except Exception as e:
logger.error(f"记录任务日志失败: {e}")
def _run_sync_hotspots(self):
"""运行热点同步任务"""
task_type = 'sync_hotspots'
task_name = self.TASK_TYPES[task_type]['name']
logger.info(f"[新闻流量] 开始执行: {task_name}")
start_time = time.time()
try:
engine = self._get_engine()
if not engine:
raise Exception("引擎模块不可用")
# 运行快速分析(仅数据同步和基础计算)
result = engine.run_quick_analysis()
duration = time.time() - start_time
if result.get('success'):
snapshot_id = result.get('snapshot_id')
message = f"成功同步{result.get('success_count', 0)}个平台数据"
self._log_task(task_name, task_type, 'success', message, duration, snapshot_id)
logger.info(f"[新闻流量] {task_name}完成: {message}")
else:
message = result.get('error', '未知错误')
self._log_task(task_name, task_type, 'failed', message, duration)
logger.error(f"[新闻流量] {task_name}失败: {message}")
self.last_run_times[task_type] = datetime.now()
except Exception as e:
duration = time.time() - start_time
message = str(e)
self._log_task(task_name, task_type, 'error', message, duration)
logger.error(f"[新闻流量] {task_name}异常: {e}")
def _run_generate_alerts(self):
"""运行预警生成任务"""
task_type = 'generate_alerts'
task_name = self.TASK_TYPES[task_type]['name']
logger.info(f"[新闻流量] 开始执行: {task_name}")
start_time = time.time()
try:
engine = self._get_engine()
if not engine:
raise Exception("引擎模块不可用")
# 运行预警检查
result = engine.run_alert_check()
duration = time.time() - start_time
if result.get('success'):
alert_count = len(result.get('alerts', []))
message = f"生成{alert_count}个预警"
self._log_task(task_name, task_type, 'success', message, duration)
logger.info(f"[新闻流量] {task_name}完成: {message}")
# 发送通知
if alert_count > 0 and self.alert_system:
self.alert_system.send_notification(result['alerts'])
else:
message = result.get('error', '未知错误')
self._log_task(task_name, task_type, 'failed', message, duration)
logger.error(f"[新闻流量] {task_name}失败: {message}")
self.last_run_times[task_type] = datetime.now()
except Exception as e:
duration = time.time() - start_time
message = str(e)
self._log_task(task_name, task_type, 'error', message, duration)
logger.error(f"[新闻流量] {task_name}异常: {e}")
def _run_deep_analysis(self):
"""运行深度分析任务"""
task_type = 'deep_analysis'
task_name = self.TASK_TYPES[task_type]['name']
logger.info(f"[新闻流量] 开始执行: {task_name}")
start_time = time.time()
try:
engine = self._get_engine()
if not engine:
raise Exception("引擎模块不可用")
# 运行完整分析(包含AI
result = engine.run_full_analysis(include_ai=True)
duration = time.time() - start_time
if result.get('success'):
snapshot_id = result.get('snapshot_id')
advice = result.get('ai_analysis', {}).get('investment_advice', {}).get('advice', 'N/A')
message = f"深度分析完成,建议:{advice}"
self._log_task(task_name, task_type, 'success', message, duration, snapshot_id)
logger.info(f"[新闻流量] {task_name}完成: {message}")
else:
message = result.get('error', '未知错误')
self._log_task(task_name, task_type, 'failed', message, duration)
logger.error(f"[新闻流量] {task_name}失败: {message}")
self.last_run_times[task_type] = datetime.now()
except Exception as e:
duration = time.time() - start_time
message = str(e)
self._log_task(task_name, task_type, 'error', message, duration)
logger.error(f"[新闻流量] {task_name}异常: {e}")
# ==================== 手动触发方法 ====================
def run_sync_now(self) -> Dict:
"""立即执行热点同步"""
logger.info("[新闻流量] 手动触发热点同步")
self._run_sync_hotspots()
return {'success': True, 'message': '热点同步已执行'}
def run_alerts_now(self) -> Dict:
"""立即执行预警生成"""
logger.info("[新闻流量] 手动触发预警生成")
self._run_generate_alerts()
return {'success': True, 'message': '预警生成已执行'}
def run_analysis_now(self) -> Dict:
"""立即执行深度分析"""
logger.info("[新闻流量] 手动触发深度分析")
self._run_deep_analysis()
return {'success': True, 'message': '深度分析已执行'}
# ==================== 配置方法 ====================
def set_task_enabled(self, task_type: str, enabled: bool):
"""设置任务开关"""
if task_type in self.task_enabled:
self.task_enabled[task_type] = enabled
logger.info(f"[新闻流量] 任务 {task_type} {'启用' if enabled else '禁用'}")
# 如果调度器正在运行,重新注册任务
if self.running:
self.stop()
self.start()
def set_task_interval(self, task_type: str, interval: int):
"""设置任务间隔(分钟)"""
if task_type in self.task_intervals:
self.task_intervals[task_type] = interval
logger.info(f"[新闻流量] 任务 {task_type} 间隔设置为 {interval} 分钟")
# 如果调度器正在运行,重新注册任务
if self.running:
self.stop()
self.start()
def get_status(self) -> Dict:
"""获取调度器状态"""
return {
'running': self.running,
'task_enabled': self.task_enabled.copy(),
'task_intervals': self.task_intervals.copy(),
'last_run_times': {
k: v.strftime('%Y-%m-%d %H:%M:%S') if v else None
for k, v in self.last_run_times.items()
},
'next_run_times': self._get_next_run_times(),
}
def _get_next_run_times(self) -> Dict:
"""获取下次运行时间"""
next_times = {}
for job in schedule.jobs:
if 'news_flow' in job.tags:
for tag in job.tags:
if tag in self.TASK_TYPES:
next_times[tag] = str(job.next_run)
return next_times
def get_task_logs(self, days: int = 7, task_type: str = None) -> List[Dict]:
"""获取任务日志"""
if self.db:
return self.db.get_scheduler_logs(days, task_type)
return []
# 全局实例
news_flow_scheduler = NewsFlowScheduler()
# 测试代码
if __name__ == "__main__":
print("=== 测试新闻流量调度器 ===")
# 获取状态
status = news_flow_scheduler.get_status()
print(f"\n调度器状态:")
print(f" 运行中: {status['running']}")
print(f" 任务配置: {status['task_enabled']}")
print(f" 任务间隔: {status['task_intervals']}")
# 启动调度器
print("\n启动调度器...")
news_flow_scheduler.start()
# 再次获取状态
status = news_flow_scheduler.get_status()
print(f"\n调度器状态:")
print(f" 运行中: {status['running']}")
print(f" 下次运行: {status['next_run_times']}")
# 等待一会儿
print("\n等待5秒...")
time.sleep(5)
# 停止调度器
print("\n停止调度器...")
news_flow_scheduler.stop()
print("\n测试完成")