首页
/ puppeteer Browser 类详解:从启动、窗口与屏幕管理到断开重连的浏览器实例控制

puppeteer Browser 类详解:从启动、窗口与屏幕管理到断开重连的浏览器实例控制

2026-09-04 19:05:41作者:滑思眉Philip

本篇技术文章基于 puppeteer 官方 API 文档 Browser 类 展开,系统讲解 Browser 抽象类的完整 API 面:页面与浏览器上下文的创建、Cookie 与权限的快捷方法、窗口/屏幕管理、扩展与 PWA 管理,以及 close / disconnect / wsEndpoint 构成的连接生命周期体系。读完后你将能够基于 Browser 实例完成多上下文隔离、远程重连、多屏仿真等典型场景,并理解每个方法在 packages/puppeteer-core/src/api/Browser.ts 与 CDP 实现层 packages/puppeteer-core/src/cdp/Browser.ts 中的真实调用链。

类的定位:抽象基类与两种获取途径

Browser 表示一个浏览器实例,该实例要么通过 Puppeteer.connect() 连接而来,要么由 PuppeteerNode.launch() 启动。官方类签名如下:

export declare abstract class Browser extends EventEmitter<BrowserEvents>

它继承自 EventEmitter<BrowserEvents>,是一个抽象类。文档中明确要求:该类的构造函数被标记为内部(internal),第三方代码不应直接调用其构造函数,也不应创建继承自 Browser 的子类。

在源码中可以印证这一约束。packages/puppeteer-core/src/api/Browser.ts#L478 中声明了抽象类,构造函数同样标注为 @internal

// packages/puppeteer-core/src/api/Browser.ts
export abstract class Browser extends EventEmitter<BrowserEvents> {
  #logger: Logger;

  /** @internal */
  constructor(logger: Logger) {
    super(undefined, logger);
    this.#logger = logger;
  }
  ...
}

从源码结构看,仓库中存在多个具体实现:CDP 协议的 CdpBrowserpackages/puppeteer-core/src/cdp/Browser.ts)、WebDriver BiDi 协议的 packages/puppeteer-core/src/bidi/Browser.tspackages/puppeteer-core/src/bidi/core/Browser.ts。用户通过 launch() / connect() 得到的 Browser 实例由对应协议的实现类填充。

官方示例一:用 Browser 创建 Page

文档给出的第一个核心用法,是启动浏览器后创建页面并关闭:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();

在 CDP 实现中,newPage 本身只是一层转发——它把调用委托给默认浏览器上下文:

// packages/puppeteer-core/src/cdp/Browser.ts
override async newPage(options?: CreatePageOptions): Promise<Page> {
  return await this.#defaultContext.newPage(options);
}

真正的建页逻辑在 _createPageInContextL414-L455):先发送 CDP 命令 Target.createTargeturl: 'about:blank'),再调用 waitForTarget 等待目标完成初始化,最后经 target.page() 得到 Page 实例。注意 CreatePageOptions 支持三种形态(见 api/Browser.ts#L255-L270):

  • 省略 typetype: 'tab':在当前窗口新建标签页;
  • type: 'window' 并可附带 windowBoundsleft/top/width/height/windowState):新建独立窗口;
  • background?: boolean:在后台创建页面,默认 false

测试文件 test/src/browser.test.ts#L168-L196 演示了窗口形态:通过 context.newPage({type: 'window', windowBounds}) 建窗后,用 page.windowId() 拿到窗口 id,再配合 browser.getWindowBounds 回读校验。

官方示例二:断开连接与重连

第二个官方示例展示 wsEndpoint()disconnect()Puppeteer.connect() 的协作——先保存 WebSocket 端点,断开后再凭端点重建连接:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
// Store the endpoint to be able to reconnect to the browser.
const browserWSEndpoint = browser.wsEndpoint();
// Disconnect puppeteer from the browser.
await browser.disconnect();

// Use the endpoint to reestablish a connection
const browser2 = await puppeteer.connect({browserWSEndpoint});
// Close the browser.
await browser2.close();

