第1章 迎接AI浏览器自动化的新范式
当浏览器不再只是"打开网页"的工具,而成为AI Agent的"手"与"眼",一切才刚刚开始。
如果你在过去一年里关注过AI领域的动态,一定对这样的场景不陌生:一个AI助手自主打开浏览器,搜索信息、填写表单、提交订单,全程无需人类干预。这不再是科幻——AutoGPT、Browser Use、WebVoyager等项目已经在GitHub上斩获数万Star,而它们背后都指向同一个核心能力:对浏览器的程序化控制。
然而,当大多数教程还在教你用Selenium find_element_by_id 的时候,真正的底层玩家已经在用一种更强大、更精细、更贴近浏览器本质的方式操控一切——Chrome DevTools Protocol(CDP)。
本章将带你从零理解CDP的来龙去脉,搭建开发环境,并写出你的第一个CDP程序。系好安全带,我们出发。
1.1 从Selenium到CDP:AI时代浏览器操控的进化
浏览器自动化的三代演进
浏览器自动化并非新概念。回溯其发展脉络,大致经历了三个阶段:
| 世代 | 代表技术 | 核心机制 | 优势 | 局限 |
|---|---|---|---|---|
| 第一代 | Selenium RC | 注入JS沙箱 + 代理服务器 | 跨浏览器支持 | 速度慢,沙箱限制多 |
| 第二代 | WebDriver (Selenium 2+) | 浏览器扩展/Native事件 | W3C标准,生态成熟 | 仍需中间驱动层 |
| 第三代 | CDP / Playwright / Puppeteer | 直接WebSocket通信 | 零中间层,完全控制 | 仅限Chromium系 |
第一代:Selenium RC(Remote Control) 诞生于2004年,它的原理是在浏览器中注入一段JavaScript沙箱,通过代理服务器转发命令。这意味着你的操作实际上是在一个"被劫持"的JS环境中运行——任何同源策略、跨域限制都可能成为绊脚石。
第二代:WebDriver 是Selenium 2引入的革命性改进,它成为了W3C推荐标准(WebDriver Spec)。每个浏览器厂商提供自己的Driver(如ChromeDriver、geckodriver),通过HTTP协议接收指令,再转化为浏览器的原生操作。这解决了JS沙箱的问题,但引入了新的复杂性:每条命令都要经过 Client → HTTP → Driver → Browser 的漫长链路。
第三代:CDP直接通信 则是另一个维度的飞跃。它跳过所有中间层,直接通过WebSocket与浏览器的调试端口通信,命令延迟从百毫秒级降到毫秒级,同时获得了对浏览器内部状态的完全访问权——网络请求、渲染帧、内存堆栈,一切尽在掌握。
📊 图示位置:浏览器自动化三代架构对比图
- 左侧:Selenium RC架构(Client → Proxy Server → JS Sandbox → Browser)
- 中间:WebDriver架构(Client → HTTP → ChromeDriver → Browser)
- 右侧:CDP架构(Client → WebSocket → Browser DevTools Port)
- 箭头越少,延迟越低
为什么AI时代需要CDP?
Selenium够用吗?对于传统的"登录→点击→验证"测试流程,或许足够。但AI时代的浏览器自动化,需求已经发生了根本性变化:
- 实时感知:AI Agent需要实时获取页面状态(DOM结构、网络请求、渲染结果),而不仅仅是"找到某个元素"
- 精细控制:模拟真实用户行为需要控制鼠标轨迹、键盘事件时序,而非简单的
click() - 性能监控:AI决策需要页面性能数据作为输入,而Selenium无法直接获取渲染指标
- 多上下文管理:AI Agent可能同时操控多个标签页、多个浏览器实例,需要轻量的会话管理
- 反检测需求:AI自动化需要隐藏自身痕迹,CDP提供了修改浏览器指纹的底层能力
来看一个直观的对比——同样是获取页面的所有网络请求:
// Selenium方式:无法直接获取,需要借助第三方代理(如BrowserMob Proxy)
const proxy = new BrowserMobProxy();
proxy.start(8080);
proxy.newHar("network");
// ... 执行操作 ...
const har = proxy.getHar(); // 只能拿到HAR格式的摘要数据// CDP方式:直接监听网络事件
const client = await CDP();
const { Network } = client;
await Network.enable();
Network.requestWillBeSent((params) => {
console.log(`${params.request.method} ${params.request.url}`);
});
// 每个请求的详细信息实时获取,包括headers、timing、body差距不言而喻。
AI Agent的浏览器控制范式
当前主流的AI浏览器自动化项目,几乎都建立在CDP之上:
- Puppeteer:Google官方的Node.js CDP封装,AI项目的事实标准
- Playwright:微软出品的跨浏览器自动化框架,底层同样使用CDP(Chromium通道)
- Browser Use:直接基于Playwright的AI Agent框架,让LLM自主决策浏览器操作
- LaVague:结合CDP与RAG技术的Web Agent,支持自然语言驱动浏览器
这种范式可以抽象为一个简单模型:
感知(CDP Event)→ 推理(LLM)→ 行动(CDP Method)→ 感知(CDP Event)→ ...CDP既是AI的"眼睛"(通过Event获取页面状态),也是AI的"手"(通过Method执行操作)。理解CDP,就是理解AI操控浏览器的底层逻辑。
1.2 Chrome DevTools Protocol的核心价值与架构解析
什么是Chrome DevTools Protocol?
Chrome DevTools Protocol(CDP)是Chrome浏览器提供的调试协议,它允许外部程序通过WebSocket连接与浏览器通信,控制浏览器的行为并获取其内部状态。
这个名字可能会让人误解——"DevTools"是不是只能用于开发者工具?实际上,CDP的功能远超DevTools界面所展示的范畴。你平时在Chrome DevTools中看到的一切功能(Elements面板、Console、Network面板、Performance面板……),都是通过CDP实现的。反过来说,CDP的能力也远超DevTools界面,因为很多底层API并没有在UI中暴露。
CDP架构全景
CDP的架构可以用一个三层模型来描述:
┌─────────────────────────────────────────┐
│ Application Layer │
│ (Puppeteer / Playwright / 自定义客户端) │
├─────────────────────────────────────────┤
│ Transport Layer │
│ (WebSocket / JSON-RPC) │
├─────────────────────────────────────────┤
│ Browser Layer │
│ (Chrome DevTools Backend / CDP Server)│
└─────────────────────────────────────────┘传输层:CDP基于WebSocket传输,消息格式采用JSON-RPC 2.0。每条消息要么是请求(Request),要么是响应(Response),要么是事件通知(Event)。
浏览器层:Chrome在启动时可以通过--remote-debugging-port参数开放调试端口。连接建立后,浏览器内部有一个CDP Server,负责将协议命令分发给对应的处理模块。
应用层:你可以直接使用WebSocket客户端与CDP通信,也可以使用高级封装库(如Puppeteer、Playwright)。后者隐藏了WebSocket和JSON-RPC的细节,提供更友好的API。
Domain:CDP的功能组织单元
CDP将所有功能组织为若干Domain(域),每个Domain负责一类相关功能。以下是核心Domain的概览:
| Domain | 职责 | 典型用途 |
|---|---|---|
Target | 管理浏览器中的调试目标(标签页、iframe等) | 发现和附加到指定页面 |
Page | 页面级操作 | 导航、截图、打印PDF |
Runtime | JavaScript执行环境 | 执行JS表达式、监听Console |
DOM | DOM树操作 | 查询节点、修改属性 |
CSS | 样式操作 | 获取/修改计算样式 |
Network | 网络监控与拦截 | 监听请求、修改响应 |
Emulation | 设备模拟 | 模拟移动设备、地理位置 |
Performance | 性能数据采集 | 获取指标、录制Trace |
Security | 安全状态 | 处理证书错误 |
DOMDebugger | DOM断点 | 设置事件监听断点 |
Debugger | JS调试 | 设置断点、单步执行 |
Profiler | 性能分析 | CPU Profile、堆快照 |
IO | 输入输出 | 流式读取大数据 |
Log | 日志监听 | 获取控制台日志 |
Fetch | 网络请求拦截 | 修改请求/响应内容 |
每个Domain下包含三类成员:
- Method(方法):可调用的命令,如
Page.navigate - Event(事件):浏览器主动推送的通知,如
Network.requestWillBeSent - Type(类型):结构化的数据类型定义,如
Network.Request
Method、Event与Type的协作
以一个完整的"导航到URL并等待加载完成"为例,展示三者的协作:
// 1. 启用Page域(必须先enable才能接收事件)
await Page.enable();
// 2. 监听加载完成事件(Event)
Page.loadEventFired(() => {
console.log('页面加载完成!');
});
// 3. 调用导航方法(Method)
await Page.navigate({ url: 'https://www.baidu.com' });这个过程体现了CDP的典型模式:先enable → 再监听event → 然后调用method。几乎所有Domain都遵循这一模式。
CDP消息格式
所有CDP消息都遵循JSON-RPC 2.0格式。来看实际的请求与响应:
请求(调用Method):
{
"id": 1,
"method": "Page.navigate",
"params": {
"url": "https://www.baidu.com"
}
}响应:
{
"id": 1,
"result": {
"frameId": "main",
"loaderId": "1234567890"
}
}事件通知:
{
"method": "Page.loadEventFired",
"params": {
"timestamp": 1718294400000
}
}注意关键区别:请求和响应用id字段关联,而事件通知没有id(因为它们不是对某个请求的响应,而是浏览器主动推送的)。
Session:多目标通信的关键
当浏览器中有多个标签页时,CDP需要一种机制来区分命令应该发送到哪个目标。这就是Session的概念。
// 1. 获取所有目标
const { targetInfos } = await Target.getTargets();
// 2. 附加到特定目标,创建Session
const { sessionId } = await Target.attachToTarget({
targetId: targetInfos[0].targetId,
flatten: true // 使用"扁平化"Session(推荐)
});
// 3. 在该Session下发送命令
await cdp.send('Page.navigate', { url: 'https://example.com' }, sessionId);扁平化Session(flatten: true)是CDP 1.3引入的重要特性,它允许在同一条WebSocket连接上通过sessionId区分不同目标的消息,避免了为每个目标创建独立WebSocket连接的开销。
1.3 CDP与Chrome浏览器版本的兼容性矩阵
版本对应关系
CDP与Chrome版本是强绑定的——每个Chrome大版本对应一组CDP API。Chrome每6周发布一个大版本,CDP也会随之更新。
CDP采用语义化版本号,格式为major.minor:
| Chrome版本 | CDP版本 | 关键新增特性 |
|---|---|---|
| 90 | 1.3 | Fetch域、扁平化Session |
| 100 | 1.3 | FedCm域(联邦登录管理) |
| 110 | 1.3 | Preload域、Storage域扩展 |
| 120 | 1.3 | Extensions域(扩展管理) |
| 125+ | 1.3 | AuctionWorklet域(Privacy Sandbox) |
🔗 完整版本列表:CDP Version History
实验性API与稳定API
CDP中的Method和Event分为两类:
- 稳定(Stable):已正式发布,向后兼容,可以安全用于生产环境
- 实验性(Experimental):标记为
"experimental": true,可能在后续版本中变更或移除
在官方文档中,实验性API会以特殊标识显示:
{
"name": "Page.adScriptStarted",
"experimental": true,
"description": "..."
}实践建议:
- 生产环境优先使用稳定API,实验性API需要做降级处理
- 定期查看CDP变更日志,关注Breaking Changes
- 在Puppeteer/Playwright中,实验性API通常会被封装并标注为实验性
查询特定版本的CDP信息
你可以通过以下方式查询特定Chrome版本支持的CDP API:
# 方法1:通过Chrome的JSON端点
curl http://localhost:9222/json/version
# 返回包含webSocketDebuggerUrl和Browser版本信息// 方法2:通过CDP协议查询
const { Browser } = client;
const version = await Browser.getVersion();
console.log(version.product); // Chrome/126.0.6478.114
console.log(version.userAgent); // 完整UA字符串兼容性实战策略
在AI自动化项目中,兼容性问题通常出现在以下场景:
- Docker部署:Docker镜像中的Chrome版本可能与本地不同
- CI/CD环境:GitHub Actions等CI环境中的Chrome版本会自动更新
- 远程浏览器:BrowserPool等远程浏览器服务的版本可能滞后
推荐的做法是锁定Chrome版本,而非追逐最新版:
# Dockerfile示例:锁定Chrome版本
FROM node:20-slim
# 安装指定版本的Chrome
RUN wget -q https://dl.google.com/linux/direct/google-chrome-stable_126.0.6478.114-1_amd64.deb \
&& dpkg -i google-chrome-stable_126.0.6478.114-1_amd64.deb \
&& rm google-chrome-stable_126.0.6478.114-1_amd64.deb对于无法锁定版本的场景,可以使用Feature Detection模式:
// 检查某个Method是否可用
async function isMethodSupported(client, domain, method) {
try {
// 获取域的描述信息
const { domains } = await client.send('Schema.getDomains');
const targetDomain = domains.find(d => d.name === domain);
return !!targetDomain;
} catch {
return false;
}
}1.4 开发环境搭建:Node.js/Python环境与调试配置
Node.js环境搭建
Node.js是CDP开发的首选环境——毕竟Puppeteer是Google官方的Node.js库,生态最完善。
1. 安装Node.js
推荐使用nvm管理Node.js版本:
# 安装nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# 安装LTS版本
nvm install --lts
nvm use --lts
# 验证
node --version # v20.x 或更高
npm --version🔗 nvm官方仓库:https://github.com/nvm-sh/nvm
2. 创建项目
mkdir cdp-demo && cd cdp-demo
npm init -y
# 安装核心依赖
npm install chrome-remote-interface # CDP底层客户端
npm install puppeteer # 高级封装(自带Chrome)chrome-remote-interface是最接近CDP协议原生的Node.js客户端,适合学习协议细节;puppeteer是高级封装,适合实际项目开发。本章后续会同时使用两者。
3. TypeScript配置(可选但推荐)
npm install -D typescript @types/node tsx
npx tsc --init修改tsconfig.json关键配置:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"outDir": "./dist"
}
}Python环境搭建
Python生态中,CDP开发主要通过pychrome或playwright进行。
1. 创建虚拟环境
# 使用venv
python3 -m venv cdp-env
source cdp-env/bin/activate # macOS/Linux
# cdp-env\Scripts\activate # Windows
# 安装依赖
pip install pychrome # CDP底层客户端
pip install playwright # 高级封装
python -m playwright install chromium # 安装浏览器🔗 pychrome仓库:https://github.com/nicholasgasior/pychrome
🔗 Playwright Python文档:https://playwright.dev/python/
Chrome调试模式启动
无论使用哪种语言,都需要以调试模式启动Chrome:
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-debug
# Linux
google-chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-debug \
--no-first-run
# Windows
"C:\Program Files\Google\Chrome\Application\chrome.exe" ^
--remote-debugging-port=9222 ^
--user-data-dir=%TEMP%\chrome-debug关键参数说明:
| 参数 | 作用 | 说明 |
|---|---|---|
--remote-debugging-port=9222 | 开启调试端口 | 默认9222,可自定义 |
--user-data-dir | 指定用户数据目录 | 避免与日常浏览器的Profile冲突 |
--no-first-run | 跳过首次运行向导 | 自动化场景必加 |
--headless=new | 无头模式 | 服务器部署必备(新版Headless) |
--disable-gpu | 禁用GPU加速 | 部分Linux环境需要 |
--window-size=1920,1080 | 设置窗口大小 | 影响视口和截图尺寸 |
验证调试端口
启动Chrome后,访问调试端点验证是否成功:
# 获取浏览器版本信息
curl http://localhost:9222/json/version
# 获取所有可调试的页面
curl http://localhost:9222/json/list成功的响应应该类似:
{
"Browser": "Chrome/126.0.6478.114",
"Protocol-Version": "1.3",
"User-Agent": "Mozilla/5.0 ...",
"V8-Version": "12.6.228.9",
"WebKit-Version": "537.36",
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/xxx"
}📊 图示位置:CDP开发环境架构图
- 上方:开发者IDE / 终端
- 中间:Node.js / Python 进程(CDP Client)
- 下方:Chrome进程(调试端口9222)
- 连接线:WebSocket通信
调试技巧:Chrome DevTools Inspector
Chrome本身提供了一个Web界面的DevTools Inspector,可以直接在浏览器中调试CDP连接:
- 启动调试模式的Chrome
- 在另一个浏览器窗口中访问
http://localhost:9222 - 你会看到所有可调试的页面列表
- 点击任意页面,会打开一个完整的DevTools窗口
这在开发过程中非常实用——你可以同时看到CDP命令的执行效果和DevTools中的状态变化。
1.5 第一个CDP程序:远程控制浏览器打开百度
理论够多了,现在让我们动手写代码。我们将用三种方式实现同一个目标:远程控制Chrome浏览器打开百度首页。
方式一:使用chrome-remote-interface(底层方式)
这是最接近CDP协议原生的实现,适合理解协议细节。
// hello-cdp.js
const CDP = require('chrome-remote-interface');
async function main() {
// 1. 建立CDP连接
const client = await CDP({
host: 'localhost',
port: 9222
});
// 2. 解构获取需要的Domain
const { Page, Runtime, Network } = client;
// 3. 启用必要的Domain
await Promise.all([
Page.enable(),
Network.enable()
]);
// 4. 监听页面加载事件
Page.loadEventFired(() => {
console.log('✅ 页面加载完成!');
// 获取页面标题
Runtime.evaluate({ expression: 'document.title' }).then(result => {
console.log(`📄 页面标题: ${result.result.value}`);
});
});
// 5. 监听网络请求
Network.requestWillBeSent((params) => {
console.log(`🌐 ${params.request.method} ${params.request.url}`);
});
// 6. 导航到百度
await Page.navigate({ url: 'https://www.baidu.com' });
console.log('🚀 正在导航到百度...');
}
main().catch(console.error);运行:
# 先确保Chrome已在调试模式下运行
node hello-cdp.js输出示例:
🚀 正在导航到百度...
🌐 GET https://www.baidu.com
🌐 GET https://www.baidu.com/favicon.ico
✅ 页面加载完成!
📄 页面标题: 百度一下,你就知道方式二:使用Puppeteer(高级封装方式)
Puppeteer隐藏了CDP的底层细节,同时暴露了page.createCDPSession()方法,允许你在需要时直接访问CDP。
// hello-puppeteer.js
const puppeteer = require('puppeteer');
async function main() {
// 1. 连接到已有的Chrome实例
const browser = await puppeteer.connect({
browserURL: 'http://localhost:9222'
});
// 2. 创建新页面
const page = await browser.newPage();
// 3. 创建CDP Session(可选,用于高级操作)
const cdpSession = await page.createCDPSession();
// 4. 监听网络请求(通过CDP Session)
cdpSession.on('Network.requestWillBeSent', (params) => {
console.log(`🌐 ${params.request.method} ${params.request.url}`);
});
await cdpSession.send('Network.enable');
// 5. 导航到百度
await page.goto('https://www.baidu.com', {
waitUntil: 'networkidle2' // 等待网络空闲
});
// 6. 获取页面标题
const title = await page.title();
console.log(`📄 页面标题: ${title}`);
// 7. 截图保存
await page.screenshot({ path: 'baidu.png' });
console.log('📸 截图已保存为 baidu.png');
}
main().catch(console.error);🔗 Puppeteer官方文档:https://pptr.dev/
方式三:使用Python + pychrome
# hello_cdp.py
import pychrome
def on_request(params):
request = params.get('request', {})
print(f"🌐 {request.get('method', '?')} {request.get('url', '?')}")
def main():
# 1. 创建浏览器连接
browser = pychrome.Browser(url="http://localhost:9222")
# 2. 获取或创建标签页
tab = browser.list_tab()[0]
tab.start()
# 3. 启用Network域并监听事件
tab.call_method("Network.enable")
tab.on("Network.requestWillBeSent", on_request)
# 4. 导航到百度
tab.call_method("Page.navigate", url="https://www.baidu.com")
tab.wait(3) # 等待页面加载
# 5. 获取页面标题
result = tab.call_method("Runtime.evaluate", expression="document.title")
print(f"📄 页面标题: {result.get('result', {}).get('value', '未知')}")
tab.stop()
if __name__ == "__main__":
main()三种方式对比
| 维度 | chrome-remote-interface | Puppeteer | pychrome |
|---|---|---|---|
| 协议透明度 | ⭐⭐⭐ 直接操作CDP | ⭐⭐ 封装后可降级 | ⭐⭐⭐ 直接操作CDP |
| 学习曲线 | 中等 | 低 | 中等 |
| 文档完善度 | 一般 | 优秀 | 较少 |
| 生态与社区 | 小众 | 庞大 | 小众 |
| 适合场景 | 学习CDP协议、定制化开发 | 生产项目、AI Agent | Python生态集成 |
| 自动管理Chrome | ❌ 需手动启动 | ✅ 可自动启动 | ❌ 需手动启动 |
初学者建议:先从Puppeteer入手快速上手,遇到封装不足的场景时降级到cdpSession.send()直接调用CDP Method。等对协议理解深入后,再尝试chrome-remote-interface做更精细的控制。
一个更完整的示例:搜索百度
让我们在第一个CDP程序的基础上,增加一个实际操作——在百度搜索框中输入内容并搜索:
// baidu-search.js
const CDP = require('chrome-remote-interface');
async function main() {
const client = await CDP({ port: 9222 });
const { Page, Runtime, DOM, Input } = client;
await Promise.all([Page.enable(), DOM.enable()]);
// 导航到百度
Page.loadEventFired(async () => {
console.log('✅ 百度页面加载完成');
// 1. 找到搜索框并聚焦
const { root } = await DOM.getDocument();
const { nodeId } = await DOM.querySelector({
nodeId: root.nodeId,
selector: '#kw'
});
// 2. 设置搜索框的值
await DOM.setAttributeValue({
nodeId,
name: 'value',
value: 'Chrome DevTools Protocol'
});
// 3. 模拟输入事件(让百度感知到输入)
await Runtime.evaluate({
expression: `
const input = document.querySelector('#kw');
input.dispatchEvent(new Event('input', { bubbles: true }));
`
});
// 4. 点击搜索按钮
const { nodeId: btnNodeId } = await DOM.querySelector({
nodeId: root.nodeId,
selector: '#su'
});
// 获取按钮的位置坐标
const { model } = await DOM.getBoxModel({ nodeId: btnNodeId });
const x = (model.border[0] + model.border[2]) / 2;
const y = (model.border[1] + model.border[5]) / 2;
// 5. 模拟鼠标点击
await Input.dispatchMouseEvent({
type: 'mousePressed', x, y, button: 'left', clickCount: 1
});
await Input.dispatchMouseEvent({
type: 'mouseReleased', x, y, button: 'left', clickCount: 1
});
console.log('🔍 已搜索: Chrome DevTools Protocol');
});
await Page.navigate({ url: 'https://www.baidu.com' });
}
main().catch(console.error);这个示例展示了CDP的核心操作流程:查找DOM节点 → 获取位置信息 → 模拟用户输入。虽然比Puppeteer的page.type()和page.click()繁琐,但你获得了对每一步的完全控制——这正是CDP的价值所在。
常见问题与排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
ECONNREFUSED | Chrome未启动或端口错误 | 检查Chrome是否带--remote-debugging-port启动 |
TypeError: Cannot read property 'nodeId' | 元素未找到 | 确保在loadEventFired后再操作DOM |
| 连接成功但无响应 | Domain未enable | 先调用Domain.enable()再操作 |
| 多个Chrome实例冲突 | 调试端口被占用 | 使用不同的--user-data-dir和端口 |
| 截图空白 | 页面未渲染完成 | 使用waitUntil: 'networkidle0'等待 |
本章小结
这一章我们从浏览器自动化的演进史出发,理解了CDP为何成为AI时代浏览器控制的核心技术。让我们回顾关键要点:
- CDP是第三代浏览器自动化技术,通过WebSocket直接通信,延迟更低、控制更精细
- CDP以Domain为功能组织单元,核心Domain包括Target、Page、DOM、Runtime、Network等
- CDP消息遵循JSON-RPC 2.0格式,分为Method调用、Event通知和Type定义三类
- 开发环境搭建需要Node.js/Python + 调试模式Chrome,Puppeteer是最推荐的上手工具
- 第一个CDP程序只需5步:连接 → enable → 监听 → 调用 → 处理结果
下一章,我们将深入CDP的协议细节,理解Domain、Method与Event的协作机制,掌握构建稳定CDP应用的核心技能。
💡 动手练习:尝试修改第一个CDP程序,让它打开GitHub并获取页面中所有仓库链接。提示:使用
DOM.querySelectorAll和Runtime.evaluate两种方式分别实现,体会它们的差异。