Puppeteer 是一个 Node.js 库,它提供了强大的 API,用于通过 DevTools 协议控制无头 Chrome 和 Chromium。其最有用的功能之一是能够以编程方式捕获网页和元素的屏幕截图。
对于网络抓取工具来说,能够使用 Puppeteer 进行屏幕截图解锁了各种有价值的用例:
- 直观地调试抓取问题和测试失败。
- 捕获动态页面和 SPA 的状态。
- 监控视觉回归和 UI 变化。
- 创建带有上下文屏幕截图的教程和文档。
- 从网页生成图像资源。
在这份综合指南中,我们将探讨如何利用 Puppeteer 屏幕截图来改进您的网页抓取工作流程。
网页抓取中 Puppeteer 的兴起
Puppeteer 于 2017 年首次发布,并被网络抓取社区迅速采用。以下是一些突出其受欢迎程度的统计数据:
- Github 上有超过 52,000 颗星,使其成为顶级 JS 项目之一。
- NPM 每周下载量超过 3 万次。
- 490 年,Puppeteer 的 Google 搜索量同比增长 2022%。
那么,是什么让 Puppeteer 在网络抓取方面脱颖而出呢?
无头浏览器控制
Puppeteer 通过 Chrome DevTools 协议提供对无头浏览器的完全控制。这允许复制用户交互以实现自动化和抓取动态内容。
轻巧快速
仅无头意味着 Puppeteer 会跳过所有使 Chromium 变得重量级的 UI 渲染。这可以实现大规模抓取的快速性能。
积极发展
在 Google Chrome 团队的支持下,Puppeteer 经常获得更新以及针对自动化和抓取用例量身定制的新功能。
比硒简单
Puppeteer 只专注于控制 Chromium,而 Selenium 支持多种浏览器。 API 更加简洁和惯用,使其易于使用。
由于这些原因,许多网络抓取工具正在从 Selenium/WebDriver 切换到 Puppeteer,以提高速度、可靠性和功能。
现在让我们深入探讨如何利用 Puppeteer 强大的屏幕截图功能。
捕获整页屏幕截图
截取整个页面的最简单方法是使用 page.screenshot()
方法:
// Launch browser
const browser = await puppeteer.launch();
// Open page
const page = await browser.newPage();
await page.goto(‘https://example.com‘);
// Screenshot
await page.screenshot({
path: ‘fullpage.png‘
});
这将捕获当前可见的视口。要截取整个页面的高度,请设置 fullPage
选项 true
:
await page.screenshot({
path: ‘longpage.png‘,
fullPage: true
});
指定图像选项
screenshot()
方法接受选项来控制类型、质量等:
type
– png、jpeg 或 webp。默认为 png。quality
– 对于 jpeg/webp,质量范围为 0-100。默认值为 80。omitBackground
– 隐藏默认的白色背景并允许透明度。encoding
– 可以输出为 base64,而不是保存文件。
例如,要保存高质量的 jpeg:
await page.screenshot({
path: ‘page.jpeg‘,
type: ‘jpeg‘,
quality: 100
});
Tips::使用 webp 可以获得更好的压缩效果并具有同等质量。但是 webp 可能存在兼容性问题。
处理大屏幕截图
整页屏幕截图的大小很容易超过数兆字节。默认情况下,Puppeteer 在保存之前会在内存中缓冲屏幕截图,这可能会超出进程限制。
要处理大屏幕截图,请传递选项 encoding: ‘base64‘
获取 Base64 字符串而不是 Buffer。然后使用 fs.writeFile() 保存以避免在内存中缓冲图像。
这是一个例子:
const buffer = await page.screenshot({ encoding: ‘base64‘ });
fs.writeFile(‘screenshot.png‘, buffer, ‘base64‘, err => {
// handle error
});
滚动高页面以捕获整页
要捕获比视口长的页面的完整高度,我们需要先滚动页面。
这是一种使用的方法 page.evaluate()
:
// Scroll to bottom
await page.evaluate(() => {
window.scrollTo(0, document.body.scrollHeight);
});
// Screenshot full scrollable area
await page.screenshot({ path: ‘longpage.png‘, fullPage: true });
我们还可以逐步滚动屏幕截图,然后将它们拼接成一个高屏幕截图。这可以避免在内存中缓冲整个图像。
替代方案:另存为 PDF
捕获整页内容的另一个选项 - 生成 PDF!
// Generates PDF and saves to disk
await page.pdf({
path: ‘page.pdf‘,
printBackground: true
});
PDF 的优点:
- 开箱即用地处理多页内容。
- 矢量格式通常会产生较小的文件大小。
- 打印格式保持不变。
缺点:
- 编程处理不太灵活。
- 与图像相比,样式选项有限。
- 可能无法捕获动态呈现的内容。
设置视口大小
默认情况下,Puppeteer 使用 800px x 600px 的视口。为了在不同的桌面和移动尺寸上获得准确的全页屏幕截图,我们可以显式设置视口:
// 1200px wide desktop
await page.setViewport({
width: 1200,
height: 800
});
// 400px wide mobile
await page.setViewport({
width: 400,
height: 1200
});
然后屏幕截图将匹配指定的视口大小。
捕获元素
除了整页屏幕截图之外,我们还可以使用以下方法捕获特定元素的屏幕截图 element.screenshot()
.
// Get reference to element
const menu = await page.$(‘.main-menu‘);
// Screenshot just that element
await menu.screenshot({path: ‘menu.png‘});
在捕获屏幕截图之前,该元素将滚动到视图中。这允许捕获可能在屏幕外的元素的镜头,而无需滚动到它们。
元素屏幕截图的一些用例:
- 捕获动态组件(如股票行情或动画)的屏幕截图。
- 通过拍摄各个元素来调试布局问题。
- 获取图标和插图的图像资源。
屏外元素截图
一个常见的问题是在交互过程中尝试捕获屏幕截图时元素被遮挡或移动。
我们可以利用自动元素滚动 element.screenshot()
即使在屏幕外,也能可靠地捕获任何状态下的元素:
// Click button which hides the element
await page.click(‘.toggle-menu‘);
// Menu is now hidden but we can still screenshot it
await menu.screenshot({path: ‘hidden-menu.png‘});
这样可以轻松进行屏幕截图,而无需重置页面状态。
等待动态内容加载
使用动态页面时,我们需要等待内容渲染,然后再截取屏幕截图以捕获所需的状态。
这是等待元素出现的示例:
// Click button to trigger ajax call
await page.click(‘.load-content‘);
// Wait for new content to load
await page.waitForSelector(‘.loaded‘);
// Screenshot after loaded
await page.screenshot({path: ‘loaded.png‘});
page.waitForSelector()
等待选择器存在于 DOM 中后再继续。
其他一些有用的等待包括:
page.waitFor()
– 等待给定条件为真。page.waitForFunction()
– 等待异步 DOM 更新完成。page.waitUntil()
– 等待导航发生。
关键是为要在屏幕截图中捕获的页面更新选择正确的等待条件。
等待特定 DOM 更改
为了与更离散的 DOM 更改同步,我们可以等待属性更新而不是毯子选择器:
// Wait for text content to change
await page.waitForFunction(() => {
return document.querySelector(‘.status‘).textContent === ‘Loaded‘;
});
// Element updated
await page.screenshot({/*...*/});
这种方法非常适合等待关键数据加载,而不是静态 DOM 更改。
处理单页应用程序 (SPA)
对于无需重新加载即可更新状态的复杂 JavaScript SPA,等待 DOM 更改可能会很棘手。
处理这些问题的一些技巧:
- 交互后等待网络空闲以允许 XHR 完成。
- 等待特定组件(例如覆盖层)而不是毯子选择器消失。
- 在截取屏幕截图之前,滚动到所需部分以强制渲染。
- 使用增量等待而不是固定超时。
没有一种方法可以完美适用于所有 SPA。您必须尝试使用相关应用程序。
在截取整页屏幕截图之前滚动页面
对于需要滚动的页面,我们需要以编程方式滚动,然后才能使用以下命令进行完整屏幕截图 fullPage: true
.
这是一个可靠的方法:
await page.evaluate(() => {
// Scroll to bottom
window.scrollTo(0, document.body.scrollHeight);
});
// Capture full scrolled screenshot
await page.screenshot({fullPage: true});
这会将页面向下滚动到最大滚动位置,然后再进行屏幕截图。
另一种方法是使用 window.scrollBy()
一次增量滚动一定量。这允许在向下滚动整个页面长度的同时进行连续的屏幕截图。
处理长可滚动页面
对于极长的页面,一次性滚动整个长度仍可能超出内存或时间限制。
一个好的解决方案是将其分成几个部分,一次滚动一点,截取屏幕截图,然后将它们缝合在一起:
const screenshots = [];
while (hasMoreContent()) {
await page.evaluate(scrollDown);
screenshots.push(await page.screenshot());
}
// Stitch screenshots together into one tall image
这可以防止必须在内存中缓冲整个页面高度。
也可以水平滚动
对于水平滚动的页面,我们可以调整滚动顺序,使其也水平滚动:
await page.evaluate(() => {
window.scrollTo(
document.body.scrollWidth,
document.body.scrollHeight
);
});
await page.screenshot({fullPage: true});
这捕获了整个页面的宽度和高度!
可靠屏幕截图的最佳实践
以下是使用 Puppeteer 获取一致、可靠的屏幕截图的一些关键技巧:
等待网络空闲 - 使用 page.waitForNetworkIdle()
在交互之后确保所有异步请求在捕获状态之前完成。
使用适当的等待 – 选择与所需页面状态同步的条件等待,而不是总超时。
设置视口大小 – 显式设置视口以捕获准确的设备屏幕截图。
屏蔽动画/弹出窗口 – 悬停元素可以触发变化 – 使用 page.evaluate()
以避免副作用。
留出渲染时间 – 滚动页面后等待几百毫秒以完成渲染,然后再进行屏幕截图。
稳定片状测试 – 设置重试循环,并在屏幕截图步骤周围等待以处理碎片。
与已知良好进行比较 – 利用视觉回归测试工具来捕捉意外的变化。
结论
我希望本指南提供了使用 Puppeteer 截取完整页面和元素屏幕截图的全面概述,以满足您的网页抓取需求。
我们讨论的一些关键主题:
- 使用 page.screenshot() 和 element.screenshot() 捕获屏幕截图
- 用于控制图像类型、质量、格式的选项
- 滚动页面并等待动态内容
- 设置响应式页面的视口大小
- 可靠的屏幕截图工作流程的最佳实践
自动屏幕截图对于调试抓取工具、视觉测试和捕获动态状态非常有用。使用 Puppeteer 将它们添加到您的网页抓取工具包中!