puppeteer Browser 类详解:从启动、窗口与屏幕管理到断开重连的浏览器实例控制
2026/9/13 6:33:49 网站建设 项目流程

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

【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer

本篇技术文章基于 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 协议的 CdpBrowser(packages/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); }

真正的建页逻辑在_createPageInContext(L414-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暴露两个只读属性:

属性修饰符类型说明
connectedreadonlybooleanPuppeteer 是否已连接到该浏览器
debugInforeadonlyDebugInfo(实验性)获取 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):对已存在目标做一次快照,再 mergetargetcreatedtargetchanged事件流,经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)), ), ); }

对应的WaitForTargetOptions(api/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-L58:proxyServer(可选端口的代理服务器,账密通过Page.authenticate设置)、proxyBypassList(绕过代理的主机列表)、downloadBehavior(下载行为定义,未设置则用默认)。CDP 实现将前两者直接传给Target.createBrowserContext(cdp/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():获取关联的 NodeChildProcess。若该实例是通过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.removeScreen(cdp/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),返回id(cdp/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),底层的PWACDP 域不会通过 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.ts、test/src/launcher.test.ts、test/src/cdp/pipe.test.ts,以及 BrowserContext、Target、Page 等关联 API 文档。

【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询