fix(data): bs_eod启动登录重试——治08-18型瞬断整天缺口——main()登录失败不再当场exit2,改login_with_retry串行重试(1+2轮,间隔120s/300s,无并发不触封禁红线;用户拍板2026-08-20:优先自身健壮性,实测无频繁重连/超48k配额);瞬时闪断(10054/10002007)秒~分钟级恢复,2/5分钟两轮覆盖;仍失败才exit2,次日18:05例行LOOKBACK=7自愈(自愈机制本就完好,重试只是把'当晚瞬断'消掉);+4测试(首试成功不等待/第2次恢复/3次全败False/集成exit2契约保持);data_platform 116绿 [vps]
This commit is contained in:
@@ -82,6 +82,27 @@ def relogin():
|
||||
return login_once()
|
||||
|
||||
|
||||
# 启动登录重试间隔(秒): 1 + 2 轮重试, 共 3 次尝试。
|
||||
# 2026-08-20 用户拍板: 优先自身健壮性(实测无频繁重连/超 48k 配额问题, 封禁风险低)。
|
||||
# 08-18 实录: 18:05:06 登录被 10054 掐断即 graceful exit, 整天缺口裸奔到次日;
|
||||
# 瞬时闪断秒~分钟级恢复, 2/5 分钟两轮串行重试(无并发)覆盖; 仍失败(真服务故障)
|
||||
# 才放弃 exit 2, 次日 LOOKBACK=7 自愈。
|
||||
LOGIN_RETRY_DELAYS = (120, 300)
|
||||
|
||||
|
||||
def login_with_retry():
|
||||
"""启动登录: 失败按 LOGIN_RETRY_DELAYS 串行重试(治瞬时闪断), 全败返 False。"""
|
||||
if login_once():
|
||||
return True
|
||||
total = len(LOGIN_RETRY_DELAYS) + 1
|
||||
for i, delay in enumerate(LOGIN_RETRY_DELAYS, start=2):
|
||||
log.warning("登录失败, 第 %d/%d 次重试(等待 %ds)", i, total, delay)
|
||||
time.sleep(delay)
|
||||
if login_once():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def fetch_all_stocks():
|
||||
"""query_stock_basic() 无参 -> 全 A 含退市 (type=1)。返回 [(code, 'sh'/'sz')]。"""
|
||||
global QUERY_COUNT
|
||||
@@ -281,8 +302,8 @@ def main():
|
||||
start = (today - dt.timedelta(days=LOOKBACK)).strftime("%Y-%m-%d")
|
||||
log.info("bs_eod start window=%s~%s LOOKBACK=%d limit=%s", start, end, LOOKBACK, args.limit or "无")
|
||||
|
||||
if not login_once():
|
||||
log.error("[SKIP] baostock 黑名单/冷却, graceful exit 2")
|
||||
if not login_with_retry():
|
||||
log.error("[SKIP] 重试后仍登录失败(baostock 冷却/服务故障), graceful exit 2")
|
||||
sys.exit(2)
|
||||
|
||||
try:
|
||||
|
||||
@@ -451,3 +451,60 @@ def test_periodic_relogin_disabled_when_relogin_every_huge(isolated_main_env, mo
|
||||
with pytest.raises(SystemExit):
|
||||
bs_eod.main()
|
||||
assert m_rel.call_count == 0 # 5 < 10000, 没到周期
|
||||
|
||||
|
||||
# ---------- ④ 启动登录重试 (2026-08-20: 治 08-18 18:05 登录秒败整天缺口) ----------
|
||||
|
||||
def _login_result(error_code="0"):
|
||||
"""构造 baostock login 返回对象风格 (error_code/error_msg)."""
|
||||
m = MagicMock()
|
||||
m.error_code = error_code
|
||||
m.error_msg = "" if error_code == "0" else "网络接收错误"
|
||||
return m
|
||||
|
||||
|
||||
def test_login_with_retry_first_try_ok(monkeypatch):
|
||||
"""首次登录成功 → 不 sleep 不重试."""
|
||||
mock_bs = MagicMock()
|
||||
mock_bs.login.return_value = _login_result("0")
|
||||
sleeps: list = []
|
||||
monkeypatch.setattr(bs_eod, "bs", mock_bs)
|
||||
monkeypatch.setattr(bs_eod.time, "sleep", lambda s: sleeps.append(s))
|
||||
assert bs_eod.login_with_retry() is True
|
||||
assert mock_bs.login.call_count == 1
|
||||
assert sleeps == []
|
||||
|
||||
|
||||
def test_login_with_retry_recovers_on_second_attempt(monkeypatch):
|
||||
"""瞬时闪断(08-18 实录 10054): 第 1 次失败, 第 2 次成功 → 重试覆盖, 不放弃."""
|
||||
mock_bs = MagicMock()
|
||||
mock_bs.login.side_effect = [_login_result("10002007"), _login_result("0")]
|
||||
sleeps: list = []
|
||||
monkeypatch.setattr(bs_eod, "bs", mock_bs)
|
||||
monkeypatch.setattr(bs_eod.time, "sleep", lambda s: sleeps.append(s))
|
||||
assert bs_eod.login_with_retry() is True
|
||||
assert mock_bs.login.call_count == 2
|
||||
assert sleeps == [120] # 只等了第一轮间隔
|
||||
|
||||
|
||||
def test_login_with_retry_gives_up_after_all_attempts(monkeypatch):
|
||||
"""持续失败(真冷却/服务故障): 3 次全败 → 返 False (main 转 exit 2, 次日自愈)."""
|
||||
mock_bs = MagicMock()
|
||||
mock_bs.login.return_value = _login_result("10002007")
|
||||
sleeps: list = []
|
||||
monkeypatch.setattr(bs_eod, "bs", mock_bs)
|
||||
monkeypatch.setattr(bs_eod.time, "sleep", lambda s: sleeps.append(s))
|
||||
assert bs_eod.login_with_retry() is False
|
||||
assert mock_bs.login.call_count == 3 # 1 + 2 重试
|
||||
assert sleeps == [120, 300]
|
||||
|
||||
|
||||
def test_main_exits_2_when_all_login_retries_fail(isolated_main_env, monkeypatch):
|
||||
"""集成: 全部重试失败 → 维持原 graceful exit 2 契约 (schtask 结果码可见)."""
|
||||
mock_bs = MagicMock()
|
||||
mock_bs.login.return_value = _login_result("10002007")
|
||||
monkeypatch.setattr(bs_eod, "bs", mock_bs)
|
||||
monkeypatch.setattr(bs_eod.time, "sleep", lambda s: None)
|
||||
with pytest.raises(SystemExit) as ei:
|
||||
bs_eod.main()
|
||||
assert ei.value.code == 2
|
||||
|
||||
Reference in New Issue
Block a user