fix(data): backfill硬化—重登on-error/登录超时/断路器/marker-resume+冷却续跑watcher

首跑5yr 673/5493后baostock连接死、定时重连没恢复、登录卡死=被限流。修四个硬伤:
- 重登on-error: except分支强制bs.logout+login建新socket再retry(原只sleep重试同死socket)
- 登录超时: SIGALRM给bs.login套30s闹钟,防永久挂起
- 断路器: 连续30只failed→save_progress+break+exit 2,不再假完成空跑2000票
- marker-resume: load_progress扫.baostock marker文件(非done JSON),failed无marker永远重试
- watcher: 每30min探测baostock,可登就续跑,冷却就等,exit 0则退出
9/9单测绿(Mac venv311 mock baostock)。
This commit is contained in:
2026-07-13 11:56:23 +08:00
parent b322f4e07b
commit 4b83452294
2 changed files with 197 additions and 23 deletions
@@ -0,0 +1,68 @@
"""5yr 15min 回填冷却后自动续跑 watcher。
baostock 被限流/冷却时登录会卡死或失败。本 watcher 每 30min 探测一次 baostock
可登就启动 backfill_15min_baostockmarker-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()