我们维护一套多平台发布链路,账号分散在十几个内容平台。每天开跑前都要回答同一个问题:哪个平台还能发,哪个已经掉了登录。
最初的巡检脚本很朴素——读一遍浏览器的 cookie,哪个平台的关键 cookie 在,就认为登录有效。跑了一周,结果和实际差得很远:说「已登录」的,打开后台是登录页;说「掉线」的,标签页里正好好登着。误报最严重那天,十几个平台错了五个。
坑一:cookie 存在不等于会话有效
三个实测反例,都发生在 cookie 名齐全的情况下:
- 小红书:
web_session在,接口直接返回 401。 - 公众号:账号被踢回登录页,但
slave_sid、slave_user、data_bizuin一个都没被删。 - 掘金:只有
passport_csrf_token、s_v_web_id时,一堆 passport 前缀看起来像已登录,实际是未登录;真正的判据是sessionid、sessionid_ss。
原因不复杂:退出登录时清 cookie 是前端行为,服务端会话失效不一定回写浏览器。所以 cookie 只能当初筛,它能筛掉「肯定没登过」的平台、省下开页面的时间,但不能拿它下「已登录」的结论。
坑二:判据要分三层
现在固定三层,成本逐层递增:
- cookie 初筛(毫秒级,不开页面):关键 cookie 一个都不在,直接判未登录。
- 落地 URL(打开后台首页,看最终地址):被 302 到
signin、login、passport这类路径即未登录。 - 页面关键词(读 DOM 文本):首页出现「扫码」「重新登录」,或关键容器为空即未登录。
第二层能覆盖九成情况,第三层专治「SPA 空壳」——URL 没跳,页面却是白板,因为接口 401 后前端把 DOM 清空了。判据是 input 数为 0 且 body.innerText.length < 300;这时不要猜选择器,直接去问接口。平台自己有会话查询端点时,优先 fetch 它看 401,比读 DOM 稳。
坑三:备份键必须是三元组
巡检发现问题要能自动回灌,每个平台的登录态要落盘。第一版用 cookie 的 name 当键做差集,表现是:某些平台回灌后仍然掉登录,另一些回灌后多出一堆重复项。
原因是同名 cookie 可以并存——域名不同(a.com 与 .a.com)、路径不同(/ 与 /creator)都是不同的 cookie。只用 name 当键,后写的覆盖先写的,回灌时只补回其中一个。改成 (name, domain, path) 三元组后问题消失:
def key(c):
return (c["name"], c.get("domain", ""), c.get("path", "/"))
两条配套规则:备份用合并不覆盖,别整体覆写,否则一次异常态会把好备份冲掉;回灌只补「当前根本没有」的,不删不覆盖已有值——正在用的会话 cookie 被旧值覆盖,等于自己制造掉线。
坑四:浏览器级 CDP 只有 Storage.setCookies
Storage.setCookies、Storage.getCookies 在浏览器级连接上可用;而 Network.setCookie、Network.deleteCookies 属于页面会话,在 devtools/browser/<id> 这条 WebSocket 上调用会直接返回:
-32601 Protocol error: 'Network.setCookie' wasn't found
这个错很容易被 try/except 吞掉,最后表现为「脚本没报错,cookie 却没进去」。所以回灌的回执必须回读:把发出去的 cookie 逐个 (name, domain, path) 与 Storage.getCookies 的结果比一次,缺一个都算这次回灌失败。
坑五:cron 的解释器不是你以为的那个
巡检是定时跑的。系统 python3 是 3.6,项目 venv 是 3.11。cron 里写 python3 script.py,缺 websocket-client 直接崩,日志只有一行 ModuleNotFoundError,看板上就变成「全部平台未登录」。修法是脚本顶部自检,缺包就 os.execv 换到 venv 解释器,一行判断省掉一整类误报。
顺带一点:cron 的 PATH 常缺 /usr/local/bin,脚本里自己设一遍,别指望环境。
巡检结果长什么样
每次落一行 JSON:{"platform":"juejin","at":"2026-11-10T20:10:00+08:00","cookie":true,"url_ok":true,"dom_ok":true,"status":"active","cost_ms":2140}。
status 只有三档:active(三层全过)、expired(任一层明确判否)、unknown(超时或打不开)。unknown 不许当 active 用——超时被当登录有效,正是最初误报的一半来源。发布任务的前置条件就一句:status == "active" 才开跑;加上之后,失败类型从「发到一半发现没登录」变成「开工前就知道要扫码」。
小结
五个坑一个共性:判据给的往往是「看起来对」,不是「确实对」。
- cookie 存在 ≠ 会话有效
- URL 没跳 ≠ 页面加载成功
- 回灌返回成功 ≠ cookie 落进去了
- 巡检说已登录 ≠ 能发文
所以每条判断都要回读校验,并把「说不准」单独落一档。自动化的难点不在把动作做出来,而在不确定时敢说「我不知道」。