派猫猫旅行巡检自动化-
从建任务到成功落地的完整复盘
关键词:定时自动化 · Bearer 鉴权 · Cookie 鉴权 · Chrome DevTools Protocol · App-Bound Encryption · Keycloak JWT · 运行期内存取证
时间跨度:2026-09-01 → 2026-09-29 | 适用读者:做过网页签到、定时脚本、浏览器自动化的开发者

封面:WorkBuddy 成长计划 · 派猫猫旅行入口
一句话结论把"每天手动点网页领积分"这件小事做成无人值守的定时任务,中途撞上 401 鉴权故障;排查一圈后确认真因是脚本缺 Authorization: Bearer 凭据,而不是平台风控;最终用「令牌直连 + 进程内存取证」把链路做稳,并已实测积分真实到账。 |
|---|
目录
一、建任务:背景与目标
二、故障:过程中出现的具体问题
三、排查思路与解决方案(含经验与警示)
四、成果:当前已成功实现
附录:关键判断命令(备查)
一、建任务:背景与目标
1.1 需求从哪来
平台的成长中心里有一个叫「派猫猫旅行」的小玩法:把虚拟宠物派出去旅行,到达目的地后回来能领积分。积分本身不大,但"每天要卡着时间点去点三次"(派出 → 查看是否到达 → 领取)实在琐碎,属于典型的"懒人自动化"需求。

图 A1 任务入口:客户端头像 → 成长计划 → 派猫猫旅行
1.2 玩法机制:它不是一个静态按钮
先要把玩法摸清楚,才能定自动化的状态机。据实拍页面,这个玩法的三条关键规则是:
· 可选地点:咖啡馆、古镇客栈、健身房、商场店铺等多个地点,每次随机一个;
· 随机 1~4 小时:从"确定派出"到"回来"的时长不固定,这是自动化的核心难点——不能派出后立刻领取;
· 回来可领奖励:旅行结束后宠物带回见闻和奖励(积分),需要再次进入页面领取。

图 A2 玩法机制:可选地点、随机 1~4 小时、回来可领奖励
1.3 目标定义
目标 | 验收标准 |
|---|---|
无人值守 | 每天 4 次巡检(08:15 / 12:30 / 16:45 / 21:00)+ 1 次凭据续期(05:40),无人工介入 |
幂等不漏不重 | 状态机驱动:arrived → 领取积分;traveling → 跳过,不重复派出;daily_limit_reached → 跳过;idle → 随机派出一个地点 |
不碰 GUI | 优先纯 HTTP 直连,不起浏览器、不截图 OCR、不模拟点击 |
可追溯 | 每轮结果写入日志与状态快照,异常按固定口径报告;不自动重试、不代替登录 |
1.4 技术选型:三层降级架构
定时任务(05:40 续期 / 08:15 / 12:30 / 16:45 / 21:00)
└─ run_travel.py 统一入口:令牌优先,失败自动降级
├─ travel_auto_token.py 主通道:取 Bearer 令牌,urllib 直连(无浏览器 / 无 cookie)
├─ travel_auto_web.py 备用:cookie + Chrome UA 纯 HTTP 直连
└─ run_travel_cdp.py 兜底:离屏 Edge,页面内 fetch 代发
接口路径统一为 https://www.workbuddy.cn/activity/growth/buddy/travel/{status,depart,claim}(脚本内不带 /v2/ 前缀)。

