用 CDP 驱动常驻 headless Chrome:网页自动化踩坑实录
在只有 2 vCPU、不到 2GB 可用内存的服务器上跑网页自动化,最容易犯的错就是"每次任务新开一个浏览器"。我在这台机器上把模式改成:一个常驻的 headless Chrome(systemd 托管、固定 user-data-dir、固定调试端口 9222),所有脚本都通过 Chrome DevTools Protocol 连上去复用同一批标签页。下面是这套方案踩过的六个坑和对应解法。
一、为什么不再"用完即关"
三个理由:
- 冷启动成本:headless Chrome 冷启动加首次渲染通常要几秒,一天跑十几次任务,纯等待时间就很可观;
- 登录态:每次用新的 profile,站点会当成新设备;固定 profile 才能让 Cookie 长期存活;
- 内存:这是一台 2 vCPU / 1.7GB RAM 的机器,网关服务常驻就占掉 800MB 上下,同时起多个浏览器实例很容易打满内存。
所以做法是:浏览器只起一次并交给 systemd Restart=always 托管,脚本只负责建标签页、操作、关标签页。
二、坑 1:页面一片空白,按钮全部 disabled
这是最迷惑的一类故障:标题出来了、document.body.innerText 有内容,但按钮点不动,组件区是空的。
原因是 headless 模式下 document.visibilityState 恒为 hidden,而 React / Next 这类做流式 SSR + 惰性 hydrate 的页面,在这个状态下往往根本不启动 hydration。
解法是导航之后显式把生命周期和焦点模拟打开,并且每次 Page.navigate 之后都要重设:
cdp('Emulation.setFocusEmulationEnabled', enabled=True)
cdp('Page.setWebLifecycleState', state='active')
三、坑 2:CDP 调用报 “timed out after 5s waiting for the daemon”
封装层给 CDP IPC 的默认超时常常只有 5 秒。普通站点够用,但遇到慢站、SSO 多次跳转、或者页面上埋点请求堆积时,5 秒很容易被打穿,而且报错信息指向"导航超时",容易误判成网络问题。
办法:对已知慢的页面单独放大超时(例如 30 秒),并且先导航、后设置可视化状态,别把两个动作的耗时叠在一次调用里。
四、坑 3:标签页泄漏
云控制台类站点的按钮很喜欢 target=_blank。一个流程跑下来攒出十几个标签页,2GB 内存直接被榨干,表现就是后续所有操作越来越慢,最后整个浏览器卡死。
我的做法是:一个任务尽量在一个标签页里完成;遇到新开的页面立刻用 Target.closeTarget 关掉;每轮任务开始前用 GET http://127.0.0.1:9222/json/list 列一遍,清理到只剩 1–2 个标签页。
五、坑 4:登录态与二次验证
用一次性 profile 登录,站点会当成新设备,短信验证码几乎必弹(有些还只发手机、邮箱收不到),自动化流程直接卡死在这一步。固定 profile + 常驻进程让 Cookie 长期存活才是关键。
真的过期了怎么办:不要把密码写进脚本。把账号口令放进凭据管理器,让工具在登录页按 origin 匹配后填充并提交。实测同一个长期使用的 profile 重新登录时不再触发短信验证,这也印证了"新 profile = 新设备"的判断。
六、坑 5:wait_for_load 不等于数据就绪
很多页面的骨架是 SSR 直接出的,真正的数据要靠前端 XHR 拉。导航完成事件触发时,你要的那个数字可能还没到。
两个更稳的做法:
- 轮询
document.body.innerText,直到目标字符串出现(例如等待积分数字从-变成真实数字); - 直接在页面里
fetch那个 XHR 接口,拿 JSON,比解析 DOM 稳定得多。
七、坑 6:能走接口就别爬 DOM
这条提升最大。列表类、库存类数据往往有公开的 JSON 接口,匿名即可读。与其拖 DOM、等渲染、扛改版,不如直接请求接口:
curl -s 'https://example.com/api/xxx?limit=100' | python3 -m json.tool
DOM 选择器会因为前端发版而失效,接口的字段通常稳定得多。
八、小结:一份上线前检查清单
每次写新的自动化流程前,我会先确认这几件事:
- [ ] 浏览器是常驻的,调试端口通(
curl http://127.0.0.1:9222/json/version); - [ ] 每次导航后重设 focus emulation 和 lifecycle state;
- [ ] 慢站的 CDP 调用放大了超时;
- [ ] 任务开始前后各清理一次标签页;
- [ ] 关键数据优先走接口,其次才是轮询 DOM;
- [ ] 凭据只从凭据管理器取,不写进代码。
这套东西本身不复杂,难的是把"浏览器是稀缺资源"这件事当成前提来设计流程。资源一旦被当成常驻服务而不是一次性消耗品,稳定性和速度都会好很多。
- 点赞
- 收藏
- 关注作者
评论(0)