"""5yr 15min 回填冷却后自动续跑 watcher。 baostock 被限流/冷却时登录会卡死或失败。本 watcher 每 30min 探测一次 baostock, 可登就启动 backfill_15min_baostock(marker-based resume 自动续跑缺口),断路器触发 或登录失败就继续等。backfill 成功(exit 0,全部缺口填完)则退出。 detached 启动: nohup ./venv311/bin/python3 scripts/data_platform/resume_5yr_watcher.py \ > /Users/chufeng/data_cache/stock/logs/daily_update/resume_watcher.log 2>&1 &! """ import os import subprocess import time ROOT = "/Users/chufeng/.openclaw/sanguo_projects/sanguo_vnpy_v2" PY = os.path.join(ROOT, "venv311/bin/python3") SCRIPT = os.path.join(ROOT, "scripts/data_platform/backfill_15min_baostock.py") PROBE_INTERVAL = 1800 # 30min # 探测脚本:SIGALRM 给 bs.login() 套 15s 闹钟,避免卡死 _PROBE = r''' import signal def _h(*a): raise TimeoutError() signal.signal(signal.SIGALRM, _h) signal.alarm(15) try: import baostock as bs lg = bs.login() ok = lg.error_code == "0" try: bs.logout() except Exception: pass print("LOGIN_OK" if ok else "LOGIN_FAIL") except Exception as e: print("LOGIN_ERR", type(e).__name__, e) finally: signal.alarm(0) ''' def baostock_up() -> bool: try: r = subprocess.run([PY, "-c", _PROBE], capture_output=True, text=True, timeout=30) return "LOGIN_OK" in r.stdout except Exception: return False def main() -> None: round_ = 0 while True: round_ += 1 if baostock_up(): print(f"[round {round_}] baostock 可登,启动回填(marker-resume 续跑缺口)", flush=True) env = dict(os.environ) env["STOCK_ROOT"] = "/Users/chufeng/data_cache/stock" env["BS_START_DATE"] = "20210101" rc = subprocess.call([PY, SCRIPT], env=env, cwd=ROOT) if rc == 0: print(f"[round {round_}] 回填完成(exit 0),退出 watcher", flush=True) return print(f"[round {round_}] 回填 exit={rc}(断路器/失败),等 {PROBE_INTERVAL}s 再试", flush=True) else: print(f"[round {round_}] baostock 不可登(冷却中),等 {PROBE_INTERVAL}s", flush=True) time.sleep(PROBE_INTERVAL) if __name__ == "__main__": main()