派猫猫旅行巡检自动化

一次 401 鉴权故障的完整排查复盘
关键词:自动化 · 反爬 · Cookie 鉴权 · Chrome DevTools Protocol · App-Bound Encryption · Edge
适合读者:做过"网页自动签到 / 定时脚本 / 浏览器自动化"的开发者;遇到过"脚本突然 401、手动又能访问"这类问题的同学。
见上一篇让 WorkBuddy 自己领积分:自动化「派猫猫旅行」绕过 JWT 误区、Cookie 鉴权与幂等状态机实战-腾讯云开发者社区-腾讯云 https://cloud.tencent.com/developer/article/2735446
一、背景:这个自动化在干什么
「派猫猫旅行」是某协作平台(www.workbuddy.cn)成长中心里的一个小游戏:每天可以把一只虚拟猫"派出去旅行",到达目的地后领取积分。积分虽小,但每天 4 次巡检、到点自动完成,是个典型的"懒人自动化"需求。
自动化架构分三层:
定时任务(每天 08:15 / 12:30 / 16:45 / 21:00)
└─ run_travel_cdp.py 包装器:探测浏览器、离屏拉起、401 自愈
├─ travel_auto_cdp.py 业务脚本:在已登录页面上下文里 fetch 接口
└─ refresh_cookies.py 凭据同步:把登录态 cookie 注入专用调试目录
鉴权模型是理解整个故障的钥匙:
· 接口靠 HttpOnly 的 Session Cookie(session / session_2 / tgw_l7_route)鉴权,没有 JWT;
· 平台前置了 APISIX + EdgeOne 风控,按 TLS / HTTP 指纹和浏览器上下文区分流量,拒绝一切非浏览器 HTTP 客户端(urllib、curl、甚至带 Chrome 指纹的 curl_cffi 全返回 401);
· 结论:请求必须由"真浏览器"在页面内代发,普通 HTTP 库直调接口这条路是被风控焊死的。
所以自动化没有走"直接拼请求",而是走"用 DevTools Protocol 在已登录页面里 fetch"——浏览器自己发,风控放行。

图 1 巡检自动化整体架构:请求由浏览器代发以通过风控
二、问题现象:9 月 15 日正常,9 月 16 日全 401
日期 / 时间 | 巡检结果 | 备注 |
|---|---|---|
09-15 12:30 | state=idle,SKIP(当日额度已满) | 最后一次正常:cookie 有效,无 401 |
09-16 08:15 | ERROR auth_expired / HTTP 401 | cookie 失效,未执行任何动作 |
09-16 12:30 | ERROR auth_expired / HTTP 401 | 同上 |
现象很干净:一夜之间,所有巡检在调 status 接口时就直接 401,拿不到任何 state,自然也就没法派出、领积分。手动在浏览器里打开同一页面却一切正常——典型的"脚本被风控认出来了,真人没事"。
三、根因分析:两件事叠在了一起
把日志往回翻,真正的转折点发生在 9 月 16 日中午前后:平台上线了 APISIX 前层的 EdgeOne 风控。
1. 直接死因(旧方案被封):原来最早的 travel_auto.py 是用 Python urllib 带上 cookie 直接调接口的。风控上线后,所有非浏览器客户端一律 401——这套方案从根上被废掉了。于是切到"浏览器代发"的 CDP 方案。
2. 导火索(Cookie 过期):CDP 方案依赖一个有效的登录态 Session。Session Cookie 有明确的过期时间,而 9 月 15 日 21:00 之后到 9 月 16 日 08:15 之前,这个 Session 自然过期了。CDP 脚本用的是一份"写死的旧 cookie",过期即 401。
一句话:风控让"直连"失效,过期让"旧 cookie"失效,两个因素叠加,脚本彻底哑火。

图 2 故障根因:风控上线与 Cookie 过期双因素叠加
修复方向因此分成两条:
· 让请求"像浏览器一样发"(解决风控);
· 让 cookie 能自动、持续地刷新(解决过期)。
第二条才是真正的难点——因为你不能轻易把浏览器里的登录态"读"出来给另一个无头实例用。
四、排查过程与方案演变
下面是按时间线梳理的几种方案,以及各自的成败与代价。

