用 Chrome DevTools Protocol 驱动浏览器:自动化任务踩坑记录

举报
yd_297054392 发表于 2026/10/31 03:13:38 2026/10/31
【摘要】 在 2 核小内存服务器上用 CDP 驱动常驻 Chrome 做定时自动化:visibilityState hidden 导致页面不渲染、跨域 iframe 编辑器的 attach 写法、JS click 不触发 React 事件、模态遮罩静默吞点击、IPC 超时与标签页内存管理,全部来自实际跑通的脚本。

最近在一台 2 核 1.7G 内存的小服务器上跑定时自动化任务:每天登录站点、填表单、点按钮、核对结果。装 Selenium 或 Playwright 都会被内存劝退(光浏览器常驻就 700MB+),最后直接用 Chrome DevTools Protocol(CDP)驱动一个常驻 Chrome。下面记录的都是真实踩过的坑,代码取自实际跑通的脚本。

一、常驻浏览器 + 远程调试端口

用 systemd 把 Chrome 起成常驻服务,profile 落在磁盘上,登录态、Cookie 都能跨进程保留:

ExecStart=/usr/bin/google-chrome \
  --remote-debugging-port=9222 \
  --user-data-dir=/root/.hermes/state/chrome-profile \
  --no-first-run --no-default-browser-check

好处很直接:不必每次任务重新登录,也不必重新过人机校验。代价是会话级 Cookie 在进程重启后会失效,所以"进程重启导致页面回到未登录"是正常现象,不是信任设备失效——用密码重登即可,信任设备还在的话不会再要短信码。

二、页面不渲染:visibilityState 是 hidden

这是最隐蔽的一个坑。常驻 Chrome 里页面常以 document.visibilityState === 'hidden' 运行,React / Next 这类流式页面会一直不 hydrate,表现是页面空白、按钮全部 disabled,看起来像网站挂了。

一次导航后重设这两个状态就能救回来:

cdp('Emulation.setFocusEmulationEnabled', enabled=True)
cdp('Page.setWebLifecycleState', state='active')

每次导航之后都要重设一次,只在开头设一次不够。

三、跨域 iframe 里的编辑器

很多富文本 / Markdown 编辑器是跨域 iframe(res-hd.hc-cdn.cn/... 之类),父页面拿不到内部 DOM,document.querySelector 一律返回 null,TinyMCE 也不存在。正确做法是用 Target.getTargets 找到 iframe 的 targetId 再 attach:

tid = [t['targetId'] for t in cdp('Target.getTargets')['targetInfos']
       if 'HDMDEditorPortal' in t.get('url', '')][0]
sid = cdp('Target.attachToTarget', targetId=tid, flatten=True)['sessionId']
cdp('Runtime.evaluate', expression='setEditorContent(%s)' % json.dumps(md),
    session_id=sid)

两个注意点:必须带 flatten=True,否则拿到的是嵌套 session;sessionId 不跨进程调用存活,每次用之前重新 attach。

四、JS click() 不触发 React 事件

页面上大量按钮其实是带 onClick 的 div / span,用 element.click() 经常毫无反应、也不报错。必须走真实坐标点击:

el.scrollIntoView({block: 'center'})
rect = el.getBoundingClientRect()
click_at_xy(rect.x + rect.width/2, rect.y + rect.height/2)

反过来看,这也是个有用的信号:如果点击后完全没有网络请求,大概率是没点中或被遮罩挡了。提交类操作前建议先复核落点:

document.elementFromPoint(x, y).id  // 是不是目标按钮

模态遮罩(提示标签重复、不支持手动添加等)会盖在提交按钮上,静默吞掉所有点击,这是最常见的"按钮坏了"假象。

另外要小心隐藏模板元素:querySelectorAll('.status-btn')[0] 可能是一个 0×0 的模板节点,应取 filter(b => b.getBoundingClientRect().width > 0)[0]。

五、填输入框:两种可靠方式

  • 普通 input:js 设 value 后必须派发 input / change 事件,否则框架收不到。
  • contenteditable 或 CodeMirror:聚焦后用 CDP 的 Input.insertText 整段写入,比逐字符模拟按键稳得多,也不会被输入法的 composition 事件打断。
  • 密码框显示 ******** 且值不在 DOM 里时,点旁边的复制图标,再建临时 textarea 聚焦后 Ctrl+V 把值粘出来读。

六、IPC 超时和长等待

默认 IPC 超时通常只有 5 秒,慢站上 Runtime.evaluate 会随机报错。给每个 CDP 调用显式加超时:cdp('Page.navigate', url=..., _response_timeout=40)。

还有两个反直觉的点:

  1. 在 js() 里 await 一条 fetch 链会等满整条链,超时就整段失败。改成"结尾赋值 + 立即返回",再用单独的调用轮询读结果。
  2. 在自动化会话里 time.sleep 超过约 60 秒,标签页连接会冻结,后续所有 CDP 调用全部 5 秒超时。长等待放到 shell 里,或者拆成多次短调用。

七、内存:标签页要随手关

小内存机器上,站内卡片点击几乎都在新标签页打开,几个标签就能吃掉几百 MB,最后表现是连 1+1 的 evaluate 都超时。处理完就用 Target.closeTarget 关掉,常驻只留 1–2 个。判断当前开着的页面数:

len([t for t in cdp('Target.getTargets')['targetInfos'] if t['type'] == 'page'])

真卡死时 systemctl --user restart hw-chrome 立刻恢复,profile 在磁盘上,登录态不丢。

小结

CDP 的好处是看得见:网络请求、DOM、Cookie、JS 返回值都能直接核验。做自动化时不要相信页面上的文案反馈(“已收藏”、计数 +1 都可能是全局数据),真正的判据是网络请求是否发出、接口返回是否变化、以及结果页/列表里能否查到自己刚提交的记录。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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