未登录会伪装成「未完成」:空响应、token not found 与 unknown 三种回法
无人值守脚本最难缠的不是接口复杂,是「登录态没了」这件事有三种长相。上午登录成功跑完一轮,中午再打同一批接口,实测到的三种回法如下。
一、三种回法
A. 某业务域:HTTP 200 + 完全空的 body(没有 content-type,不是登录页 HTML)
B. 成长中心:{"error_code": "HD.92010003", "error_msg": "token not found"}
同域还见过 HD.32110001 auth error、以及字面量 not login
C. 实践中心:GET /api/growth/tasks -> {"logged_in": false,
"tasks": [ {"today_status": "unknown", "git_bound": null, "orgs": null, ...} ]}A 最容易误判。因为状态码是 200,任何「按状态码判成败」的写法都会把它当成成功,然后卡在下一步解析 JSON:Expecting value: line 1 column 1,报错信息完全指不到登录态。我现在的做法是在解析层显式归一:
body = (r.text or "").strip()
try:
return r.json()
except Exception:
# 实测:会话失效时网关回 200 + 完全空的 body,没有 content-type
if not body:
return {"error_code": "NOT_LOGIN", "error_msg": "登录态失效(空响应)"}
if body[:1] == "<" or r.status_code in (401, 403):
return {"error_code": "NOT_LOGIN", "error_msg": "登录态失效(返回登录页)"}
raise把「空响应」和「返回登录页」都收敛成同一个错误码,上层才只用一种判断。这一步做完,日志里再也不会出现「Expecting value」这种看不出根因的行。
二、C 种回法为什么最危险
未登录的时候,任务列表接口照样返回任务对象,字段还在,只是状态变成 unknown、配置类字段(git_bound、orgs、repos)全为 null。如果脚本这么写:
if task["today_status"] != "done":
do_star() # 没做完就去做一遍那么未登录那天它会把撤不回的动作重做一次(Star 是不可撤销的),并且第二天还会再来一次。正确的顺序是先确认会话活着,再读状态:
if not payload.get("logged_in"):
return "未登录,本轮既不判状态也不执行动作"
if task.get("evaluator") == "external_check" and task.get("git_bound") is None:
return "绑定信息读不到,同样是未登录,不是任务没做"判据要挑那种「只有登录了才可能有值」的字段:登录标志、绑定账号名、积分是否是整数。beans=None 和 beans=1270 的差别,比任何错误码都直白。
三、B 种回法要区分令牌轮换与真失效
CSRF 类令牌会轮换,第一次 token not found 常常只是本地令牌旧了。我的处理是强制刷新一次再打第二次,只有第二次仍然报错才算会话死。如果不做这个重试,会把大量「一次刷新就能救」的情况误判成需要重新登录,脚本于是频繁走整轮登录流程,反而更容易撞上风控。
四、还有一类不是登录问题,别去重登
最难分辨的是「接口本身已经下线」。有一阵某个每日福利接口回 4000 invalid channel, group not found,后来同一个口改成回 200 + 空 body,看起来跟登录失效一模一样。判断方法不是换个更仔细的登录,而是用刚重登成功、令牌齐全的新会话再打一次:如果新会话也是同样回法,那就不是 Cookie 的事。我这次就是这样确认的——新登录态下余额接口仍然回空、领取仍然回同样错误,于是把这条从「每天报登录失效、并顺手触发整轮重登」改成「如实播报厂商侧已下线」。省掉一次无效重登,也就少一次被风控盯上的机会。
四种回法收敛成一句话:先证明会话活着,再解释状态;证明不了就什么都不做。未登录和未完成长得越像的接口,越要把「读状态」和「执行动作」之间插一道登录校验。
- 点赞
- 收藏
- 关注作者
评论(0)