拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Puppeteer CDPSession 深入解析:用原始 Chrome DevTools Protocol 直接驱动浏览器

Puppeteer CDPSession 深入解析用原始 Chrome DevTools Protocol 直接驱动浏览器【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer本文围绕 Puppeteer 的 CDPSession 类展开讲解它如何在高层 API 之外让你以原始 CDP方式直接调用 Chrome DevTools Protocol 的任意命令、订阅任意协议事件。读完本文你将掌握page.createCDPSession()的获取方式、send()/on()/detach()/id()/connection()的完整用法与参数语义并基于当前仓库源码理解一次 CDP 命令从发出到响应、协议事件从接收到的分发全过程包括超时、断开会话等边界情况的行为。CDPSession 的定位通往原始协议的门面CDPSession 实例用于直接与 Chrome Devtools ProtocolCDP通信。其类型签名为export declare abstract class CDPSession extends EventEmitterCDPSessionEvents两个关键设计可以从签名中读出它是抽象类CDPSession本身不含实现Puppeteer 内部按浏览器后端提供具体实现类。第三方代码不应直接调用其构造函数也不应继承它构造函数在文档与源码中均被标记为 internal而应通过Page/Target提供的工厂方法获取实例它继承 EventEmitter 且泛型为 CDPSessionEvents这意味着任意 CDP 协议事件都可以通过标准的on/off/once事件订阅机制被监听事件类型表由协议映射自动展开send()发起的命令响应与协议事件共用同一套会话对象。在仓库中公开抽象定义位于 packages/puppeteer-core/src/api/CDPSession.tsCDP 后端的实际实现位于 packages/puppeteer-core/src/cdp/CdpSession.ts。如何获取 CDPSession官方文档给出的标准入口是Page的createCDPSession()方法见 Page.createCDPSession()抽象声明在 packages/puppeteer-core/src/api/Page.ts 中abstract createCDPSession(): PromiseCDPSession;除了PageTarget同样提供了工厂方法见 Target.createCDPSession()声明位于 packages/puppeteer-core/src/api/Target.ts因此对 Worker 等非页面目标也可以创建会话。完整 API方法、属性与参数send()发送任意协议命令CDPSession.send() 是调用协议命令的核心方法签名为abstract sendT extends keyof ProtocolMapping.Commands( method: T, params?: ProtocolMapping.Commands[T][paramsType][0], options?: CommandOptions, ): PromiseProtocolMapping.Commands[T][returnType];参数类型说明methodT协议命令名如Animation.enable类型为全部 CDP 命令键的联合可获得完整的参数与返回值类型推导paramsProtocolMapping.Commands[T][paramsType][0]可选。该命令对应的协议参数对象optionsCommandOptions可选。命令选项当前包含timeout: number字段控制该次命令等待响应的超时时间返回值PromiseProtocolMapping.Commands[T][returnType]即该命令在协议定义中的返回类型。从源码结构看CommandOptions.timeout会在底层 packages/puppeteer-core/src/cdp/Connection.ts 的_rawSend()中生效options?.timeout ?? this.#timeout即单条命令可以覆盖连接级别的默认超时。on()订阅协议事件协议事件使用继承自EventEmitter的on方法订阅事件键即 CDP 事件名如Animation.animationCreated。事件类型体系定义在CDPSessionEvents接口见 docs/api/puppeteer.cdpsessionevents.mdexport interface CDPSessionEvents extends CDPEvents, RecordEventType, unknown { [CDPSessionEvent.SessionAttached]: CDPSession; [CDPSessionEvent.SessionDetached]: CDPSession; // Disconnected / Swapped / Ready 为内部internal事件符号键不对第三方公开 }其中 CDPEvents 由ProtocolMapping.Events展开而来因此协议中所有事件都带类型地暴露在会话上。除了协议事件还有两个公开的生命周期字符串事件定义见 packages/puppeteer-core/src/api/CDPSession.ts 的CDPSessionEvent命名空间事件载荷触发时机sessionattached新建的CDPSession会话挂载auto-attach 机制产生子会话时sessiondetached被卸载的CDPSession会话卸载时id() 与 connection()CDPSession.id()返回会话的 idCDP 的sessionId实现见 CdpSession.ts 的override id()CDPSession.connection()返回该会话底层的Connection对象若存在。在 CDP 实现中直接返回内部持有的Connection引用。detached 属性与 detach()属性detachedreadonly boolean会话已分离时为true。源码实现为this.#connection._closed || this.#detached即底层连接已关闭或本会话已显式 detach两种情况都会置真方法 CDPSession.detach()将会话从目标上分离。分离后该对象不再触发任何事件也不能再发送消息。detach()的底层行为见 packages/puppeteer-core/src/cdp/CdpSession.tsoverride async detach(): Promisevoid { if (this.detached) { throw new Error( Session already detached. Most likely the ${this.#targetType} has been closed., ); } await this.#connection.send(Target.detachFromTarget, { sessionId: this.#sessionId, }); this.#detached true; }可以看到重复detach()会抛错正常路径是向浏览器发送Target.detachFromTarget协议命令成功后本地标记为已分离。官方示例控制页面动画播放速率docs/api/puppeteer.cdpsession.md 给出的示例用Animation域演示了启用域 → 订阅事件 → 查询状态 → 修改状态的完整闭环这里保留原始示例并补充注释// 1. 为页面创建一个 CDP 会话 const client await page.createCDPSession(); // 2. 启用 Animation 域很多域需要先 enable 才会推送事件 await client.send(Animation.enable); // 3. 订阅协议事件每当有动画被创建时打印日志 client.on(Animation.animationCreated, () console.log(Animation created!), ); // 4. 查询当前播放速率send 的返回值类型由协议映射推导 const response await client.send(Animation.getPlaybackRate); console.log(playback rate is response.playbackRate); // 5. 将播放速率减半 await client.send(Animation.setPlaybackRate, { playbackRate: response.playbackRate / 2, });这段示例同时覆盖了CDPSession的三块核心能力send命令、on事件订阅、以及基于ProtocolMapping的类型安全。源码级走查一条 CDP 命令的生命周期结合仓库实现可以还原send(Animation.getPlaybackRate)从调用到返回的全过程1. 会话层detach 检查与错误翻译CdpSession.ts 的send()首先生效一个前置检查if (this.detached) { return Promise.reject( new TargetCloseError( Protocol error (${method}): Session closed. Most likely the ${this.#targetType} has been closed., ), ); }也就是说对已分离/已关闭的会话发命令会立刻得到TargetCloseError错误信息会带上目标类型如 page以便定位。随后调用connection._rawSend(...)并附加一层错误翻译如果浏览器返回Session with given id not found会触发内部的onClosed()流程并把原始协议错误改写为TargetCloseError避免用户拿到难以理解的底层字符串。2. 连接层JSON 编码与超时登记Connection.ts 的_rawSend()完成了真正的报文构建return callbacks.create(method, options?.timeout ?? this.#timeout, id { const stringifiedMessage JSON.stringify({ method, params, id, sessionId, }); this.#debugProtocolSend?.(stringifiedMessage); this.#transport.send(stringifiedMessage); });几个值得注意的细节报文体即{method, params, id, sessionId}的 JSON与 CDP 的 WebSocket 文本帧格式一致每个命令通过CallbackRegistry登记一个回调id用于后续匹配响应timeout决定超时后该 Promise 以拒绝结束——这就是CommandOptions.timeout的生效点连接已关闭时_rawSend直接拒绝为ConnectionClosedError特殊防护当规则式网络仿真rule-based emulation开启时Network.emulateNetworkConditions会被显式拒绝防止两种仿真方式互相覆盖。3. 响应与事件的分发浏览器回包进入会话的onMessage()后按有无id分流见 CdpSession.tsif (object.id) { // 有 id命令响应 if (object.error) { // 协议错误 → reject 对应命令的 Promise this.#callbacks.reject(object.id, createProtocolErrorMessage(object), object.error.message); } else { // 正常结果 → resolve 对应命令的 Promise this.#callbacks.resolve(object.id, object.result); } } else { // 无 id协议事件 → 走 EventEmitter 通道 this.emit(object.method, object.params); }这解释了为什么命令用await send()取值、事件用client.on()订阅两种模式可以共存于同一对象连接层把带id的帧路由到回调注册表把不带id的帧转发为EventEmitter事件。4. 子会话的产生从 Connection.ts 的onMessage()可以看到当收到Target.attachedToTarget事件时Puppeteer 会用消息中的sessionId、目标类型等信息即时构造一个新的CdpCDPSession并登记进会话表。这就是 CDP auto-attach 机制在 Puppeteer 中的落地也是sessionattached/sessiondetached事件的来源。从源码结构看CdpCDPSession.parentSession()会依据parentSessionId向上解析父会话某些场景如 DevTools 页没有父会话时返回自身这属于内部实现第三方无需直接使用。会话关闭时的资源清理当会话因目标关闭而结束内部onClosed()会执行三件事见 CdpSession.tsthis.#callbacks.clear()清空所有未决命令回调避免悬挂 Promisethis.#detached true让后续send()走TargetCloseError快速失败路径发出内部Disconnected符号事件供 Puppeteer 内部依赖会话生命周期的组件响应。对使用方而言实际体感就是目标关闭后未完成的send()会收到拒绝且detached变为true如需主动、干净地结束会话应调用detach()而不是丢弃引用。使用建议与适用场景CDPSession是 Puppeteer 与浏览器之间的逃生舱优先使用高层 APIpage.click、page.goto、page.emulate等它们覆盖绝大多数场景且跨浏览器后端更一致当高层 API 未暴露某能力时用createCDPSession()send()直连 CDP例如本文示例的Animation域、Tracing、Emulation的细粒度参数或任何 CDP 协议文档中的命令——参数与返回值均受ProtocolMapping类型约束写错命令名或参数会在编译期暴露长监听类需求如动画创建、网络细节事件适合client.on(事件名, 回调)并在使用结束后detach()释放会话注意适用前提page.createCDPSession()在 CDP 后端Chrome 系下功能完整该会话与具体 target 绑定target 关闭后会话即进入分离状态代码中应对TargetCloseError/ConnectionClosedError有基本的容错处理。小结能力入口要点获取会话page.createCDPSession()/target.createCDPSession()构造函数 internal勿手动构造发送命令send(method, params, options)类型安全options.timeout覆盖默认超时已分离会话抛TargetCloseError订阅事件on(事件名, 回调)全部 CDP 事件带类型另有sessionattached/sessiondetached身份与底层id()/connection()返回 CDP sessionId 与底层Connection结束会话detach()发送Target.detachFromTarget重复调用抛错detached反映真实状态核心源码与文档索引packages/puppeteer-core/src/api/CDPSession.ts公开抽象定义与事件表、packages/puppeteer-core/src/cdp/CdpSession.tsCDP 实现send/detach/onMessage/onClosed、packages/puppeteer-core/src/cdp/Connection.ts报文编码、超时与子会话创建、docs/api/puppeteer.cdpsession.md 及其关联的 send、detach、id、connection、CDPSessionEvents 文档页。【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门