Playwright 接管已登录的 Chrome:connect_over_cdp 的实测细节与五个坑

举报
yd_276135645 发表于 2026/11/05 06:09:42 2026/11/05
【摘要】 脚本要复用人工登录出来的浏览器会话,就得走 CDP 而不是 launch()。本文给出 Chrome 156 / Playwright 1.64 上的实测数据:connect_over_cdp 仅 305ms、new_page 102ms;browser.close() 只断连接不杀进程;SPA 在 domcontentloaded 时 innerText 长度为 0、DOM 节点 36 个,3

背景:为什么我要「接管」一个已经在跑的浏览器

做自动化时最烦的不是写选择器,而是登录。手机验证码、扫码、二次确认,任何一步都需要人;而这些会话又偏偏存在浏览器里,脚本 launch() 出来的干净实例一个都没有。

我维护了一台无显示器的 Linux 机器,上面长跑着一个有头 Chrome(Xvfb + noVNC),人通过网页 VNC 手动登录,脚本通过 CDP(Chrome DevTools Protocol)接管同一个实例。跑了一个多月,踩到的坑都在「连接」这一层,而不是业务逻辑里。下面这几条都是在本机实测的数字,环境是 Chrome 156.0.8078.4 / CDP Protocol 1.3 / Playwright 1.64 / Python 3.12。

一、前提:浏览器必须以调试端口启动

CDP 只能连上「启动时开了门」的进程。启动参数里要有:

chrome --remote-debugging-port=9222 \
       --remote-debugging-address=127.0.0.1 \
       --user-data-dir=/root/.remote-chrome

--remote-debugging-address 默认只监听回环,这正好:不要把 9222 暴露到公网,它等同于把浏览器完全交出去——任何能连上的人都能读你的 cookie、执行任意 JS。外面要看画面就用 noVNC,看页面用 VNC 或截图。

连上之后先确认身份,别急着操作:

sess = browser.new_browser_cdp_session()
print(sess.send("Browser.getVersion")["product"])   # Chrome/156.0.8078.4

二、连接本身很便宜,别为它做缓存

实测在同一个局域网/回环下:

  • connect_over_cdp() 到连上:305 ms
  • context.pages 里再 new_page():102 ms

也就是说连接开销基本等于一次 HTTP 请求,每次脚本跑起来重新连一遍完全没问题,不需要搞「长连接守护进程」。真正贵的是页面加载:同一个页面 goto(wait_until="domcontentloaded") 花了 2067 ms,而这还只是开始(见坑二)。

三、坑一:browser.close() 不会关掉用户的窗口

这一点反直觉但很重要。通过 CDP 连上去的 browser 对象只是「一条连接」,browser.close() 断的是连接,不是进程。实测调用之后 systemctl is-active 仍是 active,curl 127.0.0.1:9222/json/version 照常返回。

好消息是脚本崩溃也不会把用户的浏览器带走;坏消息是——你关不掉它。要真正重启浏览器得走服务管理(systemctl restart),而重启会丢会话 cookie(见坑四),所以别在任务跑到一半时重启。

四、坑二:domcontentloaded 不等于页面可用

这是最容易导致「脚本时好时坏」的一条。现在的前端基本都是 SPA,domcontentloaded 触发时 HTML 骨架刚到,数据还在路上。同一个成长中心页面,我在 domcontentloaded 之后每秒采样一次 DOM 节点数和目标卡片数:

时刻 innerText 长度 DOM 节点 目标卡片
+0 s 0 36 0
+1 s 106 123 0
+2 s 939 557 8
+3 s 1725 1679 15
+4 s 1725 1679 15

注意第 0 秒 innerText 是 0——不是「少」,是一个字符都没有,此时任何文本匹配都会得出「页面没有这个按钮」的错误结论。而到第 3 秒节点数翻了 46 倍。

所以判断页面「好了没有」不能靠固定睡眠,要靠可观测条件:

# 差:固定睡眠,机器慢一点就翻车
page.wait_for_timeout(3000)

# 好:等到真正需要的元素出现
page.wait_for_function("() => document.querySelectorAll('.cardItem').length >= 8", timeout=30000)

反过来,如果某个列表是「点展开才加载更多」,就得先点再等数量变化,而不是等固定时间——同一页面上第二次加载往往比第一次快,固定睡眠纯属浪费。

五、坑三:别假设只有一个 context

connect_over_cdp 拿到的 browser.contexts 里是用户真实在用的上下文,带着他登录出来的全部 cookie。实测这个实例 contexts 长度恒为 1。

如果你顺手写了 browser.new_context(),得到的是一个干净的、没有任何登录态的上下文——表现就是「明明连上了却处处要我登录」。正确姿势:

ctx = browser.contexts[0]          # 复用真实上下文
page = ctx.new_page()              # 自己开一个标签干活
...
page.close()                       # 干完自己关

这条「自己开的自己关」不是洁癖。长期运行下,脚本留下的重复标签会累积:我这边实测过一次跑到 20 个标签页、27 个进程、RSS 5.6 GB,清理到 1 个标签后 RSS 回落到 1.5 GB 左右。定时任务里最好再配一条「保留白名单标签、其余全关」的回收任务,每天跑一次。

六、坑四:会话 cookie 的 expires=-1 救不了重启

context.cookies(url) 读到的会话 cookie,expires 是 -1(我这边某域名 35 个 cookie 里带会话态的就静态标记为 -1 的那几个)。这类 cookie 只存在于内存,Chrome 一退出就没了——profile 目录里不会留下它们。

如果你的浏览器会因为 OOM、升级、手动重启而重启,登录态就跟着丢。想让「重启不掉登录」,得把会话 cookie 改成有明确过期时间再写回运行中的浏览器,让它落盘:

ck = ctx.cookies()
for c in ck:
    if c.get("expires", -1) == -1:
        c["expires"] = time.time() + 30*86400
ctx.add_cookies(ck)

但请注意这只是「延长寿命」,不是「永久登录」:服务端的会话该过期还是会过期(我这边实测约 20 小时),而且一旦服务端会话死了,一个还被浏览器长期保存的旧 cookie 反而可能让站点在「有会话 → 校验失败 → 弹回登录」之间循环。所以续命逻辑要配一个「先验证再回写」的开关:验证不通过就把这批 cookie 删掉,别硬塞。

七、坑五:等待失败要能「看得见」

无人值守的脚本最怕静默失败。我的做法是给每个关键步骤留一个明确的判定信号,而不是「没抛异常就算成功」。例如点击提交按钮之后:

  • 有请求事件:page.on("request") 收到目标 POST;
  • 无跳转站点:页面出现成功文案;
  • 两者都没有:就是失败,绝不重试提交(重试容易产生重复数据)。

还有一种反直觉的情况:page.evaluate() 抛 Execution context was destroyed, most likely because of a navigation,这通常意味着提交成功、页面正在跳转,不是错误。把这个异常当成「疑似成功」再去核对结果,比当成失败重发一次安全得多。

小结

  • 连接很便宜(~300 ms),别过度设计;
  • browser.close() 只断连接,杀不掉进程,也关不掉用户的窗口;
  • SPA 下 domcontentloaded 时 innerText 可能为 0,等待要等条件不要等时间;
  • 务必用 contexts[0],new_context() 会得到一个没有登录态的干净上下文;
  • 会话 cookie 只活到进程退出,要持久化得改写 expires,并且「先验证再回写」;
  • 自己开的标签自己关,配合定时回收。

这套组合让「人手动登录一次、脚本自动干活」变得可行,剩下的最大不确定性就不在代码里,而在服务端会话的有效期上了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。