图 3 排查时间线:从故障发生到彻底恢复
方案 A:--remote-debugging-port=9222 挂上已登录的 Chrome(临时方案)
思路:既然需要"真浏览器 + 已登录",最直接的就是——手动以调试端口启动你那个已经登录了 www.workbuddy.cn 的 Chrome,然后自动化通过 CDP 连上去(端口默认 9222),复用这个现成的会话去发请求。
# 临时方案:保持这个 Chrome 一直开着,自动化去连它
chrome.exe --remote-debugging-port=9222 --user-data-dir="你的默认数据目录"
优点:改动最小,立刻能用——现成的登录态,零额外配置;复用真人会话,风控 100% 放行。
缺点(也是它只能当"临时方案"的原因):
· 必须长期保持这个 Chrome 开着。一旦关机、关浏览器,端口没了,下一轮巡检立刻 401——等于给自动化"拴"了个 7×24 的人工前置;
· Cookie 过期还得手动重新登录,它不自愈;
· 在现代 Chrome 上本身就不稳:Chrome 自 137 起就硬性拒绝在"默认 profile"上开 --remote-debugging-port,报错 DevTools remote debugging requires a non-default data directory。也就是说,想挂"你正在用的那个默认 profile",端口根本起不来。
结论:方案 A 能救急,但把"无人值守"退化成了"有人值守",且在新版 Chrome 上连"救急"都未必起得来。
方案 B:专用 CDP 调试目录 + 扩展导出 Cookie(理想架构,也是最终落地形态)
思路:不依赖"你开着浏览器",而是让脚本每次离屏自启一个独立调试目录的浏览器实例(非默认目录,所以端口能正常开),再把"默认 profile 里的登录态 cookie"喂给它。
喂 cookie 的关键一步是:从默认 profile 读出明文 cookie。因为新版 Chromium 的 Cookie 库被 App-Bound Encryption(ABE)加密——密钥和 profile 真实路径绑定,外部直接用 DPAPI 解会失败。唯一稳妥的"读明文"方式是在浏览器内部用扩展的 chrome.cookies API 读(扩展运行在浏览器进程内,不受 ABE 限制),再 POST 给本地一个小服务,最后用 CDP Storage.setCookies 注入到那个独立调试目录。
默认 profile(你登录着) 专用调试目录(脚本自启、离屏)
└ 本地扩展读明文 cookie ──POST──▶ 本地接收服务 ──CDP setCookies──▶ 注入并落盘
优点:完全无人值守——脚本自己拉起、自己关,不需要留任何浏览器在后台;cookie 可自动续期——每次导出都是最新的;扩展读取走浏览器内部,天然绕过 ABE 与风控。
缺点:这个架构命门在 --load-extension(命令行临时加载扩展)。而恰恰是这里,栽了大跟头。
结论:B 是正确方向,但它强依赖"Chrome 能加载本地扩展",而这一点在现代 Chrome 上被改掉了。
方案 C:直接 DPAPI 解密 Cookie 库(失败)
思路:跳过浏览器,直接读 Cookies 数据库,用 Local State 里的加密主密钥(DPAPI 解开)去解密每条 cookie 的 encrypted_value。
结果:主密钥能解开(DPAPI 前缀,说明不是 app-bound),但 cookie 值(v20,AES-GCM)解密时抛 InvalidTag——因为它实际用的是 app_bound_encrypted_key,密钥与"真实 profile 路径"绑定。脚本读库时即便指向同一物理文件,只要 Chrome 不以内核路径打开,ABE 就判定密钥不匹配。
结论:被 ABE 硬挡,现代浏览器上不可行。
方案 D:junction / 符号链接伪装 profile 路径(危险,已酿成事故)
思路(事后看是坑):既然"非默认目录才能开端口、但默认目录才有登录态",那用 mklink /J 把默认 profile 链接成一个"看着像非默认"的目录,让 Chrome 以非默认目录打开——端口能开,同时物理上还是同一份 Cookie 库。
结果:灾难。Chrome 计算出的 profile 路径是 junction 路径,与 ABE 绑定的真实路径不符,解密失败,按机制清空了整个 Cookies 库——实测把默认 profile 里所有网站的登录态全部清零,且无备份。
结论:致命且不可逆。ABE 的"路径不符即清空"机制极其凶险,junction / 符号链接 / 任何"非真实 --user-data-dir"伪装都绝对禁止。
⚠ 经验:任何试图用"假路径"骗过 Chromium 安全机制的方案,都会触发 ABE 的销毁逻辑,直接清零 Cookie。永远不要对默认 profile 做 junction / 软链。
方案 E(最终):改用 Microsoft Edge,彻底解决
关键发现:Google Chrome 自 137 起删除了 --load-extension 命令行开关,且原用于放开该限制的 --disable-features=DisableLoadExtensionCommandLineSwitch 变通在 142 起也失效了。而这条移除仅针对 Chrome 品牌版——同为 Chromium 内核的 Edge、Chromium、Chrome for Testing 不受影响,仍支持临时加载扩展。
于是根因豁然开朗:本机一直只有 Chrome,缺的从来不是 cookie、不是配置,而是"一个能加载扩展的浏览器"。
落地步骤:
1. 安装 Microsoft Edge(与 Chrome 同源、操作一致,无需迁移习惯);
2. 在 Edge 默认 profile 登录 www.workbuddy.cn;
3. 跑 refresh_cookies.py --browser edge --verify:本地扩展经 chrome.cookies 回传明文 cookie,注入到 Edge 专用调试目录 edge-cdp-profile 并落盘;
4. 巡检命令统一切到 run_travel_cdp.py --browser edge(每次离屏自启 Edge 专用实例,你开着 Edge 也不冲突,因为用的是独立数据目录)。
实测验证:
STATUS state=traveling location=古镇客栈 record_id=6294431 daily_limit=True reward=9
SKIP 旅行中 地点=古镇客栈 剩余=01:19:59
非 401,鉴权链路恢复;本轮因猫还在路上、当日额度已满而 SKIP——属于正常业务逻辑,非故障。
五、各方案对比一览
方案 | 无人值守 | 绕过风控 | 主要代价 / 风险 | 结论 |
|---|---|---|---|---|
A. 挂已登录 Chrome(端口常开) | 否,需长期开浏览器 | 是 | 占资源、易断;新版 Chrome 默认 profile 端口被拒 | 仅救急 |
B. 专用 CDP 目录 + 扩展导出 | 是 | 是 | 依赖 --load-extension | 理想架构(Edge 上落地) |
C. DPAPI 直接解密 | 是 | 是(理论) | 被 ABE 拦截,现代浏览器不可用 | 不可行 |
D. junction 伪装路径 | — | — | 清空全部 Cookie,不可逆 | 严禁 |
E. 改用 Edge | 是 | 是 | 多装一个浏览器 | 最终采用 |
六、最终方案与部署要点
落地形态(已稳定运行):
· 巡检 4 次:run_travel_cdp.py --browser edge —— 离屏自启 Edge 专用实例,浏览器代发请求;
· 续期 1 次:refresh_cookies.py --browser edge --verify(05:40)—— 导出新鲜 cookie 到调试目录;
· 健壮性加固:401 自愈关闭实例时,改用 CDP Browser.close + 按 --user-data-dir 精确匹配,不再按进程名全杀浏览器(旧实现会误伤你正在用的 Edge 窗口)。
唯一前提:扩展导出要求 Edge 完全未运行(同 profile 二次启动会被 Chromium 单例转发、扩展加载不上)。白天常开 Edge 时,自愈会被安全跳过(不打扰你),由 05:40 续期兜底。

