Puppeteer Browser.newPage() 深度解析:创建新标签页、独立窗口与后台页面的完整指南
2026/9/7 10:29:53 网站建设 项目流程

Puppeteer Browser.newPage() 深度解析:创建新标签页、独立窗口与后台页面的完整指南

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

本篇围绕 Puppeteer 的Browser.newPage()方法展开:它是获取Page实例最直接的入口,在默认浏览器上下文(default browser context)中创建一个新页面。读完本文,你不仅能掌握该方法的完整签名、CreatePageOptions各参数(type: 'tab' | 'window'windowBoundsbackground)的用法,还能从源码层面理解它在 CDP 与 BiDi 两种协议下分别如何落到Target.createTarget/browsingContext.create调用,以及如何配合Page.windowId()Browser.getWindowBounds()验证窗口行为,并通过仓库内测试用例确认各项能力的实际表现。

方法签名与基本行为

按照官方 API 文档(docs/api/puppeteer.browser.newpage.md),该方法在默认浏览器上下文中创建一个新页面:

class Browser { abstract newPage(options?: CreatePageOptions): Promise<Page>; }
参数类型说明
optionsCreatePageOptions可选,控制页面创建方式(标签页/独立窗口)与是否后台运行

返回值Promise<Page>,解析为创建好的 Page 实例。

Browser抽象类中的定义位于 packages/puppeteer-core/src/api/Browser.ts,其 JSDoc 明确写着 "Creates a new page in the default browser context",并给出了最小可用示例:

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

需要注意一个隐含前提:newPage()创建的是默认上下文里的页面。默认上下文共享 Cookie、缓存与权限状态,且不能被关闭(见 Browser.defaultBrowserContext 文档:"The default browser context cannot be closed.")。如果你需要隔离的环境,应改用browser.createBrowserContext()后再调用context.newPage(),两者的options类型相同,实现见 BrowserContext.newPage 文档。

CreatePageOptions 参数详解

CreatePageOptionsBrowserBrowserContext共用的选项类型,在源码中的定义见 packages/puppeteer-core/src/api/Browser.ts#L255-L270(类型文档:docs/api/puppeteer.createpageoptions.md):

export type CreatePageOptions = ( | { type?: 'tab'; } | { type: 'window'; windowBounds?: WindowBounds; } ) & { /** * Whether to create the page in the background. * @defaultValue `false` */ background?: boolean; };

这是一个联合类型与{ background?: boolean }的交叉,实际含义如下:

type: 'tab'(默认)

不传options或传{type: 'tab'}时,行为一致:在当前浏览器窗口的标签栏中新开一个标签页。这是最常见、也是性能开销最小的用法——共享同一窗口、同一 GPU 进程与渲染资源,适合绝大多数爬取、截图、多任务并行的场景。

type: 'window'+windowBounds

{type: 'window'}会强制新页面在独立窗口中打开,并可通过windowBounds指定窗口的几何位置、尺寸与窗口状态。WindowBounds接口定义在 packages/puppeteer-core/src/api/Browser.ts#L236-L245,所有字段均为可选(接口文档:docs/api/puppeteer.windowbounds.md):

字段类型含义
leftnumber窗口左上角的 X 坐标
topnumber窗口左上角的 Y 坐标
widthnumber窗口宽度
heightnumber窗口高度
windowStateWindowState窗口状态

