Puppeteer 无头浏览器自动化截图

FreeGuideOnline 18阅读 2026-07-09

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 图片格式:pngjpegwebp
quality number (0-100) 仅对 jpegwebp 有效,控制压缩质量
fullPage boolean 是否截取完整页面
clip object 指定裁剪区域 {x, y, width, height}
omitBackground boolean 使背景透明(仅 png 格式)
encoding string 返回截图数据的编码方式:binarybase64

示例:仅截取特定区域

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();
  }
})();