start_all.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. # -*- coding: utf-8 -*-
  2. # Author : Charley
  3. # Python : 3.12.10
  4. # Date : 2026/09/09
  5. r"""得卡统一部署守护:一键拉起全部常驻任务(采集 + 报告调度 + 运维)+ 崩溃自动重启。
  6. 各任务独立进程(采集都已接账号池,多进程共用 20 号池,线程/进程隔离安全)。守护主循环监控
  7. 子进程,任一崩溃退出即自动重启。这是部署到服务器的统一入口(放项目根目录,一眼可见)。
  8. 任务清单(TASKS):
  9. 采集:buy_record(购买记录5家)/ onsale_alert(上架提醒)/ sold_daily(已售每日)/ team(选队随机)
  10. 报告:onsale_report(在售报告 每天 09/15/20/01 四次)/ sold_report(已售 日报09:10 + 周报周一09:20 + 月报1号09:30)
  11. 运维:maintenance(账号池 cooling 巡检 + 报告 Excel 定时清理)/ replenish(账号池自动补货 每2h)
  12. 用法:
  13. python start_all.py # 启动全部
  14. python start_all.py buy_record team # 只启动指定任务(名字见 TASKS)
  15. Ctrl+C # 优雅终止所有子任务
  16. 上线前确认:新目录采集真跑会用账号池 20 号;若旧目录 D:\work\2026-08-02(deca_spider) 生产仍在跑,
  17. 需先协调(两边同时跑会重复请求、双倍量)。
  18. """
  19. import os
  20. import sys
  21. import time
  22. import subprocess
  23. _ROOT = os.path.dirname(os.path.abspath(__file__))
  24. os.chdir(_ROOT)
  25. from loguru import logger
  26. logger.remove()
  27. logger.add(sys.stderr, format="[{time:HH:mm:ss}] {level} {message}", level="INFO")
  28. logger.add("./logs/start_all_{time:YYYYMMDD}.log", encoding="utf-8", rotation="00:00",
  29. format="[{time:YYYY-MM-DD HH:mm:ss}] {level} {message}", level="INFO", retention="7 day")
  30. # 任务名 -> 启动命令 argv(相对 _ROOT 的脚本 + 参数;spawn 时前置 sys.executable -u)。
  31. # 各脚本自身 bootstrap 会 chdir 到各自所需目录,故这里统一 cwd=_ROOT 即可。
  32. TASKS = {
  33. # ---- 常驻采集(已接账号池)----
  34. "buy_record": ["spiders/buy_record_spider.py"], # 购买记录(5 家)
  35. "onsale_alert": ["spiders/onsale_alert_spider.py", "13:00"], # 上架提醒(新上架监控推送)
  36. "sold_daily": ["spiders/sold_daily_spider.py"], # 已售每日采集(每天 08:00)
  37. "team": ["spiders/deca_team_spider.py"], # 选队随机总价(5 分钟/轮)
  38. # ---- 报告调度(各自 schedule 常驻)----
  39. "onsale_report": ["reports/onsale_report/deca_on_sale_report.py", "loop"], # 在售报告:每天 09/15/20/01 四次
  40. "sold_report": ["reports/sold_report/run_scheduler.py"], # 已售:日报09:10 + 周报周一09:20 + 月报1号09:30
  41. # ---- 运维 ----
  42. "maintenance": ["spiders/maintenance.py"], # 账号池 cooling 巡检 + Excel 定时清理
  43. "replenish": ["spiders/replenish_accounts.py", "--loop"], # 账号池自动补货:每2h巡检,healthy不足即补(补前先释放dead号占的IP)
  44. }
  45. RESTART_DELAY = 15 # 子进程崩溃退出后,重启前等待秒数(避免疯狂重启刷屏)
  46. def spawn(name: str, argv: list, procs: dict) -> None:
  47. """启动一个任务为独立子进程并登记到 procs。
  48. Args:
  49. name (str): 任务名(TASKS 的 key)。
  50. argv (list): 相对 _ROOT 的脚本路径 + 参数(不含解释器)。
  51. procs (dict): 进程登记表 name -> Popen,就地更新。
  52. """
  53. p = subprocess.Popen([sys.executable, "-u"] + argv, cwd=_ROOT)
  54. procs[name] = p
  55. logger.info(f"启动 {name} pid={p.pid} -> {' '.join(argv)}")
  56. def main(names: list) -> None:
  57. """启动指定任务并守护:子进程崩溃退出即自动重启,Ctrl+C 优雅终止全部。
  58. Args:
  59. names (list): 要启动的任务名列表(须是 TASKS 的 key)。
  60. """
  61. procs = {}
  62. for name in names:
  63. spawn(name, TASKS[name], procs)
  64. logger.info(f"已启动 {len(procs)} 个任务,进入守护监控(崩溃自动重启):{names}")
  65. try:
  66. while True:
  67. for name in names:
  68. p = procs.get(name)
  69. if p and p.poll() is not None: # 子进程已退出(正常结束或崩溃)
  70. logger.warning(f"{name} 已退出(returncode={p.returncode}),{RESTART_DELAY}s 后重启")
  71. time.sleep(RESTART_DELAY)
  72. spawn(name, TASKS[name], procs)
  73. time.sleep(5)
  74. except KeyboardInterrupt:
  75. logger.info("收到中断信号,正在终止所有子任务……")
  76. for name, p in procs.items():
  77. try:
  78. p.terminate()
  79. logger.info(f"已终止 {name} pid={p.pid}")
  80. except Exception as e:
  81. logger.warning(f"终止 {name} 失败: {e}")
  82. if __name__ == "__main__":
  83. args = sys.argv[1:]
  84. if args:
  85. bad = [a for a in args if a not in TASKS]
  86. if bad:
  87. logger.error(f"未知任务名 {bad},可选: {list(TASKS)}")
  88. sys.exit(1)
  89. selected = args
  90. else:
  91. selected = list(TASKS)
  92. main(selected)