图 1 现行鉴权模型与三层降级架构
二、故障:过程中出现的具体问题
从 09-01 到 09-29,链路一共被打断五次,问题逐层递进。
2.1 第一道坎(09-01):浏览器自动化通道冷启动即失效
· 现象:首跑即 state=error。自动化用专用 chrome-profile 冷启动打开成长中心,页面被重定向到 Keycloak 微信扫码登录页,拿不到任何业务状态。
· 排查:cookie 加密前缀是 v10(非 v11,说明没有 app-bound 加密封装层),自动化也能读到明文 KEYCLOAK_SESSION="copilot/68d9b511-…"——即"解密没问题"。
· 真因:这份 session 在服务端已经过期(其来源浏览器被关闭后会话失活)。凭据物理存在 ≠ 服务端认账。
· 影响:当日余下 3 回在无人介入前必然全错。
2.2 第二阶段(09-02 ~ 09-15):cookie 直调跑通,但凭据本身很脆
对策是彻底绕开浏览器:读 wb_cookies.txt 的会话 cookie,配 Chrome UA 纯 HTTP 直调三个接口。09-02 首跑即成功,方案看起来找到了。这段跑了 14 天,期间共 14 次领取成功、合计 +56 分,还有 2 次判断 idle 主动派出。但隐患一直在:
· 09-08 12:30 ~ 09-09 07:17:连续多轮 ERROR auth_expired HTTP 401,cookie 中途失效;07:23 重新拷贝 cookie 后恢复。
也就是说,凭据的生命周期完全不受控——它什么时候失效、失效后脚本如何自愈,当时都没有答案。
2.3 第三阶段(09-16 ~ 09-17):全链路 401
· 09-16 08:15 起,巡检在调 status 接口这一步就直接 ERROR auth_expired HTTP 401,连 state 都拿不到,自然无从派出与领取;
· 09-17 自 07:56 起连续 4 回报同一 401,排除偶发;
· 现象很"干净"但也最迷惑人:脚本侧全 401,手动在浏览器里打开同一页面却一切正常——第一反应就像"被风控认出来了,真人没事"。
这个误判是后面所有弯路的起点,详见第三部分。
2.4 第四阶段(09-25):兜底通道也失效
切到 Edge CDP 通道后,包装器自动跑了 refresh_cookies.py(用扩展导出 10 条明文 cookie 注入 CDP profile,落盘校验 workbuddy cookie=11 条),但重跑仍然 401。
根因:默认 Edge profile 里 www.workbuddy.cn 的会话在服务端已失效——cookie 物理存在但服务端拒绝,refresh 只是复制了一份"死 cookie",无法自愈。
2.5 第五阶段(09-28 ~ 09-29):令牌通道被"加密信封"打断
已经在走令牌主通道了,09-29 08:15 那一轮却报 no_credentials:
· 桌面端把 auth.accessToken 从明文字符串改成了字段级信封加密:{"$wbEncrypted":1,"envelope":"…"}(AES-256-GCM,主密钥运行时注入、磁盘上不留 keyblob);
· 原脚本仍按明文字符串去读,读到的是 dict → 直接判定"凭据缺失";
· 该轮自动降级到备用 cookie 通道才跑通(traveling,幂等跳过)。
2.6 问题清单
时间 | 现象 | 错误 | 当时判断 | 实际性质 |
|---|---|---|---|---|
09-01 | 冷启动被重定向到扫码登录页 | state=error | 浏览器 profile 坏了 | 凭据(session)服务端已过期 |
09-08/09-09 | 多轮 401,重拷 cookie 后恢复 | HTTP 401 | 偶发抖动 | 凭据生命周期不受控 |
09-16 ~ 09-17 | status 接口即 401,连读 4 回 | auth_expired HTTP 401 | 误判为风控指纹封锁 | 脚本缺 Bearer 凭据 |
09-25 | 兜底通道 refresh 后仍 401 | HTTP 401 | 通道选型有误 | 兜底 profile 会话服务端失效 |
09-29 | 令牌通道报凭据缺失 | no_credentials | 客户端未登录 | 落盘令牌改为加密信封 |

图 2 故障根因:缺 Bearer 凭据为主因,原"风控 + Cookie 过期"降级为误判
三、排查思路与解决方案
3.1 第一轮思路(误判):认定"平台上了风控指纹封锁"
被"脚本 401、手动正常"带偏后,方向定成了"如何让请求看起来像真浏览器",于是走了 A—E 五条弯路。

