Chrome DevTools Protocol
Chrome DevTools Protocol(CDP)是 Pydoll 用来控制浏览器的接口。它就是当你检视一个页面时 Chrome DevTools 所使用的同一套协议,只不过以可编程的 API 形式暴露出来。理解它,就能明白 Pydoll 的各项能力从何而来,以及为什么这里根本没有 webdriver。
CDP 是什么
CDP 是一套以编程方式控制基于 Chromium 的浏览器的协议。消息是 JSON,通过 WebSocket 发送,并按 domain 组织,每个 domain 各自覆盖浏览器的一个领域:Page 负责导航与生命周期,DOM 负责页面结构,Network 负责流量,Runtime 负责 JavaScript,Input 负责鼠标和键盘,Fetch 负责请求拦截,Target 负责标签页和上下文,等等。
CDP 由 Google 维护,并随每个 Chrome 版本不断扩展。因为它是为驱动 Chrome 自家的 DevTools 而生的,所以它深入浏览器内部,这也正是它成为 Puppeteer、Playwright 和 Pydoll 等自动化工具基石的原因。
Pydoll 直接讲 CDP,所以它的能力就是 CDP 所暴露的一切。没有一个单独的自动化层来决定你能做什么、不能做什么。
连接是如何工作的
用远程调试标志启动一个 Chromium 浏览器,它就会在该端口上打开一个 WebSocket 服务器:
Pydoll 连接到那个 WebSocket,并在整个会话期间保持连接打开。这个通道是双向的:你的代码向浏览器发送命令,浏览器则在事件发生的瞬间通过同一条连接把它们推回给你。
sequenceDiagram
participant App as 你的代码
participant WS as WebSocket
participant Browser as Chrome
App ->> WS: command: Page.navigate
WS ->> Browser: 执行它
Browser -->> WS: event: Page.loadEventFired
WS -->> App: 投递该事件
对自动化而言,一条持久的 WebSocket 比旧协议所用的请求/响应式 HTTP 端点更合适:浏览器会在事情发生的那一刻通知你,而不用你去轮询才能知道。
Domain
CDP 把它的方法和事件按 domain 分组。在自动化中你最常遇到的有:
| Domain | 覆盖范围 | 示例用途 |
|---|---|---|
| Browser | 浏览器应用本身 | 窗口管理、创建浏览器上下文 |
| Page | 页面生命周期 | 导航、运行 JavaScript、frame |
| DOM | 页面结构 | 查询元素、读取和设置属性 |
| Network | 流量 | 观察请求和响应、缓存 |
| Runtime | JavaScript 引擎 | 求值表达式、调用函数 |
| Input | 用户输入 | 鼠标移动、键盘、触摸 |
| Target | 标签页和上下文 | 打开标签页、访问 iframe、处理弹窗 |
| Fetch | 底层拦截 | 修改请求、模拟响应、认证 |
Pydoll 把这些 domain 映射为一套更友好的 API,所以 tab.go_to(...) 会发送一条 Page.navigate 命令,tab.find(...) 会使用 DOM 查询,而无需你去拼装原始消息。
命令与事件
每一次 CDP 交互都是两种消息类型之一。
命令(command) 是你发出的一个请求:一个带参数的 domain 方法。浏览器执行它,并回复一个结果,通过 id 与你的消息匹配。Page.navigate、DOM.getDocument 和 Input.dispatchMouseEvent 都是命令。
事件(event) 是浏览器在你启用其 domain 之后主动发送的一个通知。Page.loadEventFired、Network.requestWillBeSent 和 Fetch.requestPaused 都是事件。你用一个回调来订阅它,并在它触发时作出反应:
from functools import partial
from pydoll.protocol.network.events import NetworkEvent
async def on_request(tab, event):
url = event['params']['request']['url']
print(f'request to: {url}')
await tab.enable_network_events()
await tab.on(NetworkEvent.REQUEST_WILL_BE_SENT, partial(on_request, tab))
正是事件让基于 CDP 的自动化能在浏览器状态改变的那一刻作出反应,而不是靠睡眠等待再碰运气。可用的工作指南见 事件。
Target 与 session
CDP 把你能附着的每样东西称作一个 target:浏览器本身、每个标签页,以及进程外的 iframe,都是各自独立的 target。附着到一个 target 会打开一个 session,而针对该 target 的命令会携带它的 sessionId,好让浏览器知道该把它们路由到哪里。
这就是一条 WebSocket 连接如何同时驱动众多标签页的方式,也是命令如何到达一个跨源 iframe 内部元素的方式。Pydoll 为你处理好了 target 和 session 的路由,所以一个 Tab 对象无需你追踪 session id 就能工作。
为什么这里没有 webdriver
传统的 webdriver 工具在你的代码和浏览器之间放了一个翻译服务器:
graph LR
A[你的脚本] --> C[WebDriver 客户端]
C --> S[WebDriver 服务器]
S --> B[浏览器]
这个服务器把 WebDriver 协议翻译成浏览器的原生调用,而它正是你必须安装、并要与你的浏览器版本对齐的那一块。Pydoll 则直接与浏览器对话:
graph LR
A[你的脚本] --> P[Pydoll]
P --> B[通过 CDP 的浏览器]
没有一个单独的驱动需要下载或保持同步,而且这条连接就是浏览器内部使用的、同一条事件驱动的通道。这对你编写脚本意味着什么,见 核心概念。