别再反复登录了:用 Playwright 通过 CDP 接管已登录的浏览器
做浏览器自动化,最先卡住的往往不是选择器,而是登录。
一、为什么"每次都重新登录"是个坑
很多脚本的默认做法是:启动一个全新的无头浏览器,打开登录页,自动填账号和密码。看起来完整,但在真实站点上几乎一定会撞墙:
- 短信验证码、图形验证码、滑块验证会把自动化当成攻击流量;
- 频繁的"新设备 + 新指纹"登录会触发风控,越重试越糟;
- 有的站点干脆只允许扫码登录,脚本无法完成。
于是脚本陷入一个死循环:越失败越重试,越重试越像机器人。
二、换个思路:让浏览器一直活着,脚本去连它
Chromium 内核的浏览器(Chrome、Edge)内置了 DevTools 协议(CDP)。只要启动时打开远程调试端口,任何 CDP 客户端都可以接管这个已经登录好、带着完整 Cookie 和存储的浏览器。
以 Edge 在 Windows 上为例:
msedge.exe --remote-debugging-port=9222 --user-data-dir=D:\automation-profile
三个细节值得注意:
--user-data-dir一定要指向独立目录,避免和日常浏览用的 profile 抢文件锁;- 调试端口默认只监听回环地址,不要改成
0.0.0.0; - 第一次登录由人来完成(扫码或短信),之后会话就留在这个 profile 里了。
启动完成后可以先用一行命令确认端口可用:
curl http://127.0.0.1:9222/json/version
三、用 Playwright 接上去
const browser = await chromium.connectOverCDP('http://127.0.0.1:9222');
const ctx = browser.contexts()[0];
const page = ctx.pages()[0];
这里有三个容易踩的点:
connectOverCDP返回的是"已有上下文",不要再调用newContext(),否则等于开了一个干净的隐身窗口,登录态全部没有;- 脚本结束调用
browser.close()只是断开连接,浏览器本身还在(实测在 Edge 155 + Playwright 上是这个行为),下一个脚本还能接着连; - 状态应该靠 profile 保存,而不是靠长期持有内存对象。每个脚本连上来、干完活、断开,是最稳的用法。
四、两个真实踩过的坑
坑一:弹窗遮罩拦截点击。 组件库(比如 Vue)经常把对话框直接挂到 body 上,遮罩层盖住目标元素,locator.click() 会一直重试到超时,日志里写着 intercepts pointer events。我遇到的情况是读完"积分明细"弹窗后忘了关,后续所有点击都失效。解决办法很简单:每次动作前把页面回到干净状态(重新 goto),或者先显式关闭弹窗。
坑二:坐标和像素比不是一回事。 截图看到的是物理像素,getBoundingClientRect() 返回的是 CSS 像素。当 devicePixelRatio = 1.25 时,截图里位于 (390, 655) 的元素,代码里要点击 (312, 524)。要么用 devicePixelRatio 换算,要么干脆全程用元素定位、彻底不用坐标。
五、安全边界
- 远程调试端口相当于把整个浏览器交给本机任意进程,任务结束记得关掉浏览器;
- 不要在脚本、配置文件、日志里写 AK/SK 或密码;
- 自动化访问要克制:加合理的等待时间,不要用高频请求把账号送进风控名单。
六、小结
"人登录一次,脚本接管复用"这套模式,比"脚本自己登录"稳定一个数量级:验证码、扫码、二次验证全部交给人,脚本只做它擅长的重复劳动。对内部工具、数据抓取、日常任务这些场景,基本够用了。
如果要做长期运行的任务,还可以再往前一步:把上面的流程包成一个常驻进程,用定时器触发,用 profile 保持会话,失败时截图并通知人。这时脚本才真正从"演示"变成"能跑"。
- 点赞
- 收藏
- 关注作者
评论(0)