CDP 协议封装 — typed CDP 客户端与会话管理
学习目标
- 理解 cdp-use 的设计理念和 API 风格
- 掌握 CDP 会话管理和多目标(tab/iframe/worker)连接
- 了解为什么选择 CDP 而非 Playwright
项目实践
cdp-use 库
Browser Use 使用 cdp-use 作为 CDP 底层封装。cdp-use 提供浅层类型的 WebSocket 调用接口,所有 CDP 客户端管理逻辑在 browser_use/browser/session.py 中。
API 风格:
# 方法调用:cdp_client.send.DomainHere.methodNameHere(params=...)await cdp_client.send.DOMSnapshot.enable(session_id=session_id)await cdp_client.send.Page.navigate(params={'url': url, 'frameId': frame_id})
# 使用强类型参数(可选但推荐)from cdp_use.cdp.target import ActivateTargetParametersawait cdp_client.send.Target.attachToTarget( params=ActivateTargetParameters(targetId=target_id, flatten=True))
# 事件注册:cdp_client.register.Domain.eventName(callback_func)cdp_client.register.Browser.downloadWillBegin(callback_func)关键设计:
- 使用
cdp_client.register而非cdp_client.on()注册事件回调 - 参数类型可选,但推荐使用
cdp_use.cdp.*中的强类型参数模型 - 所有方法都是异步的,返回
awaitable
TimeoutWrappedCDPClient
为所有 CDP 调用添加超时保护:
class TimeoutWrappedCDPClient: """包装 CDPClient,为所有调用添加超时""" def __init__(self, cdp_client: CDPClient, timeout: float = 10.0): self._cdp_client = cdp_client self._timeout = timeout默认 CDP 超时比动作级别超时更短(10s vs 180s),因为单个 CDP 调用不应该挂起那么久。
Target 与 CDPSession 模型
class Target(BaseModel): """浏览器目标(页面、iframe、worker)""" target_id: TargetID target_type: str # 'page', 'iframe', 'worker' url: str = 'about:blank' title: str = 'Unknown title'
class CDPSession(BaseModel): """到目标的 CDP 通信通道""" cdp_client: CDPClient target_id: TargetID session_id: SessionID多目标管理:
- 一个 CDPClient 连接到浏览器进程
- 通过
Target.attachToTarget为每个目标创建独立会话 - iframe 和 worker 作为独立目标处理
CDP 核心操作流程
页面导航:
# 1. 导航await cdp_client.send.Page.navigate(params={...})# 2. 等待加载完成(通过事件)await cdp_client.register.Page.loadEventFired(callback)# 3. 等待网络空闲await asyncio.sleep(wait_for_network_idle_page_load_time)DOM 快照:
# 1. 启用 DOM 域await cdp_client.send.DOMSnapshot.enable(session_id=session_id)# 2. 捕获快照result = await cdp_client.send.DOMSnapshot.captureSnapshot( session_id=session_id, params={'includeDOMRects': True, 'includePaintOrder': True})# 3. 解析为 EnhancedDOMTreeNode 树截图:
result = await cdp_client.send.Page.captureScreenshot( session_id=session_id, params={'format': 'png', 'captureBeyondViewport': False})screenshot_b64 = result['data']问题与规避
CDP 连接断开
问题:浏览器崩溃或网络问题导致 WebSocket 断开。
对策:
CrashWatchdog检测连接断开- 重连机制:
BrowserSession.is_reconnecting标记重连中状态 - 重连事件:
BrowserReconnectingEvent→BrowserReconnectedEvent
Target 生命周期管理
问题:标签页关闭后,对应的 CDP Session 仍然持有引用。
对策:
TabClosedEvent触发时清理对应 SessionSessionManager(cdp-use 内部)管理 Session 生命周期
CDP 协议版本差异
问题:不同 Chrome 版本的 CDP 协议可能有差异。
对策:
- cdp-use 跟随 Chrome Canary 的最新协议定义
- 兼容性通过测试矩阵验证
设计取舍
CDP vs Playwright
| 方案 | 优势 | 代价 |
|---|---|---|
| CDP(当前) | 更底层、更灵活、支持 iframe/worker | 需手动处理更多边界情况 |
| Playwright | 高级 API、跨浏览器 | 对 iframe/worker 支持有限 |
Browser Use 选择 CDP 的原因:
- 浏览器自动化 Agent 需要精确控制 DOM(iframe 内元素、Shadow DOM)
- CDP 是 Chrome 的原生协议,性能更高
- 代价是需要自行处理更多边界情况