跳转到内容

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 ActivateTargetParameters
await 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 标记重连中状态
  • 重连事件:BrowserReconnectingEventBrowserReconnectedEvent

Target 生命周期管理

问题:标签页关闭后,对应的 CDP Session 仍然持有引用。

对策

  • TabClosedEvent 触发时清理对应 Session
  • SessionManager(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 的原生协议,性能更高
  • 代价是需要自行处理更多边界情况

参考来源