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

本文会分阶段编写 Playwright 截图脚本:先截取单个视口,再截取整页,然后制作可重复使用的 URL 执行器,最后加入移动端视口。
如果截图需要与驱动它们的代码放在一起,Playwright 很合适。它提供真实浏览器渲染、相互隔离的浏览器上下文、明确的视口控制、设备模拟,以及等待目标页面状态的接口。
示例都以 Shotomatic 功能页 https://www.shotomatic.com/features 为目标,因此可以比较同一页面的视口截图、整页截图、可批量运行版本和移动端截图。
**简而言之:**截图自动化本来就应该写进代码时,用 Playwright。创建浏览器上下文,设置视口或设备,等待所需的页面状态,再调用
page.screenshot();随后加入整页截图、批量处理、重试和报告。如果不想承担这套脚本的编写与维护工作,Shotomatic Website Capture可能更简单。
适合使用 Playwright 的情况
截图必须遵循浏览器逻辑时,请用 Playwright。
以下情况适合使用:
- 在 CI 中截取截图;
- 截图前需要登录;
- 等待特定选择器或应用状态;
- 在 Chromium、Firefox 或 WebKit 上运行同一项截图任务;
- 根据明确的视口设置制作响应式截图;
- 在内部工具中生成截图。
需要截图的人不是脚本维护者时,使用起来就没那么方便。如果这项工作应该由代码负责,Playwright 是不错的起点。
为截图脚本安装 Playwright
编写普通 Node.js 脚本时,先安装 Playwright 库和准备自动控制的浏览器:
$ mkdir website-screenshots-playwright
$ cd website-screenshots-playwright
$ npm init -y
$ npm i -D playwright
$ npx playwright install chromium
Playwright 库文档采用相同的基本流程:安装软件包、安装浏览器、导入 Playwright、启动浏览器,再与页面交互。以后需要 Firefox 或 WebKit 时,再安装相应浏览器。
为脚本和输出创建文件夹:
$ mkdir scripts screenshots
截取一个网站页面
创建 scripts/capture-one.js:
const { chromium } = require("playwright");
const fs = require("node:fs/promises");
const url = "https://www.shotomatic.com/features";
const outputPath = "screenshots/shotomatic-features.png";
(async () => {
await fs.mkdir("screenshots", { recursive: true });
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1440, height: 1000 },
deviceScaleFactor: 1,
});
const page = await context.newPage();
try {
await page.goto(url, { waitUntil: "load", timeout: 45_000 });
await page.screenshot({ path: outputPath });
console.log(`Saved ${outputPath}`);
} finally {
await context.close();
await browser.close();
}
})();
运行脚本:
$ node scripts/capture-one.js
这会截取可见视口。Playwright 的截图 API还支持整页截图、元素截图、缓冲区、裁剪和图片质量选项。
运行后会生成 screenshots/shotomatic-features.png:
截取整页截图
要截取整页截图,请传入 fullPage: true:
await page.screenshot({
path: "screenshots/shotomatic-features-full-page.png",
fullPage: true,
});
需要从上到下保存整个文档时,用整页截图;关心访客在特定屏幕尺寸下看到什么时,用视口截图。
整页截图仍然取决于页面状态。fullPage: true 只会改变截图区域,不会强制渲染懒加载图片、动画或进入视口才出现的区块。下文的懒加载部分会说明页面状态为什么重要。
把目标 URL 放进批量脚本
单页脚本正常工作后,把目标 URL 移到可以接收列表、并用可预测文件名保存结果的脚本中。示例仍然只用 /features,便于查看输出。之后可以向同一数组添加更多 URL。
创建 scripts/capture-batch.js:
const { chromium } = require("playwright");
const fs = require("node:fs/promises");
const urls = ["https://www.shotomatic.com/features"];
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.newContext({
viewport: { width: 1440, height: 1000 },
deviceScaleFactor: 1,
});
const page = await context.newPage();
const outputPath = `${outputDir}/${filenameForUrl(url, index)}`;
try {
await page.goto(url, { waitUntil: "load", timeout: 45_000 });
await page.locator("body").waitFor({ state: "visible", 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();
}
}
(async () => {
await fs.mkdir(outputDir, { recursive: true });
const browser = await chromium.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();
}
})();
这个版本有意按顺序运行。速度较慢,但更容易调试。确认它能在你的页面上稳定工作后,再加入并发。
为较大批次加入并发
处理 5 个 URL 时,简单循环已经够用。处理 50 或 500 个 URL 时,通常需要一个较小的并发上限。
不要一次打开几百个页面。浏览器自动化很占内存,请求过于密集时,许多网站也会限流或出错。先从 2–4 个并发截图开始,确认结果稳定后再增加。
加入本指南 Puppeteer 版本所用的同一个辅助函数:
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),
);
这样可以复用一个浏览器,为每次截图创建相互隔离的浏览器上下文,并限制同时处于活动状态的页面数量。Playwright 库文档在 Node 脚本流程中也明确使用浏览器上下文,让每次截图拥有独立的生命周期。
截取手机或平板电脑页面
Playwright 提供常见手机和平板电脑的设备模拟描述符。例如:
const { chromium, devices } = require("playwright");
(async () => {
const browser = await chromium.launch();
const iPhone = devices["iPhone 13"];
const context = await browser.newContext({
...iPhone,
});
const page = await context.newPage();
await page.goto("https://www.shotomatic.com/features", {
waitUntil: "load",
});
await page.screenshot({
path: "screenshots/shotomatic-features-iphone-13.png",
});
await context.close();
await browser.close();
})();
下面是用 iPhone 13 描述符截取的同一功能页:
如果用户代理、触控支持、视口和设备像素比都很重要,请用设备描述符。只需要特定尺寸时,使用普通视口即可:
const context = await browser.newContext({
viewport: { width: 390, height: 844 },
deviceScaleFactor: 2,
isMobile: true,
});
处理懒加载内容
许多页面只有在图片或区块进入视口后才会加载或显示。整页截图过早时,首屏以下还没有出现的内容会在截图中留下空白。
截取整页前,先滚动浏览文档,触发懒加载图片和进入视口才显示的区块:
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(600);
}
window.scrollTo(0, 0);
await delay(300);
});
}
在 page.screenshot() 前调用:
await page.goto(url, { waitUntil: "load", timeout: 45_000 });
await triggerLazyLoading(page);
await page
.waitForFunction(
() =>
Array.from(document.images).every(
(image) => image.complete && image.naturalWidth > 0,
),
null,
{ timeout: 15_000 },
)
.catch(() => {});
await page.screenshot({ path: outputPath, fullPage: true });
有些页面还需要额外处理。自定义滚动容器、动画、延迟的 API 调用和虚拟化内容,都可能需要页面专用的选择器或状态判断。
选择合适的等待策略
大多数截图可靠性问题都与时机有关。
Playwright 的导航文档说明,page.goto() 默认等待 load 事件,但现代网页可能在该事件之后继续获取数据或渲染。截图时通常要采用以下策略之一:
- 普通公开页面可以先从
waitUntil: "load"开始; - 等待一个特定定位器,以证明所需内容已经渲染;
- 就绪条件取决于应用状态时,使用
page.waitForFunction(); - 图片采用懒加载时,截图前先滚动;
- 不要把固定延时作为主要等待规则。
例如:
await page.goto(url, { waitUntil: "load", timeout: 45_000 });
await page
.getByRole("heading", { name: "Shotomatic Features" })
.waitFor({ state: "visible", timeout: 15_000 });
await page.screenshot({ path: outputPath, fullPage: true });
不要假设一种等待规则适用于所有页面。如果目标网站由你维护,等待定位器或应用状态通常比等待通用网络条件更可靠。
加入重试
网络请求会失败,页面会超时,第三方小组件也会卡住。真正用于批量任务的脚本,在放弃整次任务前,应该先重试失败的 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: "load", timeout: 45_000 });
await page.screenshot({ path: outputPath, fullPage: true });
});
在生产环境中,还应写入一份 JSON 报告,记录每个 URL、输出路径、状态、错误消息和时间戳。以后有人询问哪些页面失败、为什么失败时,这份报告能省下不少时间。
实用的文件夹结构
准备重复运行的截图任务,应把输入、输出和脚本分开保存:
website-screenshots-playwright/
scripts/
capture-batch.js
urls/
launch-pages.txt
competitor-pages.txt
screenshots/
2026-06-23-launch/
2026-06-23-competitors/
reports/
2026-06-23-launch.json
这样更容易重新运行工作流,也能让截图集与生成它的 URL 列表保持关联。
常见错误
- **截图过早:**浏览器可能在所需内容可见之前就触发
load。请等待选择器,或加入页面专用检查。 - **一次打开太多页面:**高并发可能导致浏览器崩溃、耗尽内存或触发限流。请从较小的数值开始。
- **所有网站共用一种文件名模式:**URL 可能带有查询字符串、末尾斜杠、重复项和特殊字符。请始终清理文件名并加入索引。
- **忘记清理浏览器资源:**关闭页面、上下文和浏览器。上下文泄漏会在长时间任务中成为实际问题。
- **把整页截图当成视觉测试:**截图文件只是证据。视觉回归测试还需要基线、比较阈值和评审流程。
Playwright 截图脚本的替代方式
Playwright 不是自动截取网站截图的唯一方式。选择取决于第一版脚本完成后由谁负责这项工作。
如果只需要一段用途明确的浏览器自动化脚本,Puppeteer可能已经够用。截图属于更大的测试套件、需要覆盖 Chromium、Firefox 和 WebKit,或者已经在使用 Playwright Test 的运行器、跟踪与报告时,Playwright 更合适。
如果要把截图功能集成进产品,与自行维护浏览器相比,截图 API可能更省事。
如果不想编写和维护这个脚本,请阅读如何无代码自动截取网页。如果要从 URL 列表批量截图、为每个页面设置截图选项、检查结果并导出文件,又不想维护 Playwright 代码,可以使用 Shotomatic 的 Website Capture。
常见问题
Playwright 可以自动截取网站截图吗?
可以。Playwright 能启动浏览器、打开 URL、设置视口或设备选项,并通过 page.screenshot() 保存页面截图。
Playwright 可以截取整页截图吗?
可以。向 page.screenshot() 传入 fullPage: true,即可截取整个可滚动页面,而不只是可见视口。
可以用 Playwright 截取多个 URL 吗?
可以。把 URL 放进数组,通过循环逐个处理,并为每次截图创建新的浏览器上下文。批次较大时,还要增加并发限制和错误处理。
网站截图应该用 Playwright 还是 Puppeteer?
需要跨浏览器覆盖、设备模拟,或要让截图成为较大测试工作流的一部分时,用 Playwright。脚本主要针对 Chrome 时,Puppeteer 也很实用。
什么时候无代码截图工作流比 Playwright 更合适?
如果需要由非开发人员运行、检查或导出 URL 列表截图,而且不想维护脚本、浏览器依赖和 CI 任务,无代码工作流更合适。
参考资料
以上示例已根据当前官方文档核对:
- Playwright 库文档:浏览器、上下文、页面和清理流程;
- Playwright 截图指南和
Page.screenshot()API:截图选项; - Playwright 模拟文档:设备描述符;
- Playwright 导航文档和
page.waitForFunction()API:等待行为。
相关文章
查看更多文章无需编写代码,自动截取网页
Website Capture 可以截取 URL 列表,为每个页面设置选项,检查结果并导出,不需要维护脚本。