图 4 最终方案:Edge 扩展导出 + 专用调试目录的完整数据流
七、经验与对外启示
1. "脚本突然 401、手动正常"几乎一定是反爬升级,不是代码 bug。先确认平台是否上了风控 / 指纹校验,再谈改代码。
2. Cookie 类自动化要把"取凭据"和"执行业务"解耦。前者(续期)和后者(巡检)分开调度,才能做到无人值守。
3. 浏览器品牌差异是真实陷阱。Chrome 品牌版自 137 删 --load-extension、142 起变通失效;Edge / Chromium / Chrome for Testing 不受影响。做"扩展取 cookie"类方案时,先确认目标浏览器是否还支持该开关。
4. App-Bound Encryption 极其危险。它的"路径不符即清空 Cookie"机制,意味着任何 junction / 符号链接 / 假 --user-data-dir 伪装都会直接清零登录态、且无备份。永远用真实 profile 路径启动浏览器。
5. "让浏览器自己发请求"是绕过指纹风控的通用解。当 urllib / curl / requests 全被 401 时,CDP 在页面上下文内 fetch 是最后的可靠通道。
附录:关键判断命令(备查)
# 1) 验证某浏览器是否仍支持 --load-extension
# 临时 profile 启动 + 开 CDP 端口,查 Target.getTargets 是否出现 chrome-extension://<id>/sw.js
# Edge 153 出现;Chrome 153 不出现(仅内置组件扩展)
# 2) 验证默认 profile 能否开调试端口
# Chrome 153 报错:DevTools remote debugging requires a non-default data directory
# 专用(非默认)目录则可正常监听 9222
# 3) 读 Cookie 库过期时间(Chrome/Edge 通用,无需解密)
# Cookies 库:<User Data>/Default/Network/Cookies
# expires_utc 为 1601-01-01 起微秒;>0 为到期时间,=0 为 SESSION
本文所涉排查均在本机隔离环境内完成,未对平台做任何异常访问;所有 cookie 读取均在本地浏览器进程内、经用户已授权的扩展完成。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。