feat(cmcc): 冷却自愈多轮重放 + 日志步骤中文化
- 换会话冷却自愈:单轮 15 败静默 600s 后自动重开(试数归零),最多 3 轮 (覆盖 ~36 分钟,穿出冷却期;实测静默 ≥10min 恢复命中) 号2 19:57 的 88B×15 就是素材冷却期,旧逻辑静默一次就放弃 → 现在自愈 - 日志前缀 [A][B][C][D] → [①换会话][②开门][③换票][④拿钥匙](用户可读)
This commit is contained in:
@@ -122,11 +122,14 @@ SSO = "https://wx.10086.cn/qwhdsso"
|
||||
UTC8 = timezone(timedelta(hours=8)) # ★ 显式 UTC+8:青龙容器时区可能不是 +8
|
||||
STRIP_HEADERS = ("content-length", "accept-encoding", "host", "connection")
|
||||
|
||||
REPLAY_MAX = 30 # 命中即停:实测中位 1~9 发;30 发封顶,由错峰补签 cron 兜底
|
||||
REPLAY_MAX = 30 # 单轮命中即停:实测中位 1~9 发
|
||||
REPLAY_GAP = 5.0 # 3~5s 快节拍(慢节拍推向 236B 通道)
|
||||
WALL_LIMIT = 6 * 60 # [A] 墙钟上限(含长静默),防止极端情况拖死任务
|
||||
BACKOFF_AFTER = 15 # 连续 15 败 → 一次 10min 静默(防风暴冷却,只插一次)
|
||||
WALL_LIMIT = 6 * 60 # 单轮 [A] 墙钟上限(含长静默)
|
||||
BACKOFF_AFTER = 15 # 连续 15 败 → 静默(防风暴冷却)
|
||||
BACKOFF_SECS = 600
|
||||
COOLDOWN_ROUNDS = 3 # ★ 冷却自愈:整轮未命中时,静默后重开一轮,最多 3 轮
|
||||
# (素材在冷却期时 88B×15 + 600s 静默 ≈ 12min/轮;
|
||||
# 实测静默 ≥10 分钟恢复命中,3 轮覆盖 ~36 分钟,足够穿出冷却期)
|
||||
THROTTLE = 1.2 # 业务请求间隔
|
||||
|
||||
MASK = lambda s: (s[:3] + "****" + s[-4:]) if s and len(s) >= 7 else (s or "?")
|
||||
@@ -302,19 +305,23 @@ def classify(resp):
|
||||
|
||||
|
||||
def replay_auto_login(seed, stats):
|
||||
"""重放至 492 命中,命中即停。无状态 requests.post ——
|
||||
★ Session 的 prepare_cookies 会用 jar 无条件覆盖手工 Cookie 头,素材 cookie 一被污染就 64B。"""
|
||||
"""重放至 492 命中,命中即停。无状态 post(Session 的 jar 会污染素材 Cookie)。
|
||||
★ 冷却自愈:单轮 15 败 → 静默 600s → 重开一轮(试数归零),最多 COOLDOWN_ROUNDS 轮。
|
||||
素材在冷却期(88B)时靠这个穿出去;3 轮全 88B 才认输(诊断文案会说「稍后重跑」。)"""
|
||||
total_tried = 0
|
||||
for round_no in range(1, COOLDOWN_ROUNDS + 1):
|
||||
start = time.time()
|
||||
tried = 0
|
||||
backed = False
|
||||
while tried < REPLAY_MAX and time.time() - start < WALL_LIMIT:
|
||||
tried += 1
|
||||
total_tried += 1
|
||||
try:
|
||||
r = post_seed(seed["url"], seed["body"].encode("utf-8"),
|
||||
seed["headers"], timeout=30)
|
||||
except Exception as e:
|
||||
stats["net_error"] = stats.get("net_error", 0) + 1
|
||||
log("[A] #%d 网络错误 %r" % (tried, e))
|
||||
log("[换会话] 第%d轮 #%d 网络错误 %r" % (round_no, tried, e))
|
||||
time.sleep(REPLAY_GAP)
|
||||
continue
|
||||
form = classify(r)
|
||||
@@ -326,17 +333,24 @@ def replay_auto_login(seed, stats):
|
||||
raw_list = [r.headers.get("set-cookie", "")]
|
||||
blob = "; ".join(x.strip() for x in raw_list if x.strip())
|
||||
sess = blob.split("; Secure")[0].strip() # ★ 原始全值(含 Comment/Path 碎片)
|
||||
log("[A] 第 %d 发命中(统计=%s)" % (tried, stats))
|
||||
return sess, tried
|
||||
log("[换会话] 第%d轮 第%d发命中(统计=%s)" % (round_no, tried, stats))
|
||||
return sess, total_tried
|
||||
if tried % 5 == 0 or form.startswith(("403", "150")):
|
||||
log("[A] 第 %d 发 %s" % (tried, form))
|
||||
log("[换会话] 第%d轮 第%d发 %s" % (round_no, tried, form))
|
||||
if tried >= BACKOFF_AFTER and not backed:
|
||||
log("[A] 连续 %d 败 → 静默 %ds 防风暴冷却" % (tried, BACKOFF_SECS))
|
||||
time.sleep(BACKOFF_SECS)
|
||||
backed = True
|
||||
if round_no < COOLDOWN_ROUNDS:
|
||||
log("[换会话] 连续 %d 败 → 疑似冷却期,静默 %ds 后自动重开一轮" % (tried, BACKOFF_SECS))
|
||||
time.sleep(BACKOFF_SECS)
|
||||
break # break 出 while → 下一轮(tried 归零)
|
||||
else:
|
||||
log("[换会话] 末轮连续 %d 败,不再静默" % tried)
|
||||
else:
|
||||
time.sleep(REPLAY_GAP)
|
||||
return None, tried
|
||||
# while 正常跑满 REPLAY_MAX 也进入下一轮(若还有轮次)
|
||||
if round_no < COOLDOWN_ROUNDS:
|
||||
log("[换会话] 第%d轮 %d 发未命中,重开新一轮" % (round_no, tried))
|
||||
return None, total_tried
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- [B]~[D] 换票
|
||||
@@ -350,11 +364,11 @@ def mint_token(sess, stats):
|
||||
r = s.get(ACT_URL, timeout=30, allow_redirects=True)
|
||||
m = re.search(r"sid=([A-Za-z0-9]+)", r.text)
|
||||
except Exception as e:
|
||||
log("[B] 活动页请求异常 %r" % e)
|
||||
log("[②开门] 活动页请求异常 %r" % e)
|
||||
time.sleep(2)
|
||||
continue
|
||||
if not m:
|
||||
log("[B] 没取到 sid")
|
||||
log("[②开门] 没取到 sid")
|
||||
time.sleep(2)
|
||||
continue
|
||||
sid = m.group(1)
|
||||
@@ -365,22 +379,22 @@ def mint_token(sess, stats):
|
||||
"referer": r.url, "origin": "https://wx.10086.cn"})
|
||||
j = r2.json()
|
||||
except Exception as e:
|
||||
log("[C] 异常 %r" % e)
|
||||
log("[③换票] 异常 %r" % e)
|
||||
time.sleep(2)
|
||||
continue
|
||||
url = (j.get("data") or {}).get("url", "")
|
||||
log("[C] %s %s" % (j.get("code"), j.get("msg", "")))
|
||||
log("[③换票] %s %s" % (j.get("code"), j.get("msg", "")))
|
||||
if "404" in url or not url: # ★ 判据是 url 含 404,code 仍是 SUCCESS
|
||||
time.sleep(2)
|
||||
continue
|
||||
try:
|
||||
s.get(url, timeout=30, allow_redirects=True)
|
||||
except Exception as e:
|
||||
log("[D] 异常 %r" % e)
|
||||
log("[④拿钥匙] 异常 %r" % e)
|
||||
continue
|
||||
tok = next((c.value for c in s.cookies if c.name == "QWHD_SESSION_TOKEN"), None)
|
||||
if tok:
|
||||
log("[D] token=%s…%s" % (tok[:6], tok[-4:]))
|
||||
log("[④拿钥匙] token=%s…%s" % (tok[:6], tok[-4:]))
|
||||
return tok
|
||||
stats["mint_fail"] = stats.get("mint_fail", 0) + 1
|
||||
return None
|
||||
@@ -486,13 +500,13 @@ def run_one_account(name, payload, stats):
|
||||
else:
|
||||
sess, tried = replay_auto_login(payload, stats)
|
||||
if not sess:
|
||||
acct["detail"] = ("autoLogin %d 发未命中(%s)。诊断:64B多→素材/会话死,重抓;"
|
||||
acct["detail"] = ("换会话 %d 发未命中(%s)。诊断:64B多→素材/会话死,重抓;"
|
||||
"88B多→素材活着未中,1小时后重跑;236B→通道异常,快节拍重试;"
|
||||
"403/150→cURL 解析损坏" % (tried, stats))
|
||||
return 2, acct
|
||||
tok = mint_token(sess, stats)
|
||||
if not tok:
|
||||
acct["detail"] = "换票失败([C] 3 次重建 sid 未成)"
|
||||
acct["detail"] = "换票失败(③换票 3 次重建 sid 未成)"
|
||||
return 3, acct
|
||||
|
||||
m = Mark(tok)
|
||||
@@ -516,7 +530,7 @@ def run_one_account(name, payload, stats):
|
||||
if not st["signed"]:
|
||||
mk = m.mark()
|
||||
if mk.get("_token_invalid"):
|
||||
acct["detail"] = "domark 时 token 失效"
|
||||
acct["detail"] = "④拿钥匙后签到时 token 失效"
|
||||
return 3, acct
|
||||
log("[{}] 签到 code={} status={} msg={}".format(name, mk.get("code"), mk.get("status"), mk.get("msg")))
|
||||
st2 = m.state()
|
||||
|
||||
Reference in New Issue
Block a user