Puppeteer 无头浏览器自动化截图
bash mkdir puppeteer-screenshot && cd puppeteer-screenshot npm init -y
### 安装 Puppeteer
```bash
npm install puppeteer
下载过程中会自动安装匹配的 Chromium 浏览器。若需使用系统自带 Chrome,可安装 puppeteer-core,本教程使用完整版。
第一张截图
创建 screenshot.js 文件:
const puppeteer = require('puppeteer');
(async () => {
// 启动浏览器
const browser = await puppeteer.launch();
// 新建页面
const page = await browser.newPage();
// 跳转到目标网址,等待页面加载完成
await page.goto('https://example.com');
// 截取当前视图区域的截图
await page.screenshot({ path: 'example.png' });
// 关闭浏览器
await browser.close();
console.log('截图已保存为 example.png');
})();
运行:
node screenshot.js
代码说明
puppeteer.launch()启动浏览器,默认headless: true。page.goto()导航到指定 URL,默认等待load事件触发。page.screenshot()保存截图,path指定输出文件。
全页截图
通常情况下 page.screenshot() 只截取当前可视区域。要截取整个页面(包含滚动条部分),设置 fullPage: true:
await page.screenshot({
path: 'fullpage.png',
fullPage: true
});
这会自动调整视口宽度,并从头滚动到页面底部进行截图。
如果页面有无限滚动或懒加载内容,需要在截图前执行滚动或等待特定选择器。
设置视口尺寸
await page.setViewport({ width: 1280, height: 800 });
await page.goto('https://example.com');
await page.screenshot({ path: 'viewport.png', fullPage: true });
元素截图
截取页面中某个特定元素,使用 element.screenshot():
const element = await page.$('.hero-banner');
if (element) {
await element.screenshot({ path: 'element.png' });
}
page.$()类似于document.querySelector(),获取第一个匹配的元素。- 若元素不存在,返回
null,需做判断。 - 可以截取该元素的当前渲染状态,即使其部分超出视口也能正确截取。
如果使用 page.$$ 获取多个元素:
const cards = await page.$$('.card');
for (let i = 0; i < cards.length; i++) {
await cards[i].screenshot({ path: `card-${i + 1}.png` });
}
截图配置详解
page.screenshot() 支持丰富的选项:
| 选项 | 类型 | 说明 |
|---|---|---|
path |
string | 文件保存路径,支持 png、jpg、webp 等 |
type |
string | 图片格式:png、jpeg、webp |
quality |
number (0-100) | 仅对 jpeg 和 webp 有效,控制压缩质量 |
fullPage |
boolean | 是否截取完整页面 |
clip |
object | 指定裁剪区域 {x, y, width, height} |
omitBackground |
boolean | 使背景透明(仅 png 格式) |
encoding |
string | 返回截图数据的编码方式:binary 或 base64 |
示例:仅截取特定区域
await page.screenshot({
path: 'clipped.png',
clip: { x: 100, y: 100, width: 400, height: 300 }
});
示例:获取 base64 编码
const base64 = await page.screenshot({ encoding: 'base64' });
console.log(`data:image/png;base64,${base64}`);
如果不指定 path,截图将返回 Buffer(编码为 binary)或字符串(base64)。
进阶技巧
等待页面完全渲染
某些页面内容依靠异步请求填充,直接截图可能得到空白区域。使用以下方法之一:
// 等待特定选择器出现
await page.waitForSelector('.content-loaded');
// 等待自定义条件
await page.waitForFunction(() => document.querySelectorAll('.item').length > 10);
// 等待固定时间(不推荐)
await page.waitForTimeout(2000);
模拟移动端截图
// 设置用户代理和视口
await page.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)');
await page.setViewport({ width: 375, height: 812, isMobile: true, hasTouch: true });
await page.goto('https://example.com');
await page.screenshot({ path: 'mobile.png', fullPage: true });
绕过反爬检测
某些站点会拒绝无头浏览器的请求。可以采取以下方式伪装:
const browser = await puppeteer.launch({
headless: 'new', // 新版无头模式,更难被检测
args: [
'--disable-blink-features=AutomationControlled',
'--no-sandbox',
'--disable-setuid-sandbox'
]
});
const page = await browser.newPage();
// 隐藏 webdriver 痕迹
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', { get: () => false });
});
await page.goto('https://possibly-blocked-site.com');
截图性能优化
- 若只需要截图不需要交互,可在页面加载后尽快禁用图片、CSS 等资源,但可能导致截图丢失样式。通常不建议。
- 对于大量截图任务,建议复用同一个
browser实例,只创建多个page或使用page.close()释放。
const browser = await puppeteer.launch();
const urls = ['https://site1.com', 'https://site2.com'];
for (const url of urls) {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });
await page.screenshot({ path: `${url.replace('https://', '')}.png`, fullPage: true });
await page.close();
}
await browser.close();
waitUntil: 'networkidle2' 适用于依赖较多网络请求的页面,会等待网络连接数不超过 2 后继续。
常见问题与处理
1. 截图一片空白
- 检查页面是否成功加载:
page.goto()后可以获取状态码。 - 确保页面内容已渲染完成,使用
waitForSelector。 - 部分页面依赖 webgl,尝试启用 GPU 加速(
--use-gl=swiftshader)。
2. 中文乱码或字体缺失
Puppeteer 自带 Chromium 缺少中文字体。在服务器环境(尤其是 Linux)会出现方块乱码。解决方式:
# Ubuntu/Debian
apt-get install -y fonts-noto-cjk
无需额外代码,重启浏览器即可生效。
3. 截图尺寸异常大
设置 fullPage: true 时若页面高度过高(如无限滚动),可使用 page.evaluate() 在截取前移除固定定位、隐藏部分元素,或使用 clip 截取前 N 像素。
4. 如何实现带水印的截图?
可以使用 Puppeteer 无法直接添加水印,但可以通过生成图片之后用 sharp 等库合成,或在页面注入水印的 div 后再截图:
await page.evaluate(() => {
const watermark = document.createElement('div');
watermark.innerText = 'SAMPLE';
watermark.style.position = 'fixed';
watermark.style.bottom = '20px';
watermark.style.right = '20px';
watermark.style.opacity = '0.3';
watermark.style.fontSize = '24px';
watermark.style.color = 'red';
watermark.style.zIndex = '99999';
document.body.appendChild(watermark);
});
await page.screenshot({ path: 'watermarked.png', fullPage: true });
完整示例脚本
以下脚本整合了全页截图、元素截图与移动端截图,并处理了常见错误:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
// 全页截图
await page.screenshot({ path: 'screenshots/fullpage.png', fullPage: true });
// 移动端截图
await page.setViewport({ width: 375, height: 812, isMobile: true });
await page.screenshot({ path: 'screenshots/mobile.png', fullPage: true });
// 元素截图(假设页面存在 .hero)
await page.setViewport({ width: 1280, height: 800 });
const hero = await page.$('.hero');
if (hero) {
await hero.screenshot({ path: 'screenshots/hero.png' });
}
} catch (error) {
console.error('截图过程出错:', error);
} finally {
await browser.close();
}
})();