Puppeteer DownloadPolicy 详解:用四种策略精确控制浏览器下载行为
在自动化场景中,页面触发的文件下载往往会打断测试流程、污染工作目录,甚至让断言无从进行。Puppeteer 通过 DownloadPolicy 类型(及其配套的 DownloadBehavior 接口)提供了对下载行为的细粒度控制:你可以让浏览器拒绝所有下载、把文件写入指定目录,或按下载 GUID 自动命名文件。读完本文,你将掌握这四种策略的语义差异、在 launch / connect / createBrowserContext 三个入口下的完整用法,以及 CDP 与 WebDriver BiDi 两条协议链路中各自的实现细节与限制。
一、DownloadPolicy 类型定义
DownloadPolicy 是一个四值联合类型,源码位于 DownloadBehavior.ts,并通过 common.ts 统一导出为公开 API:
export type DownloadPolicy = 'deny' | 'allow' | 'allowAndName' | 'default';
四个取值的具体语义如下:
| 策略值 | 行为 | 是否需要 downloadPath |
|---|---|---|
deny |
拒绝该上下文中的一切下载请求 | 否 |
allow |
允许所有下载,文件按原名保存到指定路径 | 是 |
allowAndName |
允许所有下载,但所有文件按下载 GUID 命名 | 是 |
default |
使用浏览器默认行为(如可用),不主动干预 | 否 |
其中 allowAndName 值得单独说明:源码头注释明确写着“Setting this to allowAndName will name all files according to their download guids”(见 DownloadBehavior.ts)。当页面下载内容没有明确的 Content-Disposition 文件名、或你希望在批量下载中避免文件名冲突时,按 GUID 命名是最稳妥的做法,下载完成后再按 GUID 做重命名映射即可。
二、DownloadBehavior 接口:策略 + 路径的组合
DownloadPolicy 单独使用时只是一个字符串,真正下发给浏览器的是 DownloadBehavior 接口,完整定义见 DownloadBehavior.ts:
export interface DownloadBehavior {
/**
* Whether to allow all or deny all download requests, or use default
* behavior if available.
*
* @remarks
* Setting this to `allowAndName` will name all files according to their
* download guids.
*/
policy: DownloadPolicy;
/**
* The default path to save downloaded files to.
*
* @remarks
* Setting this is required if behavior is set to `allow` or `allowAndName`.
*/
downloadPath?: string;
}
两个属性的约束关系需要牢记:
policy是必填项,决定“允不允许下载”;downloadPath是可选的保存目录,但当policy为allow或allowAndName时必须提供——这是接口注释中明确声明的约束,也是 BiDi 协议实现中会主动校验并抛错的点(下文会看到)。
官方 API 文档可参考 DownloadPolicy 类型 与 DownloadBehavior 接口 两个页面。
三、三个配置入口:launch、connect 与 createBrowserContext
downloadBehavior 在 Puppeteer 中有三个注入点,作用域从大到小依次为:整个浏览器默认上下文、connect 时附加的每个上下文、以及单独创建的隔离上下文。
3.1 在 puppeteer.launch() 中配置(作用于默认上下文)
downloadBehavior 是 LaunchOptions 的可选字段,BrowserLauncher.ts 在解析启动参数时会取出该字段并传给浏览器连接层。对应链路是 Browser.ts 中的 _attach:只要传入了 downloadBehavior,就会对默认浏览器上下文调用 setDownloadBehavior。
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: true,
downloadBehavior: {
policy: 'allow',
downloadPath: '/path/to/downloads',
},
});
3.2 在 puppeteer.connect() 中配置
ConnectOptions 同样包含可选的 downloadBehavior 字段(见 ConnectOptions.ts,文档参考 ConnectOptions)。在 cdp/Browser.ts 的连接逻辑中,每个新建的浏览器上下文都会应用该策略,适合连接远程浏览器实例的场景。
3.3 在 browser.createBrowserContext() 中配置(推荐)
细粒度控制的最佳实践是按上下文隔离:只有传入 downloadBehavior 的上下文受影响,其余上下文保持默认行为。BrowserContextOptions 中的 downloadBehavior 字段见 BrowserContextOptions 文档。
仓库自带的测试 download.test.ts 正是用这种方式验证的——创建一个上下文、允许下载并指向临时目录,然后断言文件真实落盘:
using context = await browser.createBrowserContext({
downloadBehavior: {
policy: 'allow',
downloadPath: tempDir,
},
});
const page = await context.newPage();
await page.goto(server.PREFIX + '/download.html');
await page.click('#download');
await waitForFileExistence(join(tempDir, 'download.txt'));
而 deny 策略的对照测试见 download.test.ts:即使传入了 downloadPath,点击下载后文件也不会出现在目录中(waitForFileExistence 会 reject)。注意测试里 deny 时仍传了 downloadPath: '/tmp'——这在 CDP 链路下合法(deny 并不要求路径),说明 downloadPath 与 deny 组合不会报错,只是没有实际效果。
四、底层协议实现:CDP 与 BiDi 的差异
从源码结构看,同一个 DownloadBehavior 在两条协议链路下的落地方式并不相同,这也是理解限制条件(尤其是 allowAndName)的关键。
4.1 CDP 链路:透传给 Browser.setDownloadBehavior
CDP 实现位于 cdp/BrowserContext.ts:
public async setDownloadBehavior(
downloadBehavior: DownloadBehavior,
): Promise<void> {
await this.#connection.send('Browser.setDownloadBehavior', {
behavior: downloadBehavior.policy,
downloadPath: downloadBehavior.downloadPath,
browserContextId: this.#id,
});
}
可以看出 CDP 侧几乎是“透传”:policy 原样作为 behavior 参数下发(因此 default 也对应 CDP 的 default 行为),路径与上下文 ID 一并提交。四个策略值在 CDP 链路上均可用。
4.2 WebDriver BiDi 链路:逐项校验,allowAndName 不受支持
BiDi 的实现在 bidi/core/Browser.ts 的 createUserContext 中,策略到 BiDi 命令的映射是分支式的:
if (options.downloadBehavior?.policy === 'allowAndName') {
throw new UnsupportedOperation(
'`allowAndName` is not supported in WebDriver BiDi',
);
}
if (options.downloadBehavior?.policy === 'allow') {
if (options.downloadBehavior.downloadPath === undefined) {
throw new UnsupportedOperation(
'`downloadPath` is required in `allow` download behavior',
);
}
await this.session.send('browser.setDownloadBehavior', {
downloadBehavior: {
type: 'allowed',
destinationFolder: options.downloadBehavior.downloadPath,
},
userContexts: [userContext],
});
}
if (options.downloadBehavior?.policy === 'deny') {
await this.session.send('browser.setDownloadBehavior', {
downloadBehavior: {type: 'denied'},
userContexts: [userContext],
});
}
由此可以归纳出 BiDi 链路的三条规则(注意 Puppeteer 默认在 Firefox 下使用 webDriverBiDi 协议,见 BrowserLauncher.ts):
allowAndName直接抛UnsupportedOperation:BiDi 的browser.setDownloadBehavior没有“按 GUID 命名”的对应能力;allow时downloadPath缺失会抛错:这正是接口文档中“Setting this is required if behavior is set toalloworallowAndName”的运行时兜底;default策略不发任何 BiDi 命令,即保持浏览器原生下载行为。
4.3 各策略在两种协议下的支持矩阵
| policy | CDP | WebDriver BiDi |
|---|---|---|
deny |
支持(下发 behavior: 'deny') |
支持(type: 'denied') |
allow |
支持(需 downloadPath) |
支持(缺 downloadPath 时抛错) |
allowAndName |
支持(文件按下载 GUID 命名) | 不支持,抛 UnsupportedOperation |
default |
支持(下发 behavior: 'default') |
不发送命令,保持默认 |
如果你的脚本要跨 Chrome / Firefox 或跨协议运行,需要针对 allowAndName 做能力探测:捕获 UnsupportedOperation,或仅在 CDP 协议下使用该策略。
五、实践建议
- 按上下文隔离策略:优先在
createBrowserContext级别设置,避免影响同浏览器中的其他页面(参考 download.test.ts 的组织方式); - 目录管理:
downloadPath建议指向脚本创建的临时目录(测试用例使用mkdtemp(join(tmpdir(), 'downloads-'))并在使用后rm清理,见 download.test.ts),下载内容以页面原始文件名落盘,注意同目录下的同名覆盖问题——这也是allowAndName的价值所在; - 协议感知:Chrome 默认走 CDP,全部策略可用;Firefox 默认走 WebDriver BiDi,
allowAndName不可用,allow必须显式给出downloadPath(实现依据见 bidi/core/Browser.ts); - 相关 API 文档:DownloadPolicy、DownloadBehavior、ConnectOptions、BrowserContextOptions 可交叉查阅完整的类型定义与默认值说明。
参考文件
- 类型与接口定义:packages/puppeteer-core/src/common/DownloadBehavior.ts
- CDP 协议下发:packages/puppeteer-core/src/cdp/BrowserContext.ts
- CDP 连接/附加链路:packages/puppeteer-core/src/cdp/Browser.ts
- BiDi 协议校验与下发:packages/puppeteer-core/src/bidi/core/Browser.ts
- 启动参数解析:packages/puppeteer-core/src/node/BrowserLauncher.ts
- 行为验证测试:test/src/download.test.ts
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 StartedRust0623
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