WindowState的取值为(源码 api/Browser.ts#L231-L234):

export type WindowState = 'normal' | 'minimized' | 'maximized' | 'fullscreen';

典型用法——在指定位置打开一个 750×550 的独立窗口:

const page = await browser.newPage({ type: 'window', windowBounds: {left: 50, top: 50, width: 750, height: 550}, });

或者只指定窗口状态:

// 以最大化状态打开新窗口 const page = await browser.newPage({ type: 'window', windowBounds: {windowState: 'maximized'}, });

background

backgroundtype正交,可以叠加使用。设为true时新页面以后台方式创建,页面不会获得焦点,document.visibilityState'hidden',适合在后台静默运行任务(如预热页面、批量抓取),避免抢占用户当前焦点。

const page = await browser.newPage({background: true}); // page.evaluate(() => document.visibilityState) => 'hidden'

源码实现:CDP 协议下的调用链

newPage()在 CDP 协议下的实现位于CdpBrowser,其核心是一条清晰的委托链:

  1. CdpBrowser.newPage()转发到默认上下文(packages/puppeteer-core/src/cdp/Browser.ts#L410-L412):
override async newPage(options?: CreatePageOptions): Promise<Page> { return await this.#defaultContext.newPage(options); }
  1. CdpBrowserContext.newPage()加锁后调用_createPageInContext(packages/puppeteer-core/src/cdp/BrowserContext.ts#L132-L135):
override async newPage(options?: CreatePageOptions): Promise<Page> { using _guard = await this.waitForScreenshotOperations(); return await this.#browser._createPageInContext(this.#id, options); }

这里的#id对默认上下文为undefined,因此browser.newPage()browser.defaultBrowserContext().newPage()最终走的是同一条路径。waitForScreenshotOperations()则保证创建页面前该上下文上已挂起的截屏操作完成,避免并发冲突。

  1. _createPageInContext()是真正发协议的地方(packages/puppeteer-core/src/cdp/Browser.ts#L414-L455):
const hasTargets = this.targets().filter(t => { return t.browserContext().id === contextId; }).length > 0; const windowBounds = options?.type === 'window' ? options.windowBounds : undefined; const {targetId} = await this.#connection.send('Target.createTarget', { url: 'about:blank', browserContextId: contextId || undefined, left: windowBounds?.left, top: windowBounds?.top, width: windowBounds?.width, height: windowBounds?.height, windowState: windowBounds?.windowState, // Works around crbug.com/454825274. newWindow: hasTargets && options?.type === 'window' ? true : undefined, background: options?.background, });

从源码结构看,可以提取出几个关键实现事实:

  • 新页面初始 URL 固定为about:blank,后续由你自己gotosetContent
  • windowBounds只有在type === 'window'时才会生效——源码中windowBounds变量的赋值就带了这个条件判断,若你传了type: 'tab'同时带了windowBounds,它会被静默忽略(类型系统上也不会允许,因为'tab'分支没有windowBounds字段);
  • newWindow参数是一个 Chromium bug 的规避:仅当上下文中已存在其他 target 且请求新建窗口时才置true,源码注释标注 "Works around crbug.com/454825274";
  • background直接透传给Target.createTarget,由浏览器侧处理后台标签的可见性状态。

拿到targetId后,实现并不立即返回,而是用browser.waitForTarget(t => t._targetId === targetId)等待对应的Target对象被 TargetManager 注册,再检查target._initializedDeferred是否为SUCCESS(初始化失败会抛出Failed to create target for page (id = ...)),最后通过target.page()取出包装好的Page对象返回。也就是说,newPage()resolve 时,页面 target 已经完成初始化,你可以直接在上面执行gotoevaluate等操作。

BiDi 协议下的实现差异

Puppeteer 同时支持 WebDriver BiDi 协议(相关背景见 docs/webdriver-bidi.md),其实现位于 packages/puppeteer-core/src/bidi/BrowserContext.ts#L206-L242:

const type = options?.type === 'window' ? Bidi.BrowsingContext.CreateType.Window : Bidi.BrowsingContext.CreateType.Tab; const context = await this.userContext.createBrowsingContext(type, { background: options?.background, }); ... if (options?.type === 'window' && options?.windowBounds !== undefined) { try { await this.browser().setWindowBounds( context.windowId, options.windowBounds, ); } catch (error) { // Tolerate not supporting `browser.setClientWindowState`. Only log it. this.#logger?.(DEBUG_PREFIXES.error)?.(error); } }

与 CDP 实现的差异值得注意:

  • type映射到 BiDi 的BrowsingContext.CreateTypeWindow/Tab),background同样透传;
  • windowBounds采用"先创建、后调整"的两步策略:先创建窗口,再调用browser.setWindowBounds(context.windowId, windowBounds)调整。这与 CDP 中把left/top/width/height/windowState一次性塞进Target.createTarget的做法不同;
  • 若浏览器不支持browser.setClientWindowState能力,异常会被捕获并只记录日志而不中断创建流程("Tolerate not supporting"),即 BiDi 下windowBounds属于"尽力而为";
  • 若 launch 时配置了defaultViewport,BiDi 实现还会对新页面补一次page.setViewport(this.#defaultViewport)

这意味着在 BiDi 模式下,windowBounds相关能力对具体浏览器实现存在兼容性前提,使用建议以 CDP(Chrome)为主。

实战:创建、验证与窗口控制

通过 windowId 验证窗口几何

创建独立窗口后,可用Page.windowId()拿到所属窗口 ID,再用Browser.getWindowBounds()验证几何是否生效(windowId()实现见 packages/puppeteer-core/src/cdp/Page.ts#L439-L445,内部调用Browser.getWindowForTargetgetWindowBounds/setWindowBounds实现见 cdp/Browser.ts#L642-L657):

import puppeteer from 'puppeteer'; const browser = await puppeteer.launch(); const initialBounds = {left: 10, top: 20, width: 800, height: 600}; const page = await browser.newPage({ type: 'window', windowBounds: initialBounds, }); const windowId = await page.windowId(); const actual = await browser.getWindowBounds(windowId); console.log(actual); // {left: 10, top: 20, width: 800, height: 600, ...} // 后续还可动态调整窗口位置/尺寸 await browser.setWindowBounds(windowId, {left: 200, top: 200, width: 1024, height: 768}); await page.close(); await browser.close();

仓库测试 test/src/browser.test.ts 中的用例正是这个模式:创建带windowBounds的窗口页后断言getWindowBounds返回的对象与初始值一致;另一个用例先通过browser.addScreen()添加第二块屏幕,再把新窗口开到副屏上(left: screenInfo.availLeft + 50, ...),演示了newPage的多屏窗口布局能力。

后台页面与可见性

test/src/page.test.ts 的 "should create a background page" 用例验证了background: true的语义:

const page = await context.newPage({background: true}); expect( await page.evaluate(() => { return document.visibilityState; }), ).toBe('hidden');

即后台页面的document.visibilityState'hidden'——如果你依赖requestAnimationFrameIntersectionObserver的可见性触发逻辑,后台页面中这些 API 的行为需要留意。

多窗口并发示例

const browser = await puppeteer.launch({headless: false}); // 标签页 1:共享窗口 const tab1 = await browser.newPage(); // 独立窗口 2:固定位置与尺寸 const win2 = await browser.newPage({ type: 'window', windowBounds: {left: 400, top: 100, width: 1200, height: 800}, }); // 后台窗口 3:不抢焦点 const bg = await browser.newPage({type: 'window', background: true}); const pages = await browser.pages(); console.log(pages.length); // 3

browser.pages()会聚合所有上下文中的页面(api/Browser.ts#L637-L647),可用于确认新建页面已登记;注意非可见页面(如background_page类型的 target)不会出现在列表中。

行为验证:仓库测试用例覆盖

以下测试用例均可在仓库中直接找到,为上述行为提供了验证依据:

  • 新窗口包含检查(test/src/page.test.ts#L38-L48):context.newPage({type: 'window'})后,context.pages()browser.pages()都应包含该页面;
  • 指定位置与尺寸(test/src/page.test.ts#L49-L66):windowBounds: {left: 50, top: 50, width: 750, height: 550}后,通过window.outerWidth/outerHeight断言外框尺寸精确为 750×550,证明 bounds 作用于窗口外框而非视口;
  • 最大化状态(test/src/page.test.ts#L67-L85):windowState: 'maximized'下窗口外框尺寸等于 headless 默认屏幕的 800×600;
  • getWindowBounds回读(test/src/browser.test.ts):创建带 bounds 的窗口后,browser.getWindowBounds(page.windowId())与传入值toMatchObject相等。

与 BrowserContext.newPage() 的关系及 API 索引

browser.newPage(options)本质上等价于browser.defaultBrowserContext().newPage(options)——从 CDP 实现看二者汇聚到同一个_createPageInContext(contextId, options),仅contextId不同(默认上下文为undefined)。两者的取舍:

场景推荐入口
简单脚本、所有页面共享状态browser.newPage()
需要隔离 Cookie/缓存/权限(多用户、多站点)browser.createBrowserContext()context.newPage()
创建纯净环境后释放非默认上下文支持context.close(),默认上下文不可关闭

相关 API 文档索引(均以仓库根目录为基准):

  • CreatePageOptions 类型
  • WindowBounds 接口
  • Page 类
  • Browser.defaultBrowserContext
  • BrowserContext.newPage
  • Browser.getWindowBounds / Browser.setWindowBounds
  • Page.windowId

小结

Browser.newPage()虽只有一个可选参数,但背后覆盖了三类创建语义:默认标签页(type: 'tab')、可精确控制位置/尺寸/窗口状态的独立窗口(type: 'window'+windowBounds)、以及不抢焦点的后台页面(background: true)。CDP 实现通过一次Target.createTarget(含newWindow的 bug workaround)加waitForTarget初始化等待完成创建;BiDi 实现则采用"创建后 setWindowBounds"的两步策略并容忍能力缺失。日常使用记住三点即可:初始 URL 恒为about:blankwindowBounds仅对type: 'window'生效且作用于窗口外框;隔离需求请走BrowserContext.newPage()而非叠加默认上下文页面。

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

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

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

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

立即咨询