简介
Chrome DevTools Protocol(CDP) 是 Google Chrome / Chromium 提供的一套底层调试协议,允许外部工具通过 JSON 消息对浏览器进行检测、检查、调试和性能分析。
你可能不知道,平时用的 Chrome DevTools 开发者工具,底层就是靠 CDP 来和浏览器通信的。而现在,越来越多的自动化工具(Puppeteer、Playwright、browser-use 等)都基于 CDP 构建。
在上一篇 browser-use 实战总结 中,我们看到了 --remote-debugging-port=9222 这个参数,这正是启用 CDP 的关键。
什么是 CDP
CDP 本质上是一个基于 WebSocket 的 JSON 消息协议。它将浏览器的各种能力划分为多个域(Domain),每个域定义了一组支持的命令(Commands)和事件(Events)。
协议架构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
┌──────────────────────────────────────────┐
│ Chrome / Chromium │
│ ┌────────────────────────────────┐ │
│ │ DOM Network Page ... │ │
│ └────────────────────────────────┘ │
│ ▲ │ │
│ │ ▼ │
│ ┌─────────────────────┐ │
│ │ CDP WebSocket │ │
│ └─────────────────────┘ │
└──────────────────────────────────────────┘
▲ │
│ ▼
┌──────────────────────────────────────┐
│ DevTools / Puppeteer / Playwright │
└──────────────────────────────────────┘
|
核心域(Domains)
CDP 包含 40+ 个域,常用的有:
| 域 |
说明 |
Page |
页面导航、截图、打印 |
Network |
网络请求拦截、Cookie 管理 |
DOM |
DOM 树操作、节点查询 |
Runtime |
JavaScript 执行、表达式求值 |
Debugger |
JavaScript 调试、断点管理 |
Emulation |
设备模拟、UA 伪装 |
Input |
鼠标、键盘输入模拟 |
Browser |
浏览器级别操作 |
启用 CDP
启动参数
要让 Chrome / Chromium 开启 CDP 服务,启动时加上 --remote-debugging-port 参数:
1
2
3
4
5
6
7
8
|
# 启动 Chrome 并开启 CDP,端口 9222
google-chrome --remote-debugging-port=9222
# 无头模式 + CDP
google-chrome --headless --remote-debugging-port=9222
# 指定 CDP 端口为 0,让浏览器自动选择可用端口
google-chrome --remote-debugging-port=0
|
在 browser-use demo002 中,我们用的就是这个参数:
1
2
3
|
browser_profile = BrowserProfile(
args=["--remote-debugging-port=9222"],
)
|
验证是否启用成功
浏览器启动后,访问 http://localhost:9222/json/version 可以看到版本信息:
1
2
3
4
5
6
7
|
{
"Browser": "Chrome/126.0.6478.0",
"Protocol-Version": "1.3",
"User-Agent": "Mozilla/5.0 ... Chrome/126.0...",
"webkit-Version": "537.36...",
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/..."
}
|
HTTP 端点
开启 CDP 后,同一端口会暴露多个 HTTP 端点,用于查询和操作浏览器:
GET /json/version
获取浏览器版本元数据:
1
2
3
4
5
|
{
"Browser": "Chrome/72.0.3601.0",
"Protocol-Version": "1.3",
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/..."
}
|
GET /json 或 /json/list
列出所有可用的 WebSocket Target(标签页):
1
2
3
4
5
6
7
8
9
10
11
|
[
{
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=localhost:9222/devtools/page/...",
"id": "DAB7FB6187B554E10B0BD18821265734",
"title": "Example Domain",
"type": "page",
"url": "https://example.com/",
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/page/DAB7FB6187B554E10B0BD18821265734"
}
]
|
GET /json/protocol/
获取当前浏览器支持的完整 CDP 协议定义(JSON 格式):
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"domains": [
{
"domain": "Accessibility",
"experimental": true,
"dependencies": ["DOM"],
"types": [...],
"commands": [...],
"events": [...]
}
]
}
|
PUT /json/new?{url}
打开新标签页,返回新 Tab 的 Target 数据:
1
|
curl -X PUT "http://localhost:9222/json/new?https://example.com"
|
GET /json/activate/{targetId}
将指定标签页置于前台(激活 Tab):
1
|
curl "http://localhost:9222/json/activate/DAB7FB6187B554E10B0BD18821265734"
|
GET /json/close/{targetId}
关闭指定标签页:
1
|
curl "http://localhost:9222/json/close/DAB7FB6187B554E10B0BD18821265734"
|
WebSocket 通信
真正的 CDP 操作是通过 WebSocket 连接完成的。每个 Target(标签页)都有一个对应的 WebSocket URL。
连接格式
1
2
3
|
ws://localhost:9222/devtools/page/{targetId}
# 或浏览器级别:
ws://localhost:9222/devtools/browser/{browserId}
|
消息格式
CDP 消息是 JSON 对象,包含 id(请求 ID)、method(方法名)和 params(参数):
发送命令:
1
2
3
4
5
6
7
|
{
"id": 1,
"method": "Page.navigate",
"params": {
"url": "https://example.com"
}
}
|
接收响应:
1
2
3
4
5
6
|
{
"id": 1,
"result": {
"frameId": "ABC123"
}
}
|
接收事件:
1
2
3
4
5
6
|
{
"method": "Page.loadEventFired",
"params": {
"timestamp": 12345678.123
}
}
|
Python 示例:通过 WebSocket 操作浏览器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import asyncio
import json
import websockets
async def main():
# 1. 获取 Target 列表
import urllib.request
targets = json.loads(urllib.request.urlopen(
"http://localhost:9222/json"
).read())
# 2. 连接到第一个 page 的 WebSocket
ws_url = targets[0]["webSocketDebuggerUrl"].replace("ws://", "ws://")
async with websockets.connect(ws_url) as ws:
# 3. 发送 Page.navigate 命令
await ws.send(json.dumps({
"id": 1,
"method": "Page.navigate",
"params": {"url": "https://example.com"}
}))
# 4. 接收响应
response = await ws.recv()
print("Response:", json.loads(response))
# 5. 等待加载完成事件
while True:
msg = json.loads(await ws.recv())
if msg.get("method") == "Page.loadEventFired":
print("Page loaded!")
break
asyncio.run(main())
|
使用场景
CDP 适用的场景非常广泛:
1. 浏览器自动化测试
Puppeteer、Playwright 等工具底层都使用 CDP 来控制浏览器,模拟用户操作、截图、生成 PDF 等。
2. 网页爬虫
通过 CDP 可以绕过一些反爬机制,获取动态渲染后的页面内容,拦截网络请求等。
3. 性能分析
使用 Profiler、Performance 等域,可以分析页面性能、内存使用等。
4. 远程调试
通过 CDP 可以远程调试设备上的 Chrome 浏览器,特别是移动端调试。
5. AI Agent 浏览器自动化
如 browser-use 这样的 AI Agent 工具,通过 CDP 让 LLM 能够"看到"并操作网页。
基于 CDP 的主流工具
Puppeteer(Node.js)
Google 官方出品,直接封装 CDP:
1
2
3
4
5
6
7
8
|
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
args: ['--remote-debugging-port=9222']
});
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
|
Playwright(Node.js / Python)
微软出品,支持 Chromium、Firefox、WebKit 多浏览器:
1
2
3
4
5
6
7
8
|
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com")
page.screenshot(path="screenshot.png")
browser.close()
|
Playwright 也支持直接通过 CDP Session 发送原始命令:
1
2
3
|
page = browser.new_page()
cdp_session = page.context.new_cdp_session(page)
cdp_session.send("Page.captureScreenshot", {"format": "jpeg"})
|
browser-use(Python)
AI 驱动的浏览器自动化工具,底层使用 Playwright(基于 CDP):
1
2
3
4
5
6
7
8
9
10
11
12
|
from browser_use import Agent, BrowserProfile
from browser_use.llm import ChatOpenAI
browser_profile = BrowserProfile(
args=["--remote-debugging-port=9222"],
)
agent = Agent(
task="打开 https://example.com 并告诉我页面标题",
llm=ChatOpenAI(model="gpt-4o"),
browser_profile=browser_profile,
)
await agent.run()
|
使用 Chrome Extension 调试协议
除了直接连接 WebSocket,还可以通过 Chrome Extension 的 chrome.debugger API 来使用 CDP:
1
2
3
4
5
6
7
8
9
10
11
|
// 在 Chrome 扩展中
chrome.debugger.attach({ targetId: tabId }, "1.3", function() {
chrome.debugger.sendCommand(
{ targetId: tabId },
"Page.captureScreenshot",
{ format: "jpeg" },
function(result) {
console.log("Screenshot captured!");
}
);
});
|
这种方式的好处是:不需要手动管理 WebSocket 连接,Chrome 扩展框架帮你处理请求和响应的绑定。
Chrome DevTools 本身也提供了查看和发送 CDP 命令的功能:
Protocol Monitor
- 打开 DevTools → 设置(齿轮图标)→ 实验功能
- 启用 Protocol Monitor
- 关闭并重新打开 DevTools
- 菜单 → More Tools → Protocol Monitor
在这里你可以:
- 实时查看所有 CDP 请求和响应
- 直接发送 CDP 命令
- 使用 CDP Editor 可视化编辑参数
还可以打开"DevTools on DevTools",在内部的 DevTools 控制台中使用:
1
2
3
4
5
6
7
|
let Main = await import('./front_end/entrypoints/main/main.js');
await Main.MainImpl.sendOverProtocol('Emulation.setDeviceMetricsOverride', {
mobile: true,
width: 412,
height: 732,
deviceScaleFactor: 2.625,
});
|
多客户端支持
从 Chrome 63 开始,CDP 支持多个客户端同时连接。这意味着你可以一边用 DevTools 调试,一边用 Puppeteer 做自动化,两者不会互相踢出。
当某个客户端断开连接时,会收到 Inspector.detached 事件:
1
2
3
4
5
6
|
{
"method": "Inspector.detached",
"params": {
"reason": "replaced_with_devtools"
}
}
|
常见问题
CDP 和 WebDriver 有什么区别?
| 特性 |
CDP |
WebDriver (Selenium) |
| 协议 |
私有 JSON/WebSocket |
W3C 标准 HTTP |
| 速度 |
快(直接连接浏览器) |
较慢(中间层多) |
| 功能 |
更底层、更完整 |
标准化、跨浏览器 |
| 浏览器支持 |
主要是 Chrome/Chromium |
多浏览器 |
如何获取协议定义文件?
协议定义在 Chromium 源码中:
browser_protocol.pdl:浏览器相关
js_protocol.pdl:V8 相关
GitHub 镜像:https://github.com/ChromeDevTools/devtools-protocol
也可以通过 http://localhost:9222/json/protocol/ 获取当前浏览器支持的协议。
--remote-debugging-port=0 是什么?
指定端口为 0 时,Chrome 会自动选择一个可用的端口。端口号会写入:
- stderr 输出
- 用户数据目录下的
DevToolsActivePort 文件
总结
CDP 是连接人类和 Chrome 浏览器的桥梁,它让程序能够像人一样操作浏览器。从基本的页面导航、截图,到复杂的 AI Agent 自动化,CDP 提供了完整的能力支持。
核心要点:
- CDP 是基于 WebSocket 的 JSON 协议,分为 40+ 个功能域
- 通过
--remote-debugging-port 启动参数开启
- HTTP 端点提供 Target 管理和协议查询
- WebSocket 端点是实际通信的通道
- Puppeteer、Playwright、browser-use 等工具都基于 CDP
- Chrome 63+ 支持多客户端同时连接
无论你是想做浏览器自动化测试、网页爬虫,还是开发 AI Agent,理解 CDP 都是非常有价值的基础。
参考链接