不装 Playwright,用 CDP 直接驱动 Chrome 做页面自动化
为什么想到这个
最近要自动化一个网页流程:打开页面、等它渲染、截图、点几个按钮、把结果读出来。第一反应是 npm i playwright,但在一个受限环境里卡住了——安装包体积大、要下载几百 MB 的浏览器、还可能被网络和权限拦住。而机器上本来就装着一个 Chrome。
其实 Chrome 自带一个完整的远程控制协议:CDP(Chrome DevTools Protocol)。DevTools 面板本身就是它的客户端。我们完全可以不装任何浏览器自动化框架,用 Node 内置的 fetch 和 WebSocket(Node 21+ 已内置)直接和它对话。
三行命令把 Chrome 变成可编程的
启动时加一个调试端口,并把用户数据目录指向独立路径(避免和你日常用的 Chrome 冲突):
chrome.exe --remote-debugging-port=9222 \
--user-data-dir=D:\tmp\cdp-profile \
--no-first-run --no-default-browser-check
然后访问 http://127.0.0.1:9222/json/list,会拿到一个 JSON 数组,每个页面标签都有一个 webSocketDebuggerUrl。连上它,就可以发指令了。
最小可用的四个指令
CDP 是「发一条消息、回一条消息」的模型,消息体就是 {id, method, params}。日常自动化其实只需要四条:
Page.navigate—— 打开网址Runtime.evaluate—— 在页面里执行 JS 并把结果取回来(读取文本、查元素、判断状态全靠它)Page.captureScreenshot—— 截图,返回 base64Input.dispatchMouseEvent—— 派发真实鼠标事件,用于点击
核心代码不到 40 行:维护一个 id → Promise 的映射,send(method, params) 把消息写进 WebSocket,收到同 id 的响应就 resolve。没有依赖,没有浏览器下载,启动时间是毫秒级。
三个真实踩过的坑
坑一:环境变量里的代理会劫持 localhost。 机器上如果设置了 HTTP_PROXY,某些工具会把对 127.0.0.1:9222 的请求也丢给代理,表现为连接超时、报 os error 10060,看起来像是浏览器没起来。排查时先用 curl --noproxy '*' http://127.0.0.1:9222/json/version 验证端口本身是通的。Node 的 fetch 默认不吃环境代理,这也是我最后选它的原因之一。
坑二:el.click() 点不动现代前端组件。 很多 Vue / React 组件把交互逻辑绑在原生事件上,直接用 JS 调 element.click() 往往没反应,尤其是按钮被拆成「图标 + 文字 + 计数」的复合结构时。正确做法是算出元素的 getBoundingClientRect(),然后用 Input.dispatchMouseEvent 依次发 mouseMoved、mousePressed、mouseReleased,坐标取视口中心点。
坑三:看不见的浮层会吃掉所有点击。 页面上的新手引导气泡、悬浮客服头像、Cookie 提示,经常是透明或半透明的遮罩。你算出来的坐标是对的,事件也确实发出去了,但被上层元素拦截,表现就是「点了没反应」。定位这类问题最快的办法是截图看一眼,而不是反复改坐标。另外,如果一个按钮被悬浮窗压住了右半边,点它的左半部分就行——把坐标从中心改成 left + 20 往往就能过。
它适合什么、不适合什么
适合:环境受限装不了框架、只需要几十行脚本的一次性任务、需要复用用户已经登录的浏览器会话、以及想在 CI 里跑一个很轻的冒烟检查。
不适合:需要复杂等待策略、多标签协同、文件上传下载、移动端模拟的正式测试。这些场景 Playwright 的抽象是值得的,别为了省一个依赖重复造轮子。
一句话总结:CDP 不是 Playwright 的替代品,而是「我只想拧一颗螺丝」时那把刚好够用的螺丝刀。把它放在工具箱里,需要的时候能省下很多时间。
- 点赞
- 收藏
- 关注作者
评论(0)