如何用 Puppeteer 自动截取网站截图
使用 Puppeteer 和 JavaScript 自动截取网站截图。通过可重复使用的脚本截取单页、整页、URL 批次和移动端视口。

本文会分阶段编写 Puppeteer 截图脚本:先截取单个视口,再截取整页,然后制作可重复使用的 URL 执行器,最后加入移动端视口。
Puppeteer 很适合这项工作,因为第一版脚本可以很短:启动浏览器、打开 URL,再调用 page.screenshot()。官方的 Puppeteer 概述把它称为控制 Chrome 或 Firefox 的 JavaScript API,默认以无头模式运行。
示例都以 Shotomatic 工具页 https://www.shotomatic.com/tools 为目标,因此可以查看同一页面在视口截图、整页截图、可批量运行版本和移动端截图中的变化。
**简而言之:**希望用代码完成截图自动化时,用 Puppeteer。设置视口,等待所需的页面状态,调用
page.screenshot(),再加入整页截图、批量处理、重试和报告。如果只需要截取一组公开 URL,又不想维护脚本,Shotomatic Website Capture这类无代码工作流可能更简单。
适合使用 Puppeteer 的情况
截图工作流本来就应该写进代码,而且任务主要是控制浏览器时,Puppeteer 很合适。
以下情况适合使用:
- 从 Node.js 脚本截取公开页面;
- 使用 Chrome 的渲染行为生成截图;
- 截图前登录或关闭弹窗;
- 保存视口、整页、PNG、JPEG 或 WebP 截图。
截图来自真正的无头浏览器,而不是图片库。这正是 Puppeteer 在这里有用的主要原因。
为截图脚本安装 Puppeteer
在普通 Node.js 项目中安装 Puppeteer:
$ mkdir website-screenshots-puppeteer
$ cd website-screenshots-puppeteer
$ npm init -y
$ npm i puppeteer
$ mkdir scripts screenshots
标准的 puppeteer 软件包会在安装期间下载兼容的 Chrome for Testing。Puppeteer 官方安装文档还说明,它会下载 chrome-headless-shell 二进制文件。如果已经自行管理浏览器,或要连接远程浏览器,请改用 puppeteer-core。
现代软件包管理器有时会阻止安装脚本。如果 Puppeteer 已安装但浏览器缺失,官方配置文档说明了如何单独安装浏览器:
$ npx puppeteer browsers install
截取一个网站页面
创建 scripts/capture-one.mjs:
import puppeteer from "puppeteer";
import { mkdir } from "node:fs/promises";
const url = "https://www.shotomatic.com/tools";
const outputPath = "screenshots/shotomatic-tools.png";
const browser = await puppeteer.launch();
const page = await browser.newPage();
try {
await mkdir("screenshots", { recursive: true });
await page.setViewport({
width: 1440,
height: 1000,
deviceScaleFactor: 1,
});
await page.goto(url, {
waitUntil: "networkidle2",
timeout: 45_000,
});
await page.screenshot({ path: outputPath });
console.log(`Saved ${outputPath}`);
} finally {
await browser.close();
}
运行脚本:
$ node scripts/capture-one.mjs
运行后会生成 screenshots/shotomatic-tools.png:
Puppeteer 官方截图指南采用相同的基本流程:启动浏览器、打开页面、前往 URL,再调用 page.screenshot()。除非直接传入 type 选项,否则 path 的扩展名会决定输出格式。Puppeteer 的 ScreenshotOptions 和 ImageFormat 文档介绍了 fullPage、path、quality,以及受支持的 png、jpeg 和 webp 格式。
请在导航前设置视口。Puppeteer 的 page.setViewport() 文档特别提醒了这一点,因为页面加载后再调整尺寸时,有些网站的行为会发生变化,移动端布局尤其如此。
截取整页截图
要截取整页截图,请传入 fullPage: true。Puppeteer 的 ScreenshotOptions 把 fullPage 定义为截取完整页面的选项:
await page.screenshot({
path: "screenshots/shotomatic-tools-full-page.png",
fullPage: true,
});
需要整个可滚动文档时,用整页截图;关心页面在特定屏幕尺寸下的样子时,用视口截图。
整页截图仍然取决于页面状态。fullPage: true 只会改变截图区域,不会强制渲染懒加载图片、动画或进入视口才出现的区块。下文的懒加载部分会说明页面状态为什么重要。
构建可重复使用的 URL 执行器
确认单次截图正常后,把目标 URL 移到可以接收列表的脚本中。示例仍然只用 /tools,便于查看输出。之后可以向同一数组添加更多 URL。
创建 scripts/capture-batch.mjs:
import puppeteer from "puppeteer";
import { mkdir } from "node:fs/promises";
const urls = ["https://www.shotomatic.com/tools"];
const outputDir = "screenshots";
function filenameForUrl(url, index) {
const parsed = new URL(url);
const rawName = `${parsed.hostname}${parsed.pathname}`;
const slug = rawName
.replace(/^www\./, "")
.replace(/\/$/, "")
.replace(/[^a-z0-9]+/gi, "-")
.replace(/^-+|-+$/g, "")
.toLowerCase();
return `${String(index + 1).padStart(2, "0")}-${slug || "home"}.png`;
}
async function captureUrl(browser, url, index) {
const context = await browser.createBrowserContext();
const page = await context.newPage();
const outputPath = `${outputDir}/${filenameForUrl(url, index)}`;
try {
await page.setViewport({
width: 1440,
height: 1000,
deviceScaleFactor: 1,
});
await page.goto(url, {
waitUntil: "networkidle2",
timeout: 45_000,
});
await page.waitForSelector("body", {
visible: true,
timeout: 15_000,
});
await page.screenshot({
path: outputPath,
fullPage: true,
});
console.log(`Captured ${url} -> ${outputPath}`);
return { url, ok: true, outputPath };
} catch (error) {
console.error(`Failed ${url}: ${error.message}`);
return { url, ok: false, error: error.message };
} finally {
await context.close();
}
}
await mkdir(outputDir, { recursive: true });
const browser = await puppeteer.launch();
try {
const results = [];
for (const [index, url] of urls.entries()) {
results.push(await captureUrl(browser, url, index));
}
const failed = results.filter((result) => !result.ok);
if (failed.length > 0) {
process.exitCode = 1;
console.error(`${failed.length} screenshot(s) failed.`);
}
} finally {
await browser.close();
}
这样会保持一个浏览器运行,并为每个 URL 创建独立的浏览器上下文。Puppeteer 的 BrowserContext 文档把这些上下文称为相互隔离的用户会话,各自拥有 Cookie 和存储。页面在截图期间写入 Cookie 或本地存储时,这种隔离很有用。
循环有意按顺序运行。速度较慢,但一开始更容易调试。
为较大批次加入并发
只有几个 URL 时,顺序截图已经够用。列表较大时,请加入一个较小的并发上限。
先从 2–4 个并发截图开始。无头浏览器仍会使用 CPU 和内存,过于密集的批量任务也可能触发限流,或导致页面加载不稳定。
加入这个辅助函数:
async function runWithConcurrency(items, limit, worker) {
const results = new Array(items.length);
let nextIndex = 0;
async function runNext() {
while (nextIndex < items.length) {
const currentIndex = nextIndex;
nextIndex += 1;
results[currentIndex] = await worker(items[currentIndex], currentIndex);
}
}
const workers = Array.from(
{ length: Math.min(limit, items.length) },
() => runNext(),
);
await Promise.all(workers);
return results;
}
然后替换原来的顺序循环:
const results = await runWithConcurrency(urls, 3, (url, index) =>
captureUrl(browser, url, index),
);
这样既能提高批量任务吞吐量,又不会让脚本无限制地打开页面。
截取移动端页面
要截取移动端尺寸,请在 page.goto() 前设置视口:
await page.setViewport({
width: 390,
height: 844,
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
});
await page.goto("https://www.shotomatic.com/tools", {
waitUntil: "networkidle2",
});
await page.screenshot({
path: "screenshots/shotomatic-tools-mobile-viewport.png",
});
只需要大致的移动端视口时,可以使用这种方式。下面是用该视口截取的同一工具页:
如果需要更完整的设备预设,Puppeteer 还提供已知设备:
import puppeteer, { KnownDevices } from "puppeteer";
const iPhone = KnownDevices["iPhone 15"];
await page.emulate(iPhone);
await page.goto("https://www.shotomatic.com/tools", {
waitUntil: "networkidle2",
});
await page.screenshot({
path: "screenshots/shotomatic-tools-iphone-15.png",
fullPage: true,
});
视口、用户代理、触控行为和设备像素比都很重要时,设备模拟更有用。Puppeteer 的 page.emulate() 文档说明它会同时设置用户代理和视口,因此要在导航前调用。
处理懒加载内容
许多页面只有在图片或区块进入视口后才会加载或显示。整页截图过早时,首屏以下还没有出现的内容会在截图中留下空白。
截取整页前,先滚动浏览文档,触发常见的懒加载行为:
async function triggerLazyLoading(page) {
await page.evaluate(async () => {
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const viewportHeight = window.innerHeight;
const scrollHeight = document.body.scrollHeight;
for (let y = 0; y < scrollHeight; y += viewportHeight) {
window.scrollTo(0, y);
await delay(150);
}
window.scrollTo(0, 0);
});
}
在 page.screenshot() 前调用:
await page.goto(url, {
waitUntil: "networkidle2",
timeout: 45_000,
});
await triggerLazyLoading(page);
await page
.waitForFunction(
() => Array.from(document.images).every((image) => image.complete),
{ timeout: 15_000 },
)
.catch(() => {});
await page.screenshot({
path: outputPath,
fullPage: true,
});
有些页面还需要额外处理。自定义滚动容器、动画、延迟的 API 调用和虚拟化内容,都可能需要页面专用的选择器或状态判断。
选择合适的等待策略
大多数截图可靠性问题都与时机有关。
对许多截图脚本来说,networkidle2 是合理的起点,Puppeteer 自己的截图指南也在基础示例中使用它。但它只能作为起点,不能保证页面已经就绪。有些网站会一直保持后台请求,另一些则要等网络安静后才渲染主要内容。
常用的等待策略:
- 普通公开页面可以用
waitUntil: "networkidle2"; - 特定元素能够证明页面就绪时,使用
page.waitForSelector(); - 就绪条件取决于应用状态时,使用
page.waitForFunction(); - 图片采用懒加载时,截图前先滚动;
- 不要把固定延时作为主要等待规则。
例如:
await page.goto(url, {
waitUntil: "networkidle2",
timeout: 45_000,
});
await page.waitForSelector("main", {
visible: true,
timeout: 15_000,
});
await page.screenshot({
path: outputPath,
fullPage: true,
});
如果目标网站由你维护,等待选择器通常比等待通用网络条件更可靠。
加入重试与报告
真实的批量截图会因为一些常见原因失败:临时网络错误、页面过慢、重定向、限流、Cookie 横幅和第三方脚本。
为每个 URL 加入重试:
async function withRetries(task, retries = 2) {
let lastError;
for (let attempt = 0; attempt <= retries; attempt += 1) {
try {
return await task();
} catch (error) {
lastError = error;
console.warn(`Attempt ${attempt + 1} failed: ${error.message}`);
}
}
throw lastError;
}
用它包裹截图操作:
await withRetries(async () => {
await page.goto(url, {
waitUntil: "networkidle2",
timeout: 45_000,
});
await page.waitForSelector("body", {
visible: true,
timeout: 15_000,
});
await page.screenshot({
path: outputPath,
fullPage: true,
});
});
对于还会重复运行的任务,请写入一份 JSON 报告,记录每个 URL、输出路径、成功状态、错误消息和时间戳。截图展示页面,报告则说明这次任务的情况。
实用的文件夹结构
准备重复运行的任务,应把输入、输出和报告分开保存:
website-screenshots-puppeteer/
scripts/
capture-batch.mjs
urls/
product-pages.txt
competitor-pages.txt
screenshots/
2026-06-23-product-pages/
2026-06-23-competitors/
reports/
2026-06-23-product-pages.json
这样更容易重新运行和调试工作流,也能让每组截图与生成它的 URL 列表保持关联。
常见错误
- 页面就绪前截图:
page.goto()执行完,并不一定表示所需内容已经渲染。请等待选择器或应用状态。 - **视口设置得太晚:**在导航前设置视口,让页面从一开始就按正确布局加载。
- **把
fullPage当作懒加载处理:**整页截图只会改变截图区域,并不能保证每张延迟加载的图片都已加载。 - **一次打开太多页面:**请从较低并发开始。浏览器自动化可能让你的电脑或目标网站不堪重负。
- **重复使用文件名:**URL 之间可能只有末尾斜杠、查询字符串或路径大小写不同。请加入索引并清理文件名。
- **跳过清理:**关闭上下文和浏览器。页面与会话泄漏后,长时间任务会变得混乱。
Puppeteer 截图脚本的替代方式
Puppeteer 只是自动截取网站截图的方式之一。
如果这项工作需要在代码中完成,Playwright是最接近的替代方案。如何用 Playwright 自动截取网页介绍了具体方法。如果截图属于更大的测试套件,需要覆盖 Chromium、Firefox 和 WebKit,或者希望在截图流程中使用 Playwright Test 的测试运行器、跟踪和报告,Playwright 通常更合适。
如果要把截图功能集成进产品,与自行维护浏览器相比,截图 API可能更省事。一旦脚本必须为其他人稳定运行,浏览器二进制文件、CI 内存、重试、清理和登录状态都会成为你需要处理的问题。
如果不想编写和维护这个脚本,请阅读如何无代码自动截取网页。如果要从 URL 列表批量截图、为每个页面设置截图选项、检查结果并导出文件,又不想维护 Puppeteer 脚本,可以使用 Shotomatic 的 Website Capture。
常见问题
Puppeteer 可以自动截取网站截图吗?
可以。Puppeteer 能启动浏览器、打开 URL、设置视口,并通过 page.screenshot() 保存截图。
Puppeteer 可以截取整页截图吗?
可以。向 page.screenshot() 传入 fullPage: true,即可截取整个可滚动页面,而不只是可见视口。
可以用 Puppeteer 截取多个 URL 吗?
可以。保持一个浏览器运行,为每个 URL 创建页面或浏览器上下文,并为较大批次加入并发限制。
截图时 Puppeteer 比 Playwright 更好吗?
对于主要针对 Chrome 的截图脚本,Puppeteer 往往更简单。需要覆盖 Chromium、Firefox 和 WebKit,或要围绕截图建立更大的测试工作流时,Playwright 通常更合适。
什么时候应该用 Shotomatic 代替 Puppeteer?
任务是无代码 URL 列表截图,并且需要由人运行、检查和导出,又不想维护 JavaScript 脚本时,请用 Shotomatic。
参考资料
以上示例已根据官方文档核对:
- Puppeteer 概述、安装和配置;
- Puppeteer 截图指南、
Page.screenshot()和 ScreenshotOptions; - Puppeteer
BrowserContext、page.setViewport()、KnownDevices和page.emulate(); - Puppeteer
page.waitForSelector()和page.waitForFunction(); - Playwright 浏览器文档和库文档。
相关文章
查看更多文章如何用 Playwright 自动截取网站截图
使用 Playwright 和 JavaScript 自动截取网站截图,包括单页、整页、移动端视图,以及带重试机制的 URL 批量截图。




无需编写代码,自动截取网页
Website Capture 可以截取 URL 列表,为每个页面设置选项,检查结果并导出,不需要维护脚本。