浏览器自动化的会话保持:Cookie 生命周期、Profile 隔离与凭据托
把浏览器自动化写成脚本只花半天,让它连续跑三个月不掉链子却要花更久。最常见的故障不是"点不到按钮",而是某天早上脚本报告:未登录。本文只谈一件事——如何让登录态活得足够久,并且在它失效时能自愈。
一、先搞清楚 Cookie 的三种生命周期
- 持久 Cookie:带 Expires/Max-Age,关掉浏览器仍在,通常用于"记住我"。这类可以跨进程存活。
- 会话 Cookie:不带过期时间,只存在于内存中,浏览器进程一退立刻消失。很多站点的 SSO 会话票据(比如各类 sid、ticket)都属于这一类。
- 分区 Cookie(Partitioned):带 PartitionKey,按顶层站点隔离,第三方 iframe 里的登录态往往是这种,跨站复制会失效。
判断方法很直接:在 DevTools 的 Application → Cookies 里看 Expires 列,显示 Session 的就是会话 Cookie。只要站点的主登录票据是 Session 类型,就不可能靠"每次任务新起一个浏览器"来维持登录。
二、结论先行:浏览器要当常驻服务
既然会话票据活在内存里,正确的架构就不是"任务来了启动浏览器",而是:
- 用独立的 user-data-dir 起一个专用浏览器实例,常驻后台;
- 打开远程调试端口,让任务进程通过 WebSocket 连上去;
- 任务只做连接、操作、断开三件事,永远不关浏览器;
- 另起一个守护任务,定时探测调试端口,发现掉了就静默拉起。
const browser = await puppeteer.connect({ browserURL: 'http://127.0.0.1:9333' });
const page = await browser.newPage();
// ... 干活
await browser.disconnect(); // 注意:disconnect,不是 close
这一步做对,会话寿命就从"一次任务"变成"一次重启"。
三、Profile 隔离:一个账号一份配置
多账号场景不要共用同一个 user-data-dir。原因有两个:Cookie 会互相覆盖,退出登录时还会把别人的会话一起清掉。正确做法是每个账号一份目录:
D:\auto\profile-a
D:\auto\profile-b
每个目录配一个独立的调试端口和独立的计划任务,互不干扰。需要临时切号时,也不要整个目录删掉重建,只清目标站点的 Cookie 即可——用 CDP 的 Storage.getCookies 拿到全量,按域名过滤后逐条 Network.deleteCookies,能精确清掉目标站点而保留其他站点的登录态。
四、凭据托管:能自动登录,但别把密码摊在磁盘上
会话终究会过期(改密码、异地登录、服务端主动失效)。让脚本自己重新登录,需要保留一份凭据。Windows 上可以用 DPAPI:
ConvertTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString
密文与当前 Windows 用户绑定,换用户或换机器都解不开;再配合 NTFS ACL 只给当前用户完全控制,基本可以接受。脚本启动时解密、注入环境变量、用完即弃,不落日志。
五、三层失败自愈
- 接口探测:不要靠页面上的"登录/已登录"文案判断,直接请求一个需要鉴权的业务接口,看返回码和错误码。这是最可靠的信号。
- 自动重登:探测到未登录,就填表登录。注意表单提交按钮常常不是 button 而是 div,纯 JS 的 element.click() 未必触发框架的提交逻辑,用真实鼠标事件(mousedown/mouseup/click 序列)更稳。
- 人工兜底:登录可能要求短信验证码或扫码。这时脚本要做的不是傻等,而是把窗口从屏幕外挪回可见区域、把登录页置前,并起一个带超时的 watcher 轮询登录态;人补完验证码,流程自动继续。
六、实操清单
- 专用 user-data-dir + 固定调试端口,任务只 connect 不 launch;
- 守护进程每 30 分钟探活,端口不通就拉起;
- 凭据 DPAPI 加密 + ACL 收敛,解密后不写日志;
- 鉴权判断走接口,不走页面文案;
- 登录表单用真实鼠标事件提交,别用 element.click();
- 需要人机验证时把窗口挪到屏幕上,并让 watcher 接管后续流程。
把这六条落地之后,"脚本又没登录"这类问题基本会消失;剩下的失败都能在日志里看到明确原因,而不是一句笼统的 timeout。
- 点赞
- 收藏
- 关注作者
评论(0)