
























今天在 GitHub Trending 上看到一个有意思的项目:Puppeteer,这是 Google 官方维护的浏览器自动化库,能够用几行代码控制 Chrome 或 Firefox 完成复杂任务。
Puppeteer 是一个 JavaScript 库,提供高层 API 通过 DevTools Protocol 或 WebDriver BiDi 控制 Chrome 或 Firefox 浏览器。默认以无头模式(headless)运行,非常适合服务端自动化场景。
核心特性:
典型应用场景:
Puppeteer 采用分层架构设计,核心由以下几个模块组成:
┌─────────────────────────────────────┐
│ User Code (Node.js) │
├─────────────────────────────────────┤
│ Puppeteer API Layer │
│ (puppeteer/puppeteer-core) │
├─────────────────────────────────────┤
│ DevTools Protocol / WebDriver │
│ BiDi Adapter │
├─────────────────────────────────────┤
│ Chrome for Testing / Firefox │
└─────────────────────────────────────┘
从 package.json 和 eslint.config.mjs 可以看到项目的技术选型:
| 技术 | 用途 |
|---|---|
| TypeScript | 主要开发语言,提供类型安全 |
| Mocha | 测试框架,支持异步测试和重试 |
| Wireit | 构建任务编排工具 |
| Hereby | 类似 make 的任务运行器 |
| Rollup | 打包工具,用于浏览器版本 |
| esbuild | 快速 TypeScript 编译 |
Puppeteer 的核心在于与浏览器的通信机制。以 DevTools Protocol 为例:
// 简化的通信流程
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
// 通过 CDP 发送命令
await page.goto('https://example.com');
// 底层:Page.navigate command
协议适配层支持两种模式:
// 从源码 example 中提取的真实数据流
const browser = await puppeteer.launch(); // 1. 启动浏览器进程
const page = await browser.newPage(); // 2. 创建新页面(CDP Target)
await page.goto(url); // 3. 导航(发送 Page.navigate)
await page.setViewport({width, height}); // 4. 设置视口(Emulation.setDeviceMetricsOverride)
await page.keyboard.press('/'); // 5. 键盘输入(Input.dispatchKeyEvent)
const element = await page.locator('selector'); // 6. 元素定位(CSS/XPath/ARIA)
await element.click(); // 7. 点击(Input.dispatchMouseEvent)
const text = await element.evaluate(el => el.textContent); // 8. 执行 JS(Runtime.evaluate)
await browser.close(); // 9. 清理资源
# 完整安装(自动下载 Chrome)
npm i puppeteer
# 仅安装库(需手动管理浏览器)
npm i puppeteer-core
⚠️ 注意:现代包管理器(npm、pnpm、yarn、bun、deno)默认阻止依赖的安装脚本。如果安装脚本被阻止,Puppeteer 不会自动下载浏览器,会导致运行时错误。
手动安装浏览器的方法:
npx puppeteer browsers install
或者在 package.json 中允许 puppeteer 的运行脚本:
{
"allowScripts": ["puppeteer"]
}
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://developer.chrome.com/');
await page.setViewport({width: 1080, height: 1024});
// 使用键盘快捷键打开搜索
await page.keyboard.press('/');
await page.locator('::-p-aria(Search)').fill('automate beyond recorder');
await page.locator('.devsite-result-item-link').click();
const textSelector = await page
.locator('::-p-text(Customize and automate)')
.waitHandle();
const fullTitle = await textSelector?.evaluate(el => el.textContent);
console.log('The title of this blog post is "%s".', fullTitle);
await browser.close();
1. 截图功能
await page.screenshot({
path: 'example.png',
fullPage: true // 全页截图
});
2. 生成 PDF
await page.pdf({
path: 'page.pdf',
format: 'A4',
printBackground: true
});
3. 等待元素出现
// 等待选择器出现
await page.waitForSelector('.my-element');
// 等待函数返回真值
await page.waitForFunction(() => document.readyState === 'complete');
// 使用 locator(推荐)
const element = await page.locator('.my-element').waitHandle();
1. 拦截网络请求
await page.setRequestInterception(true);
page.on('request', request => {
if (request.url().includes('ads'))
request.abort(); // 阻止广告请求
else
request.continue();
});
2. 执行注入脚本
// 在页面加载前注入
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {get: () => false});
});
// 在已加载的页面执行
const result = await page.evaluate(() => {
return document.title;
});
3. 并行处理多个页面
const browser = await puppeteer.launch({
headless: 'shell' // 使用 chrome-headless-shell,更轻量
});
const pages = await Promise.all([
browser.newPage(),
browser.newPage(),
browser.newPage(),
]);
await Promise.all(
pages.map((page, i) =>
page.goto(`https://example.com/page/${i}`)
)
);
import puppeteer from 'puppeteer';
async function scrapeArticles(url: string) {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2' });
const articles = await page.evaluate(() => {
return Array.from(document.querySelectorAll('article h2'))
.map(h2 => ({
title: h2.textContent?.trim(),
link: h2.querySelector('a')?.href
}));
});
await browser.close();
return articles;
}
// 使用
const results = await scrapeArticles('https://blog.example.com');
console.log(results);
问题:npm install puppeteer 后运行时报错 Failed to launch chrome
原因:包管理器的沙箱机制阻止了安装脚本执行,浏览器未下载。
解决方案:
# 方法1:手动下载浏览器
npx puppeteer browsers install
# 方法2:允许安装脚本(npm)
npm config set ignore-scripts false
npm install puppeteer
# 方法3:使用 --unsafe-perm(CI 环境)
npm install puppeteer --unsafe-perm=true
问题:Protocol error: Connection closed
原因:浏览器进程意外退出,可能是内存不足或超时。
解决方案:
const browser = await puppeteer.launch({
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage', // 解决 Docker 环境共享内存不足
'--max-old-space-size=4096' // 增加内存限制
],
timeout: 60000 // 增加启动超时
});
问题:爬取大量页面时速度慢
解决方案:
// 1. 使用同一个 browser 实例,复用连接
// 2. 禁用图片和 CSS 加载
await page.setRequestInterception(true);
page.on('request', req => {
if (['image', 'stylesheet', 'font'].includes(req.resourceType()))
req.abort();
else
req.continue();
});
// 3. 使用 chrome-headless-shell(更轻量)
const browser = await puppeteer.launch({
headless: 'shell' // 比默认 headless 更快
});
问题:某些网站检测到 Puppeteer 并拒绝访问
解决方案:
const browser = await puppeteer.launch({
headless: false, // 使用有头模式
args: [
'--disable-blink-features=AutomationControlled', // 移除自动化特征
'--user-agent="Mozilla/5.0 ..."' // 自定义 UA
]
});
// 注入脚本隐藏 webdriver 特征
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
window.navigator.chrome = {runtime: {}};
});
Puppeteer 作为 Google 官方维护的浏览器自动化库,凭借其简洁的 API、强大的功能和活跃的社区,已成为 Node.js 生态中浏览器自动化的首选方案。无论是做 E2E 测试、网页爬取,还是服务端渲染,Puppeteer 都能提供可靠的支持。
项目亮点总结:
如果你正在寻找一个可靠的浏览器自动化工具,Puppeteer 绝对值得一试。项目 GitHub 地址:https://github.com/puppeteer/puppeteer
参考资料:
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。