Puppeteer Browser.wsEndpoint() 详解:获取浏览器 WebSocket 端点并实现断线重连
Browser.wsEndpoint() 是 Puppeteer 中用于获取与当前浏览器实例通信的 WebSocket URL 的核心 API,也是 puppeteer.connect() 进行"连接已有浏览器"或"断线重连"的关键输入。本文以 wsEndpoint 官方文档 为主体,结合仓库源码,讲清楚该方法的签名与返回值、标准用法、端点格式约定,以及 CDP / WebDriver BiDi 两种协议下它在源码中的真实实现路径,帮助你掌握"获取端点 → 断开 → 重连"的完整实战方案。
方法签名与返回类型
按照 API 文档,该方法定义在抽象类 Browser 上,是一个同步方法:
class Browser {
abstract wsEndpoint(): string;
}
返回值: string,即连接该浏览器实例的 WebSocket URL。
文档明确给出端点格式约定:
The format is always
ws://HOST:PORT/devtools/browser/<id>.
即返回的 URL 始终遵循 ws://HOST:PORT/devtools/browser/<id> 的形式,其中 <id> 是浏览器自身的调试目标标识。这也意味着你拿到返回值后无需再做解析拼接,可以直接透传给 puppeteer.connect()。
抽象声明位于 api/Browser.ts:
/**
* Gets the WebSocket URL to connect to this Browser.
* ...
* @remarks The format is always `ws://HOST:PORT/devtools/browser/<id>`.
*/
abstract wsEndpoint(): string;
Browser 的构造函数标记为内部(internal),第三方代码不应直接实例化或继承该类,而应通过 puppeteer.launch() 或 puppeteer.connect() 获取 Browser 实例——参见 Browser 类文档。
标准用法:断开连接后用端点重连
wsEndpoint() 最典型的场景是"先记录端点、断开、再重连"。这是 Browser 类文档示例 2 给出的官方用法,也是 api/Browser.ts 中 JSDoc 的原始示例:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
// 先保存端点,以便之后重连。
const browserWSEndpoint = browser.wsEndpoint();
// 断开 Puppeteer 与浏览器的连接(浏览器进程仍在运行)。
await browser.disconnect();
// 使用端点重新建立连接
const browser2 = await puppeteer.connect({browserWSEndpoint});
// 关闭浏览器。
await browser2.close();
这里有两个容易混淆的语义,值得对照 disconnect() 文档注意区分:
browser.disconnect():仅断开 Puppeteer 侧的连接,浏览器进程继续存活,因此之后可以用wsEndpoint()拿到的 URL 重新接上;browser.close():关闭浏览器进程及其所有页面,端点随之失效。
也就是说,"可重连"的前提是浏览器进程没有被 close(),这一点正是 wsEndpoint() + disconnect() 组合的价值所在:让 Node 侧会话可断开、可迁移(例如跨进程、跨 Worker 传递端点字符串)。
不从 Puppeteer 启动时如何获得端点
当浏览器不是由 puppeteer.launch() 拉起的(例如你自己用命令行启动 Chrome、或浏览器跑在远程机器 / Docker 容器里),就无法调用 browser.wsEndpoint(),此时需要按文档说明手动获取调试器 URL:
You can find the debugger URL (
webSocketDebuggerUrl) fromhttp://HOST:PORT/json/version.
即向浏览器调试 HTTP 端点发送 GET 请求,从 JSON 响应中取出 webSocketDebuggerUrl 字段。这一做法在源码中同样得到印证:BrowserConnector.ts 的 getWSEndpoint() 内部就是 fetch('/json/version') 并读取 data.webSocketDebuggerUrl。
对应到 puppeteer.connect() 上,有等价的两种方式:
// 方式一:直接给 WebSocket 端点
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://HOST:PORT/devtools/browser/<id>',
});
// 方式二:只给 HTTP 端点,Puppeteer 自动走 /json/version 换取 ws 端点
const browser = await puppeteer.connect({
browserURL: 'http://HOST:PORT',
});
源码纵深:端点在两种协议下的实现
wsEndpoint() 在抽象层只声明契约,真实返回的 URL 来自"当前连接"持有的端点地址。仓库中 packages/puppeteer-core/src 下共有三处涉及 wsEndpoint 的实现/声明,分别对应抽象基类和 CDP、BiDi 两套协议:
CDP 实现
override wsEndpoint(): string {
return this.#connection.url();
}
CDP 版 Browser 直接返回内部 Connection 对象记录的服务端 URL。而这个 URL 最初从哪来?看 node/BrowserLauncher.ts 的 createCdpSocketConnection():
const browserWSEndpoint = await browserProcess.waitForLineOutput(
CDP_WEBSOCKET_ENDPOINT_REGEX,
opts.timeout,
);
const transport = await WebSocketTransport.create(
browserWSEndpoint,
undefined,
opts.logger,
);
return new Connection(browserWSEndpoint, transport, ...);
即 puppeteer.launch() 启动浏览器子进程后,用正则 CDP_WEBSOCKET_ENDPOINT_REGEX(定义于 packages/browsers/src/launch.ts)从浏览器 stdout 首行输出中提取 ws://... 端点,再以该端点构造 Connection。所以"launch 时的 wsEndpoint() 返回值"本质上是从子进程输出捕获到的那个 URL,与 http://HOST:PORT/json/version 返回的 webSocketDebuggerUrl 是同一个东西。
WebDriver BiDi 实现
override wsEndpoint(): string {
return this.connection.url;
}
BiDi 版 Browser 返回 BiDi 连接持有的 URL。注意从源码结构看,BiDi 场景下的端点路径形态由底层 BiDi 实现决定,文档中"always ws://HOST:PORT/devtools/browser/<id>"的强格式约定主要针对 CDP(Chrome)场景;Firefox 等走 WebDriver BiDi 协议时,端点语义相同(可用于 puppeteer.connect() 重连),但路径细节以实际连接对象为准。
Puppeteer.connect() 如何消费这个端点
wsEndpoint() 的返回值作为 browserWSEndpoint 传给 puppeteer.connect() 后,BrowserConnector.ts 的 getConnectionTransport() 处理它,其中有两条值得注意的源码事实:
-
互斥校验:
browserWSEndpoint、browserURL、transport、channel四个选项必须且只能指定一个,否则抛出:Exactly one of browserWSEndpoint, browserURL, transport or channel must be passed to puppeteer.connect -
协议分流:建立 WebSocket 传输后,
_connectToBrowser()会根据protocol选项选择_connectToBiDiBrowser或_connectToCdpBrowser。因此用wsEndpoint()重连时,如果原浏览器是 BiDi 协议启动的,重连也应带上相同的protocol(ConnectOptions.ts 中protocol默认按 CDP 连接处理)。
此外,ConnectOptions 还支持 headers(仅 Node.js 环境生效,用于给 WebSocket 握手附加请求头,便于反向代理鉴权)等参数,完整参数说明见 connect() 文档。
测试用例佐证
仓库测试直接验证了 wsEndpoint() 驱动的"重连后行为"语义。browser.test.ts 中的 should not return child_process for remote browser:
const browserWSEndpoint = browser.wsEndpoint();
using remoteBrowser = await puppeteer.connect({
browserWSEndpoint,
protocol: browser.protocol,
});
expect(remoteBrowser.process()).toBe(null);
该用例印证了两点:其一,launch() 得到的端点可以直接用于 connect();其二,通过 connect() 建立的 Browser 不持有子进程句柄,process() 返回 null——这与 process() 文档 的说明一致。换言之,"重连回来的 Browser 管连接,管不了进程",如果你需要终止浏览器进程,只能调用 browser2.close()(向浏览器发送关闭命令)而非依赖本地 ChildProcess。
实战要点小结
| 场景 | 获取端点的方式 | 备注 |
|---|---|---|
puppeteer.launch() 启动 |
browser.wsEndpoint() |
端点来自子进程 stdout 捕获,可直接重连 |
| 自建 / 远程 / 容器化浏览器 | GET http://HOST:PORT/json/version 取 webSocketDebuggerUrl |
等价于 connect({browserURL}) 的自动流程 |
| 重连 | puppeteer.connect({browserWSEndpoint}) |
注意带上与原浏览器一致的 protocol |
| 断开后再连 | disconnect() 而非 close() |
close() 会终结进程,端点失效 |
使用 wsEndpoint() 时请牢记:它返回的是纯字符串同步取值,没有任何异步开销;它反映的是 Puppeteer 当前实际连接的服务端地址,因此在"launch → 断连 → 重连"生命周期中是最可靠的连接凭据。配合 disconnect()、connect() 与 Browser 类总览,即可覆盖绝大多数分布式或长生命周期浏览器会话管理需求。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00