Puppeteer 下载文件:Node.js 开发者的完整指南
Senior Web Scraping Engineer
TL;DR:
- 无头 Chrome 的下载是 CDP 问题,而不是 Puppeteer API 问题。 Puppeteer 没有一流的
page.download(); 你需要通过Browser.setDownloadBehavior直接操作 Chrome DevTools 协议并监听Browser.downloadProgress。 allowAndName加上eventsEnabled是实际报告完成的组合。 单独设置行为会静音 "保存文件" 对话框; 打开事件让你能够await完成的字节计数,而不是用固定的延迟进行猜测。- 在云浏览器中,文件在服务器端下载——因此你通过页面获取字节。 同源的
fetch()在page.evaluate()中返回文件作为 base64,你解码并写入本地磁盘。没有图形界面机器,也没有共享卷。 - 反机器人和地理限制也适用于下载。 许多受限文件只对在正确国家的住宅 IP 提供服务; 在请求之前用
proxyCountry固定出口是成功下载的组成部分。 - 整个流程在 Scrapeless Scraping Browser 上运行,只需一个
Puppeteer.connect()调用。 云浏览器创建会话,应用反检测指纹识别,并向 Puppeteer 提供标准的 WebSocket 端点——你的下载代码从这里开始就是纯 CDP。 - 免费开始。 新的 Scrapeless 账户包括免费的 Scraping Browser 运行时间——在 app.scrapeless.com 注册。
介绍:为什么用 Puppeteer "下载文件" 看起来比实际困难
Puppeteer 为 Node.js 开发者提供了对 Chrome 页面的完全控制——点击、输入、读取 DOM。文件下载是它没有专用方法的唯一常见任务。没有 await page.download(selector)。在默认的无头会话中点击下载链接会做两件无助的事:导航被吞噬,或者由于没有配置下载目录而被 Chrome 阻止传输。
真正的控制位于更底层,在 Chrome DevTools 协议(CDP)中。Browser.setDownloadBehavior 告诉 Chrome 文件去哪里以及是否允许它们;Browser.downloadWillBegin 和 Browser.downloadProgress 报告生命周期,因此你可以等待文件完成的确切时刻,而不是等待和希望。
在自己的机器上运行这段代码,你仍然有两个与 CDP 无关的问题:目标网站将无头 Chrome 标记为机器人,而受限文件通常拒绝任何不是在预期地区的住宅 IP 的请求。本指南在 Scrapeless Scraping Browser 上运行整个流程——一个反检测的云浏览器,创建会话,应用住宅出口,并向 Puppeteer 提供标准的 WebSocket 端点。下载代码保持纯粹的 CDP;检测和地理问题由运行时处理。有一个特别之处是云浏览器添加的——文件在服务器端下载——本指南的第二部分展示了将这些字节拉回本地磁盘的模式。
你可以用它做什么
- 在登录后拉取报告、导出和报表——发票 PDF、CSV 导出、数据室文件——一旦会话经过身份验证。
- 捕获生成的文件,页面 从 blob 构建客户端(图表导出,"下载 CSV" 按钮),这些文件永远不会存在为静态 URL。
- 抓取受限文档,这些文件只对特定国家的住宅 IP 提供服务,通过在请求之前固定出口。
- 确定性等待下载完成,使用
downloadProgress而不是脆弱的固定延迟。 - 在多个文件或账户之间扩展相同的脚本,无需建立图形界面机器——浏览器是远程的。
为什么选择 Scrapeless Scraping Browser
Scrapeless Scraping Browser 是一个可定制的、反检测的云浏览器,专为网络爬虫和 AI 代理设计。对于 Puppeteer 下载,它提供:
- 标准的 Puppeteer 连接——
Puppeteer.connect()返回一个普通的Browser对象,因此你已经知道的每个 CDP 调用都可以正常工作。 - 195+ 个国家的住宅代理——固定
proxyCountry以便受限文件能提供给它们所信任的 IP。 - 云端反检测指纹识别——会话看起来像真实的浏览器,因此下载链接实际上是可用的,而不是隐藏在机器人墙后面。
- 会话持久性——在导航之间保持 cookies 和身份验证状态,这使得登录下载成为可能。
- 自开发的 Chromium——完整的 CDP 接口,因此
Browser.setDownloadBehavior和下载事件表现跟文档中描述的一样。
在免费计划中获取你的 API 密钥,访问 app.scrapeless.com。
先决条件
- Node.js 18 或更新版本
- 一个 Scrapeless 账户和 API 密钥——在 app.scrapeless.com 注册
- 对 Puppeteer 和
async/await的基本了解
安装
1. 添加 SDK 和 Puppeteer
Scrapeless SDK 生成云会话并将 Puppeteer 连接到它。您需要 puppeteer-core(没有捆绑 Chromium 的协议客户端——浏览器是远程的):
bash
npm install @scrapeless-ai/sdk puppeteer-core
2. 设置您的 API 密钥
从环境中读取;绝不要硬编码:
bash
export SCRAPELESS_API_KEY="your_api_token_here"
配置:将 Puppeteer 连接到云浏览器
Puppeteer.connect() 创建一个 Scrapeless 会话并返回一个标准的 Puppeteer Browser。在此处固定代理所在国家,以便每个请求——包括下载——都从您想要的地区发出:
javascript
import { Puppeteer } from '@scrapeless-ai/sdk';
const browser = await Puppeteer.connect({
apiKey: process.env.SCRAPELESS_API_KEY,
sessionName: 'puppeteer-downloads',
proxyCountry: 'US',
sessionTTL: 300, // 会话保持活动的秒数
});
const page = await browser.newPage();
从这里开始,browser 和 page 是普通的 Puppeteer 对象。下面的内容是标准的 CDP。
基本实现:设置下载行为并等待完成
有两个重要的 CDP 调用和一个您要等待的事件:
Browser.setDownloadBehavior中的behavior: 'allowAndName'允许下载并在服务器端以 GUID 命名文件。eventsEnabled: true是大多数指南错过的部分——没有它,下载进度事件不会触发,您就只能等待。Browser.downloadWillBegin告诉您传输已开始,并提供suggestedFilename和guid。Browser.downloadProgress报告inProgress→completed(或canceled),并提供receivedBytes和totalBytes。
javascript
const cdp = await page.createCDPSession();
await cdp.send('Browser.setDownloadBehavior', {
behavior: 'allowAndName',
downloadPath: '/tmp/downloads',
eventsEnabled: true,
});
// 仅在传输实际完成时解决
const downloadComplete = new Promise((resolve) => {
let meta = {};
cdp.on('Browser.downloadWillBegin', (e) => {
meta = { filename: e.suggestedFilename, guid: e.guid };
});
cdp.on('Browser.downloadProgress', (e) => {
if (e.state === 'completed') {
resolve({ ...meta, totalBytes: e.totalBytes });
}
});
});
现在触发下载——点击按钮、链接,或者像这里一样,生成客户端文件——并等待这个 Promise:
javascript
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
// 一个 "下载 CSV" 的按钮,通过 blob 在浏览器中构建文件
await page.evaluate(() => {
const blob = new Blob(['col1,col2\n1,2\n3,4\n'], { type: 'text/csv' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'report.csv';
document.body.appendChild(a);
a.click();
});
const result = await downloadComplete;
console.log(result);
// { filename: 'report.csv', guid: '845c9455-…', totalBytes: 18 }
Promise 会在 state === 'completed' 到达时立即解决——没有固定的等待时间,您可以获得实际的字节数。
通过免费计划获取您的 API 密钥:app.scrapeless.com
云端妙招:将字节获取到本地磁盘
在本地可视运行中,allowAndName 将文件写入您的 downloadPath,然后您从磁盘读取它。在云浏览器中,文件位于 远程 会话上,而不是您的机器上——因此 downloadPath 是服务器端的,而您本地的 /tmp/downloads 保持为空。
可靠的模式是在页面上下文中获取文件的字节并将其作为 base64 返回,然后进行解码并本地写入。唯一的规则:fetch() 受 同源政策 限制,因此首先导航到文件自己的源地址,然后再进行获取:
javascript
import { writeFileSync } from 'node:fs';
const fileUrl = 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf';
// 降落到文件的原点,以便 fetch() 是同源(没有 CORS 阻塞)
await page.goto(new URL(fileUrl).origin, { waitUntil: 'domcontentloaded' });
const out = await page.evaluate(async (url) => {
const res = await fetch(url);
const bytes = new Uint8Array(await res.arrayBuffer());
let binary = '';
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
return {
status: res.status,
type: res.headers.get('content-type'),
b64: btoa(binary),
};
}, fileUrl);
const buffer = Buffer.from(out.b64, 'base64');
writeFileSync('./dummy.pdf', buffer);
console.log({ status: out.status, type: out.type, bytes: buffer.length });
// { status: 200, type: 'application/pdf; qs=0.001', bytes: 13264 }
因为获取操作在经过身份验证的页面内运行,它 携带会话的 cookies — 因此登录后的文件下载和未登录前的文件完全相同。CDP 事件方法和这种获取方法是互补的:使用事件了解何时点击触发的传输完成;当你已经有文件 URL 并希望本地获取字节时,使用页面内的获取操作。
高级模式
为被限制的文件固定出口。 如果文档仅提供给一个区域,则在连接时设置 proxyCountry。下载将继承该住宅 IP — 无需每个请求的代理切换。
认证后再下载。 在登录流程和下载中保持相同的 page(因此相同的会话和 cookies)。无缝的会话持久性使身份验证状态在导航中保持活跃,因此上面的页面内获取操作看到的是登录的上下文。
读取大文件的进度。 Browser.downloadProgress 也会在 state: 'inProgress' 和不断增加的 receivedBytes 时触发。记录与 totalBytes 的比例,以显示一个真实的进度条而不是旋转图标。
完成后关闭会话。 await browser.close() 会迅速结束云会话,这样你就不会占用未使用的运行时。
故障排除
| 症状 | 原因 | 解决方案 |
|---|---|---|
从未触发 downloadProgress 事件 |
eventsEnabled 未设置 |
通过 Browser.setDownloadBehavior 传递 eventsEnabled: true |
| 点击导航而不是下载 | 服务器将文件以内联方式发送,而非作为附件 | 在文件 URL 上使用页面内的 fetch() 检索模式 |
本地 downloadPath 为空 |
文件在远程云会话中,而非你的磁盘 | 通过页面抓取字节(参见“云扭转”) |
fetch() 抛出 CORS 错误 |
页面来源与文件来源不同 | 在获取之前使用 page.goto(new URL(fileUrl).origin) |
| 下载链接从未出现 | 该站点将其隐藏在机器人/地理墙后 | 固定 proxyCountry 并允许云浏览器的指纹识别呈现真实页面 |
结论:将下载作为你的管道中的一等步骤
使用 Puppeteer 下载文件归结为三个步骤:告诉 Chrome 允许并报告下载(使用 Browser.setDownloadBehavior 配置 eventsEnabled),等待真实完成事件(Browser.downloadProgress),并在云浏览器上通过同源的页面内获取来拉取字节。在 Scrapeless Scraping Browser 上运行将这两个与 CDP 无关的困难部分——机器人检测和地理限制——纳入运行时,因此在公共文件上有效的相同脚本也适用于受限制的已登录文件。对于以表单驱动的流程,配合 Scrapling 云浏览器指南,请查看 Scraping Browser 产品页面 和 文档 获取完整的 CDP 表面。固定出口,为经过身份验证的文件保持会话活跃,并等待 completed 而不是睡眠。
准备好构建你的 AI 驱动数据管道了吗?
加入我们的社区以获得免费计划并与构建下载和提取管道的开发者联系: Discord · Telegram。
在 app.scrapeless.com 注册以获取免费的 Scraping Browser 运行时间,并根据工作流程的文件、区域和登录调整上述模式。查看 定价 以了解规模。
常见问题解答
问:Puppeteer 有内置的下载方法吗?
没有。没有 page.download()。你通过 Chrome DevTools 协议配置下载,使用 Browser.setDownloadBehavior 并通过 Browser.downloadWillBegin 和 Browser.downloadProgress 事件观察它们。
问:下载文件需要代理吗?
对于公共文件,并不严格要求。对于按区域限制或仅提供给住宅 IP 的文件,是的 — 在连接时固定 proxyCountry 以便传输从网站信任的 IP 中发出。
问:在云浏览器上运行时,为什么我的本地下载目录为空?
因为文件下载到远程会话,而不是你的机器。通过在 page.evaluate() 内获取文件 URL 并在本地解码 base64 来检索字节,如上所示。
问:如何下载位于登录后面的文件?
首先在同一 页面 上进行身份验证,然后运行页面内的 fetch() — 它会携带会话 cookie,因此文件会以登录用户的身份下载。Scrapeless 会话持久性在导航过程中保持该身份验证状态。
问:我该如何等待下载完成,而不是猜测超时时间?
监听 Browser.downloadProgress,在 state === 'completed' 时解析。该事件包含 receivedBytes 和 totalBytes,因此您还可以呈现真实的进度。
问:我可以在没有 AI 代理或额外工具的情况下运行这个吗?
可以。这是纯 Puppeteer 和 CDP 结合 Scrapeless 会话的使用 — 不需要代理。该 SDK 只负责建立连接。
在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。



