Puppeteer 是一个由 Chrome 团队开发的 Node.js 库,提供高级 API 来控制无头(Headless) Chrome 或 Chromium 浏览器。它可以用于网页自动化、测试、爬虫等多种场景。
1. 核心特性
- 完全控制 Chrome/Chromium:可以执行几乎所有手动操作
- 无头模式支持:可以运行不显示界面的浏览器
- 网页截图和PDF生成:高质量的输出能力
- 网络请求拦截和修改:可以监控和修改网络请求
- 性能分析:获取页面加载性能数据
- 支持现代Web特性:完全支持JavaScript渲染的页面
2. 安装与基本使用
安装
1
2
3
| npm install puppeteer
# 或者使用轻量版(不自动下载Chromium)
npm install puppeteer-core
|
基本示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| const puppeteer = require('puppeteer');
(async () => {
// 启动浏览器
const browser = await puppeteer.launch();
// 打开新页面
const page = await browser.newPage();
// 导航到URL
await page.goto('https://example.com');
// 截图
await page.screenshot({ path: 'example.png' });
// 关闭浏览器
await browser.close();
})();
|
3. 主要API功能
浏览器操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 启动浏览器,可配置选项
const browser = await puppeteer.launch({
headless: false, // 显示浏览器界面
slowMo: 50, // 减慢操作速度,便于观察
args: ['--no-sandbox', '--disable-setuid-sandbox'] // 沙箱配置
});
// 创建新页面
const page = await browser.newPage();
// 设置视口大小
await page.setViewport({ width: 1280, height: 800 });
// 关闭浏览器
await browser.close();
|
页面导航
1
2
3
4
5
6
7
8
9
10
11
12
| // 跳转到URL
await page.goto('https://example.com', {
waitUntil: 'networkidle2', // 等待网络空闲
timeout: 30000 // 超时时间
});
// 后退、前进
await page.goBack();
await page.goForward();
// 刷新页面
await page.reload();
|
元素操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // 点击元素
await page.click('#submit-button');
// 输入文本
await page.type('#username', 'myusername');
// 获取元素文本
const text = await page.$eval('.title', el => el.textContent);
// 获取多个元素
const links = await page.$$eval('a', anchors =>
anchors.map(a => a.href)
);
// 上传文件
const input = await page.$('input[type="file"]');
await input.uploadFile('/path/to/file');
|
表单处理
1
2
3
4
5
6
7
| // 填写并提交表单
await page.type('#username', 'admin');
await page.type('#password', 'password');
await page.click('#submit');
// 选择下拉框选项
await page.select('#country', 'china');
|
等待机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // 等待元素出现
await page.waitForSelector('#results');
// 等待XPath元素
await page.waitForXPath('//div[contains(@class, "result")]');
// 等待函数返回true
await page.waitForFunction(
'document.querySelector("body").innerText.includes("Done")'
);
// 等待导航完成
await Promise.all([
page.waitForNavigation(),
page.click('a') // 点击会触发导航
]);
// 等待超时
await page.waitForTimeout(1000); // 等待1秒
|
截图和PDF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // 截取整个页面
await page.screenshot({
path: 'fullpage.png',
fullPage: true
});
// 截取特定区域
await page.screenshot({
path: 'element.png',
clip: { x: 10, y: 10, width: 100, height: 100 }
});
// 生成PDF
await page.pdf({
path: 'page.pdf',
format: 'A4'
});
|
4. 高级功能
网络请求拦截
1
2
3
4
5
6
7
8
9
| await page.setRequestInterception(true);
page.on('request', request => {
// 阻止图片请求
if (request.resourceType() === 'image') {
request.abort();
} else {
request.continue();
}
});
|
执行JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
| // 在页面上下文中执行代码
const dimensions = await page.evaluate(() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
};
});
// 传递参数到页面上下文
const result = await page.evaluate((x, y) => {
return window.computeSomething(x, y);
}, 10, 20);
|
处理弹窗
1
2
3
4
5
| // 监听对话框事件
page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.dismiss(); // 或 accept()
});
|
多页面和iframe
1
2
3
4
5
6
| // 获取所有页面
const pages = await browser.pages();
// 处理iframe
const frame = page.frames().find(f => f.name() === 'myframe');
await frame.click('#button-in-frame');
|
设备模拟
1
2
| const devices = require('puppeteer/DeviceDescriptors');
await page.emulate(devices['iPhone X']);
|
性能分析
1
2
3
4
5
6
7
8
| // 启用性能跟踪
await page.tracing.start({ path: 'trace.json' });
await page.goto('https://example.com');
await page.tracing.stop();
// 获取性能指标
const metrics = await page.metrics();
console.log(metrics);
|
5. 实战技巧
处理登录
1
2
3
4
5
6
7
8
9
10
11
12
| await page.goto('https://example.com/login');
await page.type('#username', 'user');
await page.type('#password', 'pass');
await page.click('#submit');
await page.waitForNavigation();
// 保存cookies
const cookies = await page.cookies();
fs.writeFileSync('cookies.json', JSON.stringify(cookies));
// 后续会话恢复cookies
const cookies = JSON.parse(fs.readFileSync('cookies.json'));
await page.setCookie(...cookies);
|
避免检测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// 覆盖navigator.webdriver属性
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => false
});
});
await page.goto('https://example.com');
})();
|
分布式爬虫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // 使用puppeteer-cluster进行并行处理
const { Cluster } = require('puppeteer-cluster');
(async () => {
const cluster = await Cluster.launch({
concurrency: Cluster.CONCURRENCY_CONTEXT,
maxConcurrency: 4, // 4个浏览器实例并行
puppeteerOptions: { headless: true }
});
await cluster.task(async ({ page, data: url }) => {
await page.goto(url);
const title = await page.title();
console.log(title);
});
// 添加任务
cluster.queue('http://example.com/page1');
cluster.queue('http://example.com/page2');
await cluster.idle();
await cluster.close();
})();
|
6. 调试技巧
调试模式
1
2
3
4
| const browser = await puppeteer.launch({
headless: false,
devtools: true // 自动打开开发者工具
});
|
慢动作模式
1
2
3
4
| const browser = await puppeteer.launch({
headless: false,
slowMo: 250 // 每个操作延迟250ms
});
|
控制台输出
1
2
3
4
5
6
7
| // 监听console事件
page.on('console', msg => {
console.log('浏览器控制台:', msg.text());
});
// 在页面上下文中输出
await page.evaluate(() => console.log('页面中的消息'));
|
7. 常见问题解决
处理超时
1
2
3
4
5
| try {
await page.waitForSelector('#element', { timeout: 5000 });
} catch (e) {
console.log('元素未在5秒内出现');
}
|
处理元素不可点击
1
2
3
4
| await page.evaluate(selector => {
document.querySelector(selector).scrollIntoView();
}, '#button');
await page.click('#button');
|
处理页面卡死
1
2
3
4
5
6
7
8
9
10
| // 设置全局超时
page.setDefaultTimeout(30000);
// 使用Promise.race实现超时
await Promise.race([
page.click('#button'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 5000)
)
]);
|
8. 性能优化
禁用不必要的内容
1
2
3
4
5
6
7
8
9
| await page.setRequestInterception(true);
page.on('request', request => {
// 阻止图片、样式表、字体等
if (['image', 'stylesheet', 'font'].includes(request.resourceType())) {
request.abort();
} else {
request.continue();
}
});
|
复用浏览器实例
1
2
3
4
5
6
7
8
9
10
| // 启动时
const browser = await puppeteer.launch();
// 每次任务
const page = await browser.newPage();
// ...执行任务...
await page.close(); // 不关闭browser
// 最后关闭
await browser.close();
|
内存管理
1
2
3
4
5
6
| // 定期清理页面
for (let i = 0; i < 10; i++) {
const page = await browser.newPage();
// ...使用页面...
await page.close();
}
|
9. 生态系统扩展
常用插件
puppeteer-extra
- 增强版Puppeteerpuppeteer-extra-plugin-stealth
- 反检测puppeteer-cluster
- 集群管理puppeteer-recorder
- 录制操作
替代方案
- Playwright (微软开发,支持多浏览器)
- Cypress (专注于测试)
- Selenium (更传统的WebDriver方案)
10. 最佳实践
- 错误处理:始终使用try-catch处理异步操作
- 资源清理:确保关闭页面和浏览器实例
- 可配置性:将选择器和URL等提取为配置
- 日志记录:记录关键操作和错误
- 速率限制:避免对目标服务器造成过大压力
Puppeteer是一个功能强大且灵活的工具,适用于从简单自动化到复杂爬虫的各种场景。通过合理使用其API和生态系统扩展,可以构建出高效可靠的浏览器自动化解决方案。