文档同时说明:wsEndpoint() 返回用于连接本浏览器的 WebSocket URL,通常配合 Puppeteer.connect() 使用;你也可以从 http://HOST:PORT/json/version 中的 webSocketDebuggerUrl 字段找到调试器地址,且该地址格式固定为 ws://HOST:PORT/devtools/browser/<id>。在 CDP 实现里它直接返回连接 URL(cdp/Browser.ts#L406-L408):

override wsEndpoint(): string {
  return this.#connection.url();
}

有一个重要的适用前提:pipe 连接下没有 WebSocket 端点。启动时设置 pipe: true 后,wsEndpoint() 返回空字符串,这一点在测试 test/src/cdp/pipe.test.ts#L17 中被断言:expect(browser.wsEndpoint()).toBe('')

重连场景在 test/src/launcher.test.ts#L774-L809 中有完整验证:先 browser.disconnect(),再用 puppeteer.connect({browserWSEndpoint, protocol}) 重连,随后通过 remoteBrowser.pages() 找回断开前已导航到 nested-frames.html 的页面,并确认其 frame 树与可执行 evaluate(返回 7 * 8 === 56)——证明浏览器进程与页面状态在断开期间被完整保留。

实例属性:connected 与 debugInfo

文档中 Browser 暴露两个只读属性:

属性 修饰符 类型 说明
connected readonly boolean Puppeteer 是否已连接到该浏览器
debugInfo readonly DebugInfo (实验性) 获取 Puppeteer 的调试信息,目前包含未完成的协议调用(pending protocol calls)

