Chromium / ChromeDriver 完全指南 / 06 - Puppeteer 集成
06 - Puppeteer 集成
使用 Puppeteer 通过 CDP 协议直接控制 Chrome/Chromium,掌握高级操作与性能优化。
6.1 Puppeteer 简介
Puppeteer 是 Google Chrome 团队维护的 Node.js 库,通过 Chrome DevTools Protocol (CDP) 直接控制 Chrome 或 Chromium。
| 特征 | 说明 |
|---|---|
| 维护方 | Google Chrome DevTools 团队 |
| 协议 | CDP (Chrome DevTools Protocol) |
| 语言 | JavaScript / TypeScript |
| 浏览器 | Chrome / Chromium (Firefox 实验性支持) |
| 包大小 | ~3MB (不含浏览器) |
| 浏览器下载 | Puppeteer 自动下载匹配版本的 Chrome |
架构
┌──────────────────────────────────────────┐
│ Node.js 应用 │
│ ┌────────────────────────────────────┐ │
│ │ Puppeteer API │ │
│ └──────────────┬─────────────────────┘ │
│ │ WebSocket │
│ ┌──────────────▼─────────────────────┐ │
│ │ Chrome DevTools Protocol (CDP) │ │
│ └──────────────┬─────────────────────┘ │
│ │ │
│ ┌──────────────▼─────────────────────┐ │
│ │ Chrome / Chromium 进程 │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘
6.2 安装与启动
安装
# 创建项目
mkdir puppeteer-demo && cd puppeteer-demo
npm init -y
# 安装 Puppeteer (自动下载 Chrome)
npm install puppeteer
# 或安装 Puppeteer Core (不自动下载浏览器,需手动指定)
npm install puppeteer-core
第一个脚本
const puppeteer = require('puppeteer');
(async () => {
// 启动浏览器
const browser = await puppeteer.launch({
headless: 'new', // 新版无头模式
args: ['--no-sandbox', '--disable-dev-shm-usage']
});
// 打开新页面
const page = await browser.newPage();
// 导航
await page.goto('https://example.com', {
waitUntil: 'networkidle2' // 等待网络空闲
});
// 获取标题
const title = await page.title();
console.log(`标题: ${title}`);
// 截图
await page.screenshot({ path: '/tmp/example.png', fullPage: true });
// 关闭浏览器
await browser.close();
})();
6.3 Puppeteer 核心 API
6.3.1 浏览器启动选项
const browser = await puppeteer.launch({
// 基础选项
headless: 'new', // true/false/'new'
executablePath: '/usr/bin/google-chrome', // 自定义 Chrome 路径
slowMo: 100, // 每步延迟 (ms, 调试用)
devtools: true, // 自动打开 DevTools
// 启动参数 (等同于 Chrome 启动参数)
args: [
'--no-sandbox',
'--disable-dev-shm-usage',
'--window-size=1920,1080',
'--proxy-server=http://proxy:8080',
'--disable-blink-features=AutomationControlled',
],
// 默认视口
defaultViewport: {
width: 1920,
height: 1080,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
},
// 超时
timeout: 30000, // 启动超时 (ms)
protocolTimeout: 60000, // CDP 协议超时 (ms)
});
6.3.2 页面导航
const page = await browser.newPage();
// 基本导航
await page.goto('https://example.com');
// 带选项
await page.goto('https://example.com', {
waitUntil: 'networkidle2', // load / domcontentloaded / networkidle0 / networkidle2
timeout: 30000, // 导航超时
referer: 'https://google.com' // Referer 头
});
// waitUntil 选项说明:
// 'load' - load 事件触发
// 'domcontentloaded' - DOMContentLoaded 事件触发
// 'networkidle0' - 0 个网络连接 (所有请求完成)
// 'networkidle2' - ≤2 个网络连接 (推荐,SPA 友好)
// 刷新
await page.reload({ waitUntil: 'networkidle2' });
// 前进后退
await page.goBack();
await page.goForward();
6.3.3 元素定位与交互
// CSS 选择器
const button = await page.$('#submit-btn'); // 单个
const items = await page.$$('.list-item'); // 多个
// XPath
const [element] = await page.$$('//div[@id="app"]');
// 点击
await page.click('#submit-btn');
await page.click('.menu-item', { button: 'right' }); // 右键
await page.click('.item', { clickCount: 2 }); // 双击
await page.click('#btn', { delay: 100 }); // 延迟点击
// 输入文本
await page.type('#search', 'Hello World', { delay: 50 });
// 清空输入
await page.$eval('#search', el => el.value = '');
// 键盘操作
await page.keyboard.press('Enter');
await page.keyboard.press('Tab');
await page.keyboard.down('Shift');
await page.keyboard.press('KeyA'); // Shift+A
await page.keyboard.up('Shift');
await page.keyboard.type('Hello');
// 选择下拉框
await page.select('#country', 'CN');
// 上传文件
const input = await page.$('input[type="file"]');
await input.uploadFile('/path/to/file.pdf');
// 悬停
await page.hover('.dropdown-toggle');
// 等待元素
await page.waitForSelector('#content', { visible: true, timeout: 10000 });
await page.waitForSelector('.loading', { hidden: true }); // 等待消失
await page.waitForFunction('document.querySelectorAll(".item").length > 5');
await page.waitForNetworkIdle();
6.3.4 数据提取
// 获取文本内容
const text = await page.$eval('.title', el => el.textContent);
const texts = await page.$$eval('.item', els => els.map(el => el.textContent));
// 获取属性
const href = await page.$eval('a', el => el.href);
const src = await page.$eval('img', el => el.src);
// 获取 HTML
const html = await page.content(); // 整个页面
const innerHTML = await page.$eval('#app', el => el.innerHTML); // 元素内部
// 执行 JavaScript
const result = await page.evaluate(() => {
return {
title: document.title,
url: window.location.href,
cookies: document.cookie,
links: [...document.querySelectorAll('a')].map(a => a.href),
};
});
// 传递参数给 evaluate
const data = await page.evaluate((selector) => {
return [...document.querySelectorAll(selector)].map(el => ({
text: el.textContent.trim(),
href: el.href,
}));
}, 'a.nav-link');
console.log(JSON.stringify(data, null, 2));
6.4 高级操作
6.4.1 请求拦截
await page.setRequestInterception(true);
page.on('request', (request) => {
const url = request.url();
// 阻止图片加载
if (request.resourceType() === 'image') {
request.abort();
return;
}
// 阻止广告
if (url.includes('doubleclick.net') || url.includes('google-analytics.com')) {
request.abort();
return;
}
// 修改请求头
request.continue({
headers: {
...request.headers(),
'X-Custom-Header': 'value',
}
});
});
// 监听响应
page.on('response', async (response) => {
const url = response.url();
if (url.includes('/api/data')) {
const data = await response.json();
console.log('API 响应:', data);
}
});
6.4.2 网络模拟
// 模拟离线
await page.setOfflineMode(true);
// 模拟慢速网络
await page.emulateNetworkConditions({
offline: false,
downloadThroughput: 1.5 * 1024 * 1024 / 8, // 1.5 Mbps
uploadThroughput: 750 * 1024 / 8, // 750 Kbps
latency: 100 // 100ms 延迟
});
6.4.3 设备模拟
const puppeteer = require('puppeteer');
const devices = puppeteer.devices;
// 使用预定义设备
await page.emulate(devices['iPhone 14 Pro']);
await page.emulate(devices['iPad Pro 11']);
// 自定义设备
await page.emulate({
viewport: { width: 375, height: 812, isMobile: true, hasTouch: true },
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X)...'
});
6.4.4 PDF 生成
await page.goto('https://example.com/report', { waitUntil: 'networkidle2' });
await page.pdf({
path: '/tmp/report.pdf',
format: 'A4',
landscape: false,
printBackground: true,
margin: { top: '20mm', right: '15mm', bottom: '20mm', left: '15mm' },
displayHeaderFooter: true,
headerTemplate: '<div style="font-size:8px;width:100%;text-align:center">报告</div>',
footerTemplate: '<div style="font-size:8px;width:100%;text-align:center">' +
'<span class="pageNumber"></span>/<span class="totalPages"></span></div>',
});
6.4.5 Trace 与性能分析
// 开始记录 Trace
await page.tracing.start({
path: '/tmp/trace.json',
screenshots: true,
categories: ['devtools.timeline', 'v8.execute', 'blink.console']
});
// 执行操作
await page.goto('https://example.com');
await page.click('#button');
// 停止并保存
const traceBuffer = await page.tracing.stop();
console.log('Trace 保存到: /tmp/trace.json');
// 可在 chrome://tracing 中加载分析
// 性能指标
const metrics = await page.metrics();
console.log('JS 堆大小:', metrics.JSHeapUsedSize);
console.log('DOM 节点数:', metrics.Nodes);
console.log('布局次数:', metrics.LayoutCount);
6.5 CDP 高级操作
Puppeteer 可以直接发送 CDP 命令,实现更底层的控制。
// 获取 CDP Session
const client = await page.target().createCDPSession();
// 获取页面性能指标
const perf = await client.send('Performance.getMetrics');
console.log(perf.metrics);
// 拦截并修改响应体
await client.send('Fetch.enable', {
patterns: [{ urlPattern: '*/api/*', requestStage: 'Response' }]
});
client.on('Fetch.requestPaused', async (event) => {
const { requestId } = event;
const response = await client.send('Fetch.getResponseBody', { requestId });
const body = JSON.parse(response.body);
body.modified = true;
await client.send('Fetch.fulfillRequest', {
requestId,
responseCode: 200,
body: Buffer.from(JSON.stringify(body)).toString('base64'),
});
});
// 注入 CSS
await client.send('Page.addScriptToEvaluateOnNewDocument', {
source: `
const style = document.createElement('style');
style.textContent = '* { border: 1px solid red !important; }';
document.documentElement.prepend(style);
`
});
6.6 连接已有的 Chrome 实例
// 方法 1: 连接到远程调试端口
// 先启动 Chrome: google-chrome --remote-debugging-port=9222
const browser = await puppeteer.connect({
browserURL: 'http://127.0.0.1:9222',
defaultViewport: null // 使用浏览器实际视口
});
// 方法 2: 连接到 WebSocket
const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://127.0.0.1:9222/devtools/browser/xxxx'
});
// 方法 3: 复用浏览器上下文
const browser = await puppeteer.launch();
const context = browser.defaultBrowserContext();
await context.overridePermissions('https://example.com', ['notifications']);
// 使用后断开连接 (不关闭浏览器)
browser.disconnect();
6.7 Puppeteer vs Selenium
| 对比维度 | Puppeteer | Selenium |
|---|---|---|
| 协议 | CDP (WebSocket) | WebDriver (HTTP) |
| 语言 | JS / TS only | Java, Python, C#, JS, Ruby |
| 浏览器支持 | Chrome / Chromium only | Chrome, Firefox, Safari, Edge |
| 安装 | npm install puppeteer (自动下载 Chrome) | 需手动安装驱动 |
| 学习曲线 | 低 | 中等 |
| API 风格 | async/await, Promise | 同步 / 异步 |
| 请求拦截 | ✅ 原生支持 | ❌ 需第三方库 |
| 设备模拟 | ✅ 内置设备列表 | ⚠️ 需手动配置 |
| PDF 生成 | ✅ 原生支持 | ⚠️ 需 CDP 扩展 |
| Trace/性能 | ✅ 原生支持 | ❌ |
| 自动等待 | ❌ 需手动等待 | ❌ 需手动等待 |
| 跨浏览器 | ❌ | ✅ |
| 生态成熟度 | 中等 | 最成熟 |
| 企业级支持 | 社区 | Selenium Grid |
选择建议
选择 Puppeteer:
✅ 只需控制 Chrome/Chromium
✅ Node.js / TypeScript 技术栈
✅ 需要请求拦截、Trace、PDF 等 CDP 高级能力
✅ 爬虫、截图、性能分析
选择 Selenium:
✅ 需要跨浏览器测试 (Chrome + Firefox + Safari)
✅ 企业级 E2E 测试
✅ 需要 Selenium Grid 分布式测试
✅ 非 JS 语言 (Java, Python, C#)
6.8 完整示例 — 动态页面爬虫
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-dev-shm-usage']
});
const page = await browser.newPage();
// 拦截图片和字体,加速加载
await page.setRequestInterception(true);
page.on('request', (req) => {
if (['image', 'font', 'stylesheet'].includes(req.resourceType())) {
req.abort();
} else {
req.continue();
}
});
// 设置 UA
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' +
'(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
);
// 导航
await page.goto('https://news.ycombinator.com', {
waitUntil: 'networkidle2'
});
// 提取数据
const articles = await page.$$eval('.titleline > a', (links) => {
return links.slice(0, 10).map(a => ({
title: a.textContent.trim(),
url: a.href,
}));
});
console.log('前 10 条新闻:');
articles.forEach((a, i) => console.log(`${i + 1}. ${a.title} - ${a.url}`));
await browser.close();
})();
6.9 要点回顾
| 要点 | 说明 |
|---|---|
| CDP 协议 | Puppeteer 通过 WebSocket 使用 CDP 直接控制 Chrome |
| async/await | 所有 API 都是异步的,使用 async/await 或 Promise |
| 自动下载浏览器 | npm install puppeteer 自动下载匹配的 Chrome |
| 请求拦截 | setRequestInterception(true) 实现请求拦截与修改 |
| 等待策略 | waitForSelector / waitForFunction / waitForNetworkIdle |
| puppeteer-core | 不自动下载浏览器,适合连接已有 Chrome 实例 |
6.10 注意事项
⚠️ Puppeteer 不等于 Playwright: 虽然都用 CDP,但 Playwright 支持多浏览器且有自动等待,Puppeteer 仅支持 Chrome。
⚠️ Node.js 版本要求: Puppeteer 21+ 需要 Node.js 18+。
⚠️ 浏览器版本锁定: 生产环境应锁定
puppeteer版本以确保 Chrome 版本一致。⚠️ 内存泄漏: 长时间运行时注意关闭不需要的
page对象,避免内存泄漏。
6.11 扩展阅读
| 资源 | 链接 |
|---|---|
| Puppeteer 官方文档 | https://pptr.dev/ |
| Puppeteer GitHub | https://github.com/puppeteer/puppeteer |
| Chrome DevTools Protocol | https://chromedevtools.github.io/devtools-protocol/ |
| CDP 参考 (JSON 格式) | https://vanilla.aslushnikov.com/ |
| Puppeteer 设备列表 | https://github.com/puppeteer/puppeteer/blob/main/packages/puppeteer-core/src/common/Device.ts |