教程
Shotomatic Team
约 18 分钟阅读

如何用 Puppeteer 自动截取网站截图

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

笔记本电脑上显示 Puppeteer 网站截图脚本代码

本文会分阶段编写 Puppeteer 截图脚本:先截取单个视口,再截取整页,然后制作可重复使用的 URL 执行器,最后加入移动端视口。

Puppeteer 很适合这项工作,因为第一版脚本可以很短:启动浏览器、打开 URL,再调用 page.screenshot()。官方的 Puppeteer 概述把它称为控制 Chrome 或 Firefox 的 JavaScript API,默认以无头模式运行。

示例都以 Shotomatic 工具页 https://www.shotomatic.com/tools 为目标,因此可以查看同一页面在视口截图、整页截图、可批量运行版本和移动端截图中的变化。

**简而言之:**希望用代码完成截图自动化时,用 Puppeteer。设置视口,等待所需的页面状态,调用 page.screenshot(),再加入整页截图、批量处理、重试和报告。如果只需要截取一组公开 URL,又不想维护脚本,Shotomatic Website Capture这类无代码工作流可能更简单。

说明:我们开发了 Shotomatic,因此示例采用我们的公开工具页。对于其他获准截取的公开网站,也可以使用相同的 Puppeteer 模式。如果需要的是无代码 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 capture-one 示例输出的 Shotomatic 工具页截图
上述脚本输出的工具页,视口尺寸为 1440 × 1000。

Puppeteer 官方截图指南采用相同的基本流程:启动浏览器、打开页面、前往 URL,再调用 page.screenshot()。除非直接传入 type 选项,否则 path 的扩展名会决定输出格式。Puppeteer 的 ScreenshotOptionsImageFormat 文档介绍了 fullPagepathquality,以及受支持的 pngjpegwebp 格式。

请在导航前设置视口。Puppeteer 的 page.setViewport() 文档特别提醒了这一点,因为页面加载后再调整尺寸时,有些网站的行为会发生变化,移动端布局尤其如此。

截取整页截图

要截取整页截图,请传入 fullPage: true。Puppeteer 的 ScreenshotOptionsfullPage 定义为截取完整页面的选项:

await page.screenshot({
  path: "screenshots/shotomatic-tools-full-page.png",
  fullPage: true,
});

需要整个可滚动文档时,用整页截图;关心页面在特定屏幕尺寸下的样子时,用视口截图

Puppeteer 输出的 Shotomatic 免费工具页整页截图
同一 `/tools` 页面在首屏以下区块完成渲染后的整页输出。

整页截图仍然取决于页面状态。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 输出的 Shotomatic 工具页移动端视口截图
Puppeteer 输出的移动端视口截图。脚本使用了 deviceScaleFactor 2,因此文件尺寸为 780 × 1688。

如果需要更完整的设备预设,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() 文档说明它会同时设置用户代理和视口,因此要在导航前调用。

处理懒加载内容

许多页面只有在图片或区块进入视口后才会加载或显示。整页截图过早时,首屏以下还没有出现的内容会在截图中留下空白。

懒加载区块渲染前,Puppeteer 整页截图中出现大块空白
立即截图:页面高度正确,但部分内容尚未渲染。
滚动经过懒加载区块后得到的 Puppeteer 整页截图
滚动后截图:首屏以下的区块已有机会显示。

截取整页前,先滚动浏览文档,触发常见的懒加载行为:

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。

参考资料

以上示例已根据官方文档核对:

相关文章

查看更多文章

如何用 Playwright 自动截取网站截图

使用 Playwright 和 JavaScript 自动截取网站截图,包括单页、整页、移动端视图,以及带重试机制的 URL 批量截图。

约 18 分钟阅读
笔记本电脑上显示网站自动截图脚本代码

如何不写代码自动截取网站截图

选择无需代码的网站截图自动化方式:URL 列表截图、自动化平台配合截图 API、监控工具或浏览器辅助工具。

约 12 分钟阅读
用于检查网站截图工作流的桌面多显示器

如何在 Mac 上批量截取网站页面

用 Shotomatic 在 Mac 上批量截取网站:粘贴 URL 列表,选择桌面、平板或手机预设,并行截图,再导出用于审计或报告。

约 11 分钟阅读
多台笔记本电脑屏幕显示数字工作区

无需编写代码,自动截取网页

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

如何用 Puppeteer 自动截取网站截图 | 博客 | Shotomatic