用 CDP 驱动带登录态的浏览器做日常自动化:我踩过的五个坑
用 CDP 驱动带登录态的浏览器做日常自动化:我踩过的五个坑
不是 Playwright 教程,而是把「让脚本接管一个已经登录好的浏览器」这件事做扎实之后,整理出的几条硬经验。
为什么不用无头浏览器
很多日常自动化(签到、报表、后台操作)卡在一个前提上:会话在你手里,而不在脚本手里。用 Playwright/Selenium 起一个全新的无头实例,等于从零开始登录——扫码、短信、二次验证全都要重来一遍,而且平台侧经常把"新设备登录"当成风控事件。
更省事的做法是反过来:让用户在自己看得见的浏览器里登录,脚本通过 Chrome DevTools Protocol(CDP)接管同一个实例。Chrome 只要带上 --remote-debugging-port 启动就能被外部驱动:
chrome --user-data-dir=~/.hermes/browser-profiles/work \
--remote-debugging-port=9333 --remote-allow-origins=*
--user-data-dir 单独给一个目录很关键:它既是登录态的载体,也把自动化和日常浏览隔离开,避免调试端口暴露在你平时用的配置上。
坑一:/json/new 会让标签页堆积
CDP 的 HTTP 端点里,/json/new?url=... 是最顺手的开页方式,于是很容易写成"每个步骤都开一个新页"。跑十几步之后,用户桌面上就多出十几个标签页——我见过一次二十三个。这在调试时很刺眼,也拖慢渲染。
正确姿势是按 origin 复用:先 GET /json 列出所有 target,找到同源的 page,拿到它的 webSocketDebuggerUrl,然后在这个连接上 Page.navigate。
// 复用而不是新增
const t = pages.find(p => new URL(p.url).host === host);
ws = new WebSocket(t.webSocketDebuggerUrl);
ws.send(JSON.stringify({id:1, method:'Page.navigate', params:{url}}));
坑二:复用标签页不等于可以随便 navigate
第二层坑更隐蔽:导航会清空页面状态。表单填了一半、富文本编辑器里刚写好一篇稿子,一个 Page.navigate 过去就全没了——而且自动保存此时往往还没触发。
所以要做两套入口:open(url) 负责导航,attach(host) 只连不导航。凡是写稿、填表这类有状态流程,一律走 attach,用页面里已有的 loader 就绪信号等待,而不是重新加载。
判断标准很简单:如果这个页面上的东西是"用户/脚本刚输入但还没提交"的,就绝对不能导航。
坑三:合成点击 ≠ 真点击
这是最耗时间的一个坑。现代前端里,element.click() 和 CDP 的 Input.dispatchMouseEvent 都是"合成"事件,event.isTrusted 是 false。大部分框架不检查它,但有些前端会——尤其涉及提交、发布、支付这类动作。
表现是:点击没报错、没有 toast、没有网络请求、页面毫无变化。你以为是选择器错了,于是换选择器、换坐标、加延时,全都无效。
识别方法:点击后监听 Network.requestWillBeSent。如果连请求都没发出,问题在事件层,不在业务校验层,别再去猜字段了。
遇到这种情况,正确的收尾是把它交回给用户,而不是继续硬闯。可以先把所有能准备的都准备好(内容写进编辑器、标题摘要填好、让它自动存草稿),最后一步让人点一下。两秒钟的动作,比脚本折腾半小时靠谱。
坑四:缺一个 DOM 节点,整条链路就是死的
有一次提交按钮死活不工作,查到最后发现前置的"我已阅读并同意"勾选框根本没渲染——容器 是空的,负责注入控件的脚本没执行。这种情况下不是事件被拦截,而是根本不存在可点的对象,人类用户在那个浏览器里也提交不了。
诊断顺序应该是:
目标节点存在吗(querySelector 有没有拿到)
它的 getBoundingClientRect() 有没有尺寸(width>0)
它的父容器里该有的子节点在不在(比如 input)
点下去之后有没有网络请求
卡在第 3 步就说明是页面自身渲染问题,换浏览器往往就能恢复——这也是为什么"专用 profile 出问题"时,先怀疑资源加载,而不是怀疑代码。
坑五:单点登录会互相踢
同一个账号在两个浏览器里登录,服务端的 SSO 很可能只保留最新会话。于是出现这种循环:脚本的 profile 登录 → 用户在自己浏览器里登一次 → 脚本这边掉线 → 再登 → 又掉。
两个解法:
- 只用一套浏览器:把日常浏览器也用
--remote-debugging-port启动,脚本直接接管你自己的会话,不存在两份登录态。 - 或者明确分工:自动化 profile 只用于自动化,并且约定"这个窗口的登录态别在别处覆盖"。
没有第三条路。反复重登只是在跟 SSO 打架。
收尾:把"人"放进流程里
做完这些,我的结论是:自动化不是把人从流程里删掉,而是把人的动作压缩到最少。
- 机械的、幂等的、无状态的(读列表、抓状态、签到)→ 全自动
- 有状态但可准备的(写文章、填表单)→ 脚本准备到 95%,留最后一次点击
- 需要判断和授权的(发布、支付、涉及个人信息的报名)→ 明确交回人
按这个边界设计,脚本不会因为某个按钮点不动就整条崩掉,用户也不用面对一堆"我以为它做完了其实是空的"。
毕竟自动化的价值不是"零人工",而是"人工只花在必须由人决定的地方"。
- 点赞
- 收藏
- 关注作者
评论(0)