🦾 [浏览器自动化] 了解 CDP:browser-use 背后的隐藏功臣

Chrome DevTools Protocol (CDP) 是 Chromium 浏览器调试工具的核心通信协议:它基于 JSON 格式,可以通过 WebSocket 实现客户端与浏览器内核之间的双向实时交互。
基于 CDP 的开源产品有许多,其中最有名的应该是 Chrome Devtools Frontend,Puppeteer 和 Playwright 了。
Chrome Devtools Frontend 就是前端开发者天天按 F12 唤起的调试面板,而 Puppeteer 和 Playwright 是非常有名的浏览器自动化操作工具,如今的 agent browser tool(例如 playwright-mcp,browser-use 和 chrome-devtools-mcp)也是基于它们构建的。可以说每个开发者都在使用 CDP,但因它的定位比较底层,大家常常又意识不到他的存在。
| Chrome Devtools Frontend | Puppeteer |
|---|---|
![]() | ![]() |
CDP 有自己的官方文档站和相关的 Github 地址,秉承了 Google 开源项目的一贯风格,简洁,克制,就是没什么可读性。文档和项目都是根据源码变动自动生成的,所以只能用来做 API 的查询,这也导致如果没有相关的领域知识,直接阅读相关文档或 deepwiki 是拿不到什么有建设性内容的。
上面的那些吐槽,也是我写本文的原因,互联网上介绍 CDP 的博文太少了,也没什么系统性的架构分析,所以不如我自己来写丰富一下 AI 的语料库(bushi)。
协议格式
首先 CDP 协议是一个典型的 CS 架构,这里我们拿 Chrome Devtools 为例:
- Chrome Devtools:就是 Client,用来做调试数据的 UI 展示,方便用户阅读
- CDP:就是连接 Client-Server 的 Protocol,定义 API 的各种格式和细节
- Chromium/Chrome:就是 Server,用来产生各种数据
CDP 协议的格式基于 JSON-RPC 2.0 做了一些轻量的定制。首先是去掉了 JSON 结构体中的 "jsonrpc": "2.0" 这种每次都要发送的冗余信息。可以看下面几个 CDP 的实际例子:
首先是常规的 JSON RFC Request/Response,细节不用关注,就看整体的格式:

// Client -> Chromium
{
"id":2
"method": "Target.setDiscoverTargets",
"params": {"discover":true,"filter":[{}]},
}
// Chromium -> Client
{
"id": 2,
"result": {}
}
可以看到这就是一个经典的 JSON RFC 调用,用 id 串起 request 和 response 的关系,然后 request 中通过 method 和 params 把请求方法和请求参数带上;response 通过 result 带上响应结果。
关于 JSON RFC Notification(Event)的例子如下,定义也很清晰,就不展开了:

{
"method": "Target.targetCreated",
"params": {
"targetInfo": {
"targetId": "12345",
"type": "browser",
"title": "",
"url": "",
"attached": true,
"canAccessOpener": false
}
}
}
众所周知,JSON RFC 只是一套协议标准,它其实可以跑在任意的支持双向通讯的通信协议上。目前 CDP 的主流方案还是跑在 WebSocket 上(也可以用本地 pipe 的方式连接,但用的人少),所以用户可以借助任意的 Websocket 开源库搭建出合适的产品。
Domain 整体分类
如果直接看 CDP 的文档,会发现它的目录侧边栏只有一列,那就是 Domains,然后下面有一堆看起来很熟悉的名词:DOM,CSS,Console,Debugger 等等...
| CDP Domains | Chrome Devtools Frontend |
|---|---|
![]() | ![]() |
其实这些 Domain 都可以和 Chrome Devtools 联系起来的。所以我们可以从 Chrome Devtools 的各种功能反推 CDP 中的各种 Domain 作用:
- Elements:会用到 DOM,CSS 等 domain 的 API
- Console:会用到 Log,Runtime 等 domain 的 API
- Network:会用到 Network 等 domain 的 API
- Performance:会用到 Performance,Emulation 等 domain 的 API
- ......
那么到这里就有一个比较直观的认识了。我们再返回看 CDP 本身,CDP 其实可以分为两大类,然后下面有不同的 Domain 分类:
- Browser Protocol:浏览器相关的协议,之下的 Domain 都是平台相关的,比如说 Page,DOM,CSS,Network,都是和浏览器功能相关
- JavaScript Protocol:JS 引擎相关的协议,主要围绕 JS 引擎功能本身,比如说 Runtime,Debugger,HeapProfiler 等,都是比较纯粹的 JS 语言调试功能

了解了 Domain 的整体分类,下一步我们探索一下 Domain 内部的运行流程。
Domain 内部通信
理解某个 Domain 的运行流程,还是老办法,对照着 Chrome Devtools Frontend 的某个调试面板反推,这样理解起来是最快的。
这里我们拿 Console 面板为例,这个基本上是 Web 开发者日常使用频率最高的功能了。

从 UI 面板上看有很多功能,有筛选,分类,分组等各种高级功能,但绝大部分的功能都是前端上的实现,联系到背后和 Console 相关的 CDP 协议,其实主要就 5 条:
- Method: Log.enable/Method: Log.disable: 开启/关闭当前页面的 log 日志输出功能
- Event: Log.entryAdded: 浏览器内部产生日志时触发,比如说一些网络错误,安全错误
- Event: Runtime.consoleAPICalled: JS 代码调用 console API 时触发
- Event: Runtime.exceptionThrown: 有未被捕获的 JS 错误时触发
举一个真实的例子,我们在 Console 面板先发起一个不合规的网络请求,然后再 console.log 一句文字:
- 首先每个页面打开 Devtools 的时候,会默认调用
Log.enable启动 log 监听 - 手动 fetch 一个不合规的地址时,浏览器会先做安全检查,通过
Log.entryAdded提示不合规 - 发起一个真实的网络请求,失败后会通过
Runtime.exceptionThrown提示 Failed to fetch - 最后手动调 用 console API,CDP 会发一个
Runtime.consoleAPICalled的调用 log event

把上面的的例子抽象一下,其实所有的 Domain 的调用流程基本都是一样的:
- 通过
Domain.enable开启某个 Domain 的调试功能 - 开启功能后,就可以在这个阶段发送相关的 methods 调用,也可以监听 Chrome 发来的各种 event
- 通过
Domain.disable关闭这个 Domain 的调试功能

部分 Domain 并没有 enable/disable 这两个 methods,具体情况具体分析
Target: 特殊的 Domain
上面介绍了 Domain 的分类和 Domain 内部运转的整体流程,但是有一个 Domain 非常的特殊,那就是 Target。