图 3 排查时间线(09-15 故障爆发 → 09-26 根因更正 → 09-29 修复定稿)
方案 A:--remote-debugging-port=9222 挂上已登录的 Chrome(临时救急)
# 保持这个 Chrome 一直开着,自动化通过 CDP 连上去复用会话
chrome.exe --remote-debugging-port=9222 --user-data-dir="你的默认数据目录"
改动最小、立刻能用,但把"无人值守"退化成"有人值守"(关机即 401);且现代 Chrome 自 137 起硬性拒绝在默认 profile 上开调试端口(报错 DevTools remote debugging requires a non-default data directory),连救急都不稳。
方案 B:专用 CDP 调试目录 + 扩展导出 Cookie
脚本每次离屏自启一个独立调试目录的浏览器(非默认目录,端口能开),再把默认 profile 的登录态 cookie 喂给它。读明文 cookie 的稳妥做法是在浏览器内部用扩展的 chrome.cookies API 读、POST 给本地服务、最后 CDP Storage.setCookies 注入。
默认 profile(你登录着) 专用调试目录(脚本自启、离屏)
└ 本地扩展读明文 cookie ──POST──▶ 本地接收服务 ──CDP setCookies──▶ 注入并落盘
方向不错,但命门是 --load-extension——Chrome 品牌版已删除该开关(见方案 E)。
方案 C:直接 DPAPI 解密 Cookie 库(失败)
跳过浏览器直接读 Cookies 数据库解密。主密钥能解,但 cookie 值(v20,AES-GCM)解密抛 InvalidTag:实际用的是 app_bound_encrypted_key,密钥与"真实 profile 路径"绑定,脚本即便指向同一物理文件,只要浏览器不以内核路径打开,ABE 就判定密钥不匹配。
方案 D:junction / 符号链接伪装 profile 路径(危险,已酿事故)
想用 mklink /J 把默认 profile 链接成"看着像非默认"的目录。结果灾难:浏览器算出的 profile 路径是 junction 路径,与 ABE 绑定的真实路径不符,解密失败后按机制清空了整个 Cookies 库——实测默认 profile 里所有网站的登录态全部清零,且无 Cookies.bak 可恢复。
【警示】这条与技术误判无关,绝对成立任何试图用"假路径"骗过 Chromium 安全机制的方案,都会触发 ABE 的销毁逻辑。永远不要对浏览器默认 profile 做 junction / 软链 / 假 --user-data-dir。 |
|---|
方案 E:改用 Microsoft Edge(兜底通道)
关键发现:Google Chrome 自 137 起删除了 --load-extension,--disable-features=DisableLoadExtensionCommandLineSwitch 的变通自 142 起失效;该移除仅针对 Chrome 品牌版,Edge / Chromium / Chrome for Testing 不受影响。装 Edge 后 CDP 通道确实能跑,但它后来在 09-25 因默认 profile 会话服务端失效而失效(见 2.4)。
3.2 第二轮思路(真因定位,09-26):抓包比对,问题是"缺凭据"不是"被风控"
停下来做最基本的一件事——看响应到底是什么:
curl -i https://www.workbuddy.cn/activity/growth/buddy/travel/status
# → 401 Authorization Required(APISIX/3.9.1),只是"缺凭据"
curl -i -H "Authorization: Bearer <token>" <同一 URL>
# → 200 OK
· 同一 URL、同一网络、同一 UA:无凭据 → 401;带 Bearer → 200;
· 所谓"唯独浏览器能 200",只是因为只有浏览器里带着登录态(含令牌),与 TLS / HTTP2 指纹无关。
真因确认:接口靠 Authorization: Bearer <accessToken>(Keycloak OIDC JWT)鉴权,不是 HttpOnly Session Cookie。09-16 起的 401 是脚本缺 Bearer 凭据,不是平台风控升级。
修复方向因此收敛成一条:让脚本自己拿到并带上有效 Bearer 令牌。"绕过风控""刷新 Cookie"全是南辕北辙。
落地为方案 F:令牌直连——令牌由桌面端落盘在 %LOCALAPPDATA%\CodeBuddyExtension\Data\Public\auth\workbuddy-desktop.info 的 auth.accessToken(expiresIn≈55 天,桌面端运行时自动刷新,同含 auth.domain、account.uid),脚本读取后以 Authorization: Bearer <token> 直连,无浏览器、无 CDP、无 cookie。
3.3 第三轮思路(09-28 ~ 09-29 加固):从"静态读文件"转向"运行期取内存"
方案 F 只用了一天就被打断:09-28 起桌面端把 accessToken 改成信封加密落盘,静态读不到明文。思路上做了一次转弯:
关键突破密钥拿不到,但客户端发 HTTPS 请求时手里必然有明文。所以不去解密信封,直接从进程运行期内存里取。 |
|---|
方案 G:进程内存取证(现行主通道)
· 手段:只读扫描 WorkBuddy.exe / node.exe 的可读内存,抓取活体明文 JWT。不注入、不修改、不重启客户端,因此不会中断会话;
· 明文长度可反推:AES-GCM 无填充 → 密文长度 = 明文长度,据此得知 accessToken 1323 B、refreshToken 700 B;再用 nickname(2 汉字 = 6 B)、phoneNumber(11 B)交叉校验,这个启发式可靠;
· 多候选校验:内存里可能抓到多个候选串,逐个调 status 接口验证,第一个返回 200 且 code=0 的才采用;同一令牌同时出现在 PID 11324 / 22200 / 24240 三个进程中,是"真令牌"的强特征;
· 落盘与兼容:命中后写入临时文件供本轮使用、用完即清;若磁盘令牌仍是明文(旧版),直接走原逻辑,向后兼容;
· 部署:新增 travel_auto/fetch_token_from_memory.py;travel_auto_token.py 增加 load_token_from_memory() 并把取令牌改为磁盘 → 内存两级,状态快照新增 token_src 字段用于诊断。
这不是孤例,而是客户端层面的系统性变更:同一根因也命中另一条自动化(Buddy 加油站每日签到)——原脚本按明文字符串拼接 accessToken,遇到信封后直接 TypeError 崩溃;修复同样改用运行期内存取证(新建 checkin_headless_token.py,不动原脚本、不动登录态),实测当日领取成功。两条自动化因此共用同一套"信封不可解 → 内存取明文"的兜底范式。
3.4 方案对比一览
方案 | 无人值守 | 是否解决真因(缺 Bearer) | 主要代价 / 风险 | 结论 |
|---|---|---|---|---|
A. 挂已登录 Chrome(端口常开) | 否,需长期开浏览器 | 否(靠浏览器带令牌) | 占资源、易断;新版 Chrome 默认 profile 端口被拒 | 仅救急 |
B. 专用 CDP 目录 + 扩展导出 | 是 | 否(仍在绕 cookie) | 依赖 --load-extension | 误判下的探索 |
C. DPAPI 直接解密 | 是(理论) | 否 | 被 ABE 拦截(InvalidTag) | 不可行 |
D. junction 伪装路径 | — | — | 清空全部 Cookie,不可逆 | 严禁 |
E. 改用 Edge(CDP 兜底) | 是 | 被动(浏览器带令牌) | 依赖 Edge 登录态,未登录即失效 | 保留为第三级兜底 |
F. Bearer 令牌直连(读磁盘明文令牌) | 是 | 是 | 落盘改加密信封后失效 | 09-26~09-28 主通道 |
G. 令牌内存取证(磁盘 → 进程内存两级) | 是 | 是 | 需桌面端常驻;扫描约 25~35 秒 | 现行主通道 |
3.5 现行链路
run_travel.py(统一入口,三级降级)
1) travel_auto_token.py → 取令牌:磁盘明文 → 进程内存取证(命中即用)
2) travel_auto_web.py → 备用:cookie + Chrome UA 纯 HTTP
3) run_travel_cdp.py → 兜底:离屏 Edge,页面内 fetch
前置条件:桌面端须常驻运行(内存里才有明文令牌)。客户端未运行时,自动降级到备用 / 兜底通道,不影响巡检本身。
安全与健壮性边界:
· 内存读取只读、不注入、不修改、不重启,不导出任何内容到外部;
· 401 自愈关闭浏览器实例时,改用 CDP Browser.close + 按 --user-data-dir 精确匹配,不再按进程名全杀浏览器(避免误伤正在使用的窗口)。

图 4 现行方案:令牌直连优先 + 三级降级兜底
3.6 经验与警示
1. "脚本 401、手动正常"先分清是「缺凭据」还是「真风控」。这次把"非浏览器客户端 401"直接归为风控,绕了一大圈。判据很简单:带头带对凭据还是 401 才是风控,带上就 200 就是缺凭据。先看响应体再下结论。
2. "取凭据"和"执行业务"要解耦。续期与巡检分开调度,是无人值守能成立的前提。
3. 凭据存储格式会变,别把读取方式写死。明文 → 信封加密只用了一天就发生了;健壮的取法应有多级来源(磁盘 → 内存 → cookie),并记录来源字段便于事后定位。
4. ABE 极其危险(与本次误判无关,绝对成立)。"路径不符即清空 Cookie"的机制意味着任何伪装路径都会清零登录态且无备份。永远用真实 profile 路径启动浏览器。
5. 浏览器是兜底,不是首选。当带凭据的纯 HTTP 就能 200 时,把浏览器留给真正需要它的场景——更轻、更稳。
四、成果:当前已成功实现
4.1 闭环验证:不是"页面显示成功",而是账户真的到账
先手动跑一次、再自动跑一次,两轮都拿到真实积分——两次都是"咖啡馆"地点,领取 +6 与 +5:
图 记录 1 / 记录 2 2026-08-31(第一次,手动)+6;2026-09-01(第二次,自动)+5
积分明细(实证):账户积分流水中可查到对应的两笔「官方活动发放」记录,与上面两次旅行一一对应。

图 积分明细:两笔官方活动发放记录
两轮合计 11 积分,到账可查——闭环成立。

图 A3 闭环验证:第一次手动、第二次自动,合计到账 11 积分
4.2 持续运行战绩(据 travel_auto.log 与巡检记忆统计)
阶段 | 通道 | 结果 | 领取 |
|---|---|---|---|
09-02 ~ 09-15 | cookie 直调(Chrome UA) | 首跑即通,14 次领取成功,2 次主动派出 | +56 分 |
09-18 ~ 09-24 | Edge CDP | 09-19 起连续 6 天领取成功(+10 / +6 / +10 / +7 / +5 / +7) | +45 分 |
09-26 ~ 09-29 | Bearer 令牌直连 | 派出 1 次(健身房)、其余轮次正常幂等跳过 | 稳定 |
合计自动化领取 101 分(不含 09-09 07:23 的一轮手动领取 +8,未计入)。
4.3 修复后的实测验证(09-29 08:53)
修复当天即以 --dry-run 端到端验证:
· 磁盘令牌 → 检测为加密信封,WARN 跳过;
· 进程内存提取命中 1323 字节 JWT(exp=2026-11-22,剩余约 54 天)→ 写入临时文件;
· 以该 Bearer + Chrome UA 直连 /status → 200,state=traveling;
· 入口判定"令牌通道成功(无浏览器)",退出码 0;临时文件已清理,状态已落盘。
4.4 当前形态与待办
已实现:三级降级链路落地;令牌主通道复活(不再依赖浏览器 cookie 导出,也不再受单个 cookie 30 天有效期约束);异常有固定报告口径,不自动重试、不代替登录;两条自动化(派猫猫旅行 / Buddy 加油站签到)共用同一套内存取证范式。
待优化(如实记录):
· 每天 05:41 紧接 05:40 续期后的那一轮曾多次 401:令牌通道先失败、回退的 CDP 兜底又因兜底 profile 未登录而失效。推测是 05:41 时令牌文件尚未就绪。建议把该轮巡检延后到令牌刷新完成之后,或保持兜底 profile 登录态。该轮常因额度 / 状态无需动作,业务影响小,属待核项。
· 内存扫描 25~35 秒,对每天 4 次的频次可接受,但若进程增多需关注耗时;
· 360 安全卫士可能弹出"进程内存读取"类提示——脚本只读、不注入,无副作用,可放行。
附录:关键判断命令(备查)
# 0) 判断是"缺凭据"还是"真风控"(本次定真因的关键判据)
# curl -i https://www.workbuddy.cn/activity/growth/buddy/travel/status
# → 401 Authorization Required(APISIX)= 只是缺凭据
# curl -i -H "Authorization: Bearer <token>" 同一 URL
# → 200 = 真因是缺 Bearer;仍 401 = 才是令牌失效 / 真风控
# 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
# 4) 令牌落盘格式变化时先看结构再写代码
# 明文 → "eyJ...";加密 → {"$wbEncrypted":1,"envelope":"..."}
# 命中后者时静态无法还原,转运行期内存取证
本文所涉排查均在本机隔离环境内完成,未对平台做任何异常访问;Cookie 读取均在本地浏览器进程内、经用户已授权的扩展完成;Bearer 令牌仅从本机桌面端凭据文件或本地进程内存中读取,不外传。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。