connected 在 CDP 实现中就是对底层连接关闭状态的取反(cdp/Browser.ts#L709-L711):

override get connected(): boolean {
  return !this.#connection._closed;
}

debugInfo 的返回结构由接口 DebugInfo 定义,仅含 pendingProtocolErrors: Error[];CDP 实现直接取自连接层:{pendingProtocolErrors: this.#connection.getPendingProtocolErrors()}cdp/Browser.ts#L727-L731)。

测试 test/src/browser.test.ts#L77-L93 验证了 connected 的一个关键行为:关闭所有页面后浏览器连接依然保持(expect(browser.connected).toBe(true)),且此时仍可继续 newPage()——即“页面全部关闭”不会导致浏览器断开。

页面与目标(Target)管理

Browser 上围绕页面/目标的常用方法如下(均引自文档的 Methods 表):

  • newPage(options?):在默认浏览器上下文中创建新页面;
  • pages(includeAll?):获取本浏览器内所有打开的页面;存在多个浏览器上下文时,返回所有上下文中的页面。注意:不可见页面(如 "background_page" 类型)不会列出,可通过 Target.page() 找到它们;
  • target():获取与默认浏览器上下文关联的目标;
  • targets():获取所有活动目标,多个上下文时返回全部上下文中的目标;
  • waitForTarget(predicate, options):等待匹配 predicate 的目标出现并返回它,会遍历所有打开的浏览器上下文。

从源码结构看,pages() 并非查询浏览器,而是对每个上下文逐页聚合(api/Browser.ts#L637-L647):

async pages(includeAll = false): Promise<Page[]> {
  const contextPages = await Promise.all(
    this.browserContexts().map(context => {
      return context.pages(includeAll);
    }),
  );
  // Flatten array.
  return contextPages.reduce((acc, x) => acc.concat(x), []);
}

waitForTarget 则是基类中用 RxJS 组合的事件流实现(api/Browser.ts#L608-L623):对已存在目标做一次快照,再 merge targetcreatedtargetchanged 事件流,经 predicate 异步过滤后,与 AbortSignaltimeout(ms) 竞速:

async waitForTarget(
  predicate: (x: Target) => boolean | Promise<boolean>,
  options: WaitForTargetOptions = {},
): Promise<Target> {
  const {timeout: ms = 30000, signal} = options;
  return await firstValueFrom(
    merge(
      fromEmitterEvent(this, BrowserEvent.TargetCreated),
      fromEmitterEvent(this, BrowserEvent.TargetChanged),
      from(this.targets()),
    ).pipe(
      filterAsync(predicate),
      raceWith(fromAbortSignal(signal), timeout(ms)),
    ),
  );
}

对应的 WaitForTargetOptionsapi/Browser.ts#L148-L160)中,timeout 默认 30000 毫秒、传 0 可禁用,signal 支持用 AbortSignal 取消等待。文档中给出的典型用法——捕获 window.open 打开的新窗口:

await page.evaluate(() => window.open('https://www.example.com/'));
const newWindowTarget = await browser.waitForTarget(
  target => target.url() === 'https://www.example.com/',
);

targets() 在 CDP 实现中返回的是“已暴露且初始化成功”的目标(cdp/Browser.ts#L659-L668),而 target() 从中查找 type() === 'browser' 的那个;若找不到会抛出 Browser target is not found

浏览器上下文(BrowserContext)与 Cookie 快捷方法

上下文管理

  • createBrowserContext(options?):创建一个不与其他上下文共享 Cookie/缓存的新浏览器上下文;
  • browserContexts():获取所有打开的上下文列表,新建的浏览器中只有一个(默认上下文);
  • defaultBrowserContext():获取默认上下文,且默认上下文无法被关闭

createBrowserContext 的选项 BrowserContextOptions 定义在 api/Browser.ts#L41-L58proxyServer(可选端口的代理服务器,账密通过 Page.authenticate 设置)、proxyBypassList(绕过代理的主机列表)、downloadBehavior(下载行为定义,未设置则用默认)。CDP 实现将前两者直接传给 Target.createBrowserContextcdp/Browser.ts#L267-L290),downloadBehavior 则在上下文创建后单独调用 setDownloadBehavior。文档中的示例:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
// Create a new browser context.
const context = await browser.createBrowserContext();
// Create a new page in a pristine context.
const page = await context.newPage();
// Do stuff
await page.goto('https://example.com');

Cookie 与权限快捷方法。文档中明确这四个方法都是对默认上下文的快捷方式:

  • cookies():返回默认 BrowserContext 中的所有 Cookie(等价 browser.defaultBrowserContext().cookies());
  • setCookie(cookies):在默认上下文设置 Cookie(等价 browser.defaultBrowserContext().setCookie());
  • deleteCookie(cookies):从默认上下文删除 Cookie(等价 browser.defaultBrowserContext().deleteCookie());
  • deleteMatchingCookies(filters):按过滤条件从默认上下文删除 Cookie(等价 browser.defaultBrowserContext().deleteMatchingCookies());
  • setPermission(origin, permissions):为指定源设置默认上下文中的权限(等价 browser.defaultBrowserContext().setPermission())。

源码印证了这种“转发”关系,例如 api/Browser.ts#L691-L705

async cookies(): Promise<Cookie[]> {
  return await this.defaultBrowserContext().cookies();
}

async setCookie(...cookies: CookieData[]): Promise<void> {
  return await this.defaultBrowserContext().setCookie(...cookies);
}

权限值支持 PermissionDescriptornameuserVisibleOnlysysexpanTiltZoomallowWithoutSanitization)与状态 'granted' | 'denied' | 'prompt'api/Browser.ts#L132-L143);旧的字符串联合类型 Permission'camera''geolocation''notifications' 等 18 种)已被标记为 @deprecated,建议改用 PermissionDescriptor

连接生命周期:process、close、disconnect 与资源释放

process():获取关联的 Node ChildProcess。若该实例是通过 Puppeteer.connect 连接而来,则返回 null。测试 test/src/browser.test.ts#L58-L76 双向验证了这一点:本地 launch 的浏览器 process().pid > 0;而通过 wsEndpoint 远程重连得到的 remoteBrowser.process()null

close():关闭该浏览器及所有关联页面。CDP 实现中它是“关闭回调 + 断开连接”的组合(cdp/Browser.ts#L690-L700):

override async close(): Promise<void> {
  await this.#closeCallback.call(null);
  await this.disconnect();
}

override disconnect(): Promise<void> {
  this.#targetManager.dispose();
  this.#connection.dispose();
  this._detach();
  return Promise.resolve();
}

disconnect():把 Puppeteer 从浏览器上断开,但进程继续运行——这正是前面重连示例的基础。

此外,Browser 实现了显式资源管理(Symbol.dispose / Symbol.asyncDispose),这解释了为什么文档方法表中列出这两个方法,也是测试代码中 using remoteBrowser = await puppeteer.connect(...) 语法的底层支撑。基类实现(api/Browser.ts#L858-L871)的决策逻辑非常简洁:

override [disposeSymbol](): void {
  return void this[asyncDisposeSymbol]().catch(error => {
    this.#logger?.(DEBUG_PREFIXES.error)?.(error);
  });
}

override async [asyncDisposeSymbol](): Promise<void> {
  if (this.process()) {
    await this.close();
  } else {
    await this.disconnect();
  }
  await super[asyncDisposeSymbol]();
}

即:自己启动的浏览器(有进程)执行 close(),仅连接的浏览器执行 disconnect()——using 声明可以按获取方式自动选择正确的清理策略。

浏览器窗口与屏幕管理

这一组方法主要用于窗口级控制与 headless 多屏仿真:

  • getWindowBounds(windowId) / setWindowBounds(windowId, windowBounds):获取/设置指定窗口的 bounds;
  • screens():获取屏幕信息对象列表;
  • addScreen(params):新增一块屏幕并返回其 ScreenInfo仅 headless 模式支持
  • removeScreen(screenId):移除一块屏幕,仅 headless 模式支持,且不能移除主屏幕(指定主屏幕 id 会失败)。

类型定义方面,WindowBoundsleft/top/width/height/windowStateWindowState'normal' | 'minimized' | 'maximized' | 'fullscreen'api/Browser.ts#L234-L250);ScreenInfo 包含 idlabelisPrimaryisExtendeddevicePixelRatioorientationworkArea 相关尺寸等完整字段(api/Browser.ts#L283-L326),AddScreenParams 支持 left/top/width/height 必填项及 workAreaInsetsdevicePixelRatiorotationcolorDepthlabelisInternal 可选项。

CDP 层对应关系为:Browser.getWindowBounds / Browser.setWindowBounds(注意 windowId 会被转成 Number 发送,cdp/Browser.ts#L642-L657),以及 Emulation.getScreenInfos / Emulation.addScreen / Emulation.removeScreencdp/Browser.ts#L623-L640)。

测试给出了可直接参考的断言样例(test/src/browser.test.ts#L96-L166):headless 下默认单块 800×600 主屏;addScreen({left: 800, top: 0, width: 1600, height: 1200, colorDepth: 32, workAreaInsets: {bottom: 80}, label: 'secondary'})screens() 返回 2 块,且新屏 availHeight 因底部 80 像素内缩变为 1120;removeScreen(screenInfo.id) 后回到 1 块。窗口最大化场景(L198-L231)则展示了先 addScreen 建副屏、在该屏上开窗、再 setWindowBounds(windowId, {windowState: 'maximized'}) 的完整流程。

扩展与 PWA 管理

扩展(Extension)

  • installExtension(path, options?):安装扩展并返回扩展 ID;
  • uninstallExtension(id):卸载指定扩展;
  • extensions():获取当前已安装扩展的 Map,键为扩展 ID,值为 Extension 实例。

CDP 实现中,安装走 Extensions.loadUnpackedenableInIncognito 默认 false),返回 idcdp/Browser.ts#L500-L510);卸载走 Extensions.uninstall,并有一段针对 service worker 目标销毁事件的补偿逻辑(L512-L540)——当前 CDP 的 Extensions.uninstall 不会触发对应 service worker 的 Target.targetDestroyed 事件,实现中通过手动补发事件避免测试抖动,注释中标记为待上游修复后移除。extensions() 则通过 Extensions.getExtensions 拉取清单并与本地缓存的 CdpExtension 实例合并。

PWA(渐进式 Web 应用),共 4 个方法,且有统一的前置限制:

  • installPWA(options):安装 PWA,返回其 manifest id。参数 InstallPWAOptions 含必填的 manifestId(Web App 清单中的 id,通常为站点 URL)、必填的 installUrlOrBundleUrl(因为浏览器级 CDP 会话没有可推导安装 URL 的页面)、可选 displayMode: 'standalone' | 'browser'
  • launchPWA(options):启动已安装的 PWA,解析为承载该应用窗口的 Page。参数含 manifestId、可选 url(应用作用域内要打开的 URL)、可选 timeout(默认 30 秒,0 禁用);
  • getPWAState(options):返回已安装 PWA 的操作系统集成状态(如角标计数与已注册的文件处理器);
  • uninstallPWA(options):卸载之前安装的 PWA。

文档对该组方法的限定必须牢记:仅通过 pipe 连接可用——需在 puppeteer.launch 中设置 pipe: true(该启动选项默认 false),底层的 PWA CDP 域不会通过 WebSocket 连接暴露。此外:installPWA 返回的 manifest id 就是传入的 InstallPWAOptions.manifestId 的回显,可直接传给 launchPWA / getPWAState / uninstallPWAlaunchPWA 在 Chromium 聚焦已有应用窗口时返回该窗口的既有 page;查询未安装应用的 getPWAState 会 reject。

从源码结构看,四个实现都先做网络限制检查,配置了 blocklist/allowlist 时直接抛错(PWA APIs are not supported when network restrictions are configured.cdp/Browser.ts#L542-L621);launchPWA 的实现值得注意:PWA.launch 解析出的是 tab 目标的 id,而 tab 目标位于 page 目标之上的层级、不会暴露在 browser.targets() 中,因此实现会 waitForTarget 等待该 tab 目标的子 page 目标出现,再取 target.page() 返回(L572-L608)。getPWAState 则调用 PWA.getOsAppState 返回 {badgeCount, fileHandlers}

版本、User-Agent 与事件

  • version():返回表示浏览器名称与版本的字符串。headless 浏览器形如 "HeadlessChrome/61.0.3153.0",非 headless / new-headless 形如 "Chrome/61.0.3153.0",Firefox 形如 "Firefox/116.0a1";文档提醒该格式可能随浏览器版本变化。CDP 实现通过一次 Browser.getVersion 拿到并缓存(Deferred 保证只发一次协议请求,cdp/Browser.ts#L680-L725);
  • userAgent():返回该浏览器的原始 User-Agent;各 Page 可用 Page.setUserAgent() 覆盖。

测试对两者均有覆盖(test/src/browser.test.ts#L14-L46):version() 非空且包含 chromefirefoxuserAgent() 在 Chrome 下包含 WebKit、Firefox 下包含 Gecko

事件Browser 会发出文档 BrowserEvent 枚举所列的事件,源码定义在 api/Browser.ts#L167-L207

事件 触发时机
Disconnected 'disconnected' Puppeteer 与浏览器断开,可能是浏览器关闭/崩溃,或调用了 Browser.disconnect
TargetChanged 'targetchanged' 目标 URL 变化,负载为 Target 实例(含所有上下文)
TargetCreated 'targetcreated' 目标被创建,如 window.openbrowser.newPage 打开新页面
TargetDestroyed 'targetdestroyed' 目标被销毁,如页面关闭
TargetDiscovered 'targetdiscovered'(internal) 内部事件

CDP 实现中,这些事件由 TargetManager 驱动:TargetAvailableemit(BrowserEvent.TargetCreated, target)TargetGoneTargetDestroyedTargetChangedTargetChanged,且会同时向所属 BrowserContext 再发一次同名字事件(cdp/Browser.ts#L373-L404);连接层的 CDPSessionEvent.Disconnected 触发 Disconnected。这也解释了 waitForTarget 为何要监听 TargetCreatedTargetChanged 两个事件——新建目标与 URL 变化都可能让 predicate 首次成立。

小结与延伸阅读

  • Browser 是 puppeteer 的浏览器级入口抽象:页面/上下文/窗口/屏幕/扩展/PWA 的资源管理都汇聚于此,具体行为由 CDP 或 BiDi 实现类填充;
  • 连接模型是理解它的钥匙:process() 区分“自启动”与“纯连接”,closedisconnect 语义不同,wsEndpoint() 是跨进程重连的凭证,[Symbol.asyncDispose] 则按前者自动选择清理方式;
  • 注意适用前提:屏幕管理仅限 headless,PWA API 仅限 pipe: true 的 pipe 连接,wsEndpoint() 在 pipe 连接下为空字符串。

可进一步深入的材料:api/Browser.ts 抽象基类cdp/Browser.ts CDP 实现test/src/browser.test.tstest/src/launcher.test.tstest/src/cdp/pipe.test.ts,以及 BrowserContextTargetPage 等关联 API 文档。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384