简介📝
Puppeteer,这个名字听起来像是个木偶,实际上它是一款由Google开源的Node.js工具库。它可以控制Chrome或Chromium浏览器进行自动化测试,网页爬取等操作。使用Puppeteer,你就可以让Chrome浏览器像机器人一样执行你的命令,包括点击、填写表单、截图、生成PDF等。Puppeteer还支持Headless模式,也就是无界面模式,这意味着你可以在后台运行,无需人工干预。同时,你还可以利用Chrome开发者工具进行调试和分析!所以,如果你想进行自动化测试、数据采集等操作,就赶快使用Puppeteer吧~ 冲!🚀🚀🚀
安装和配置🔨
安装
npm install puppeteer --save
launch配置
puppeteer.launch({
// 需要在无头模式下使用Puppeteer,需要在启动浏览器时设置headless选项为true
headless: true,
// Puppeteer默认使用的是Chromium浏览器,如果你想使用Chrome浏览器,需要在启动浏览器时设置executablePath选项为Chrome浏览器的路径
executablePath: 'D:\Program Files\Chrome',
// 设置代理服务器
args: ['--proxy-server=127.0.0.1:8080']
});
配置列表
puppeteer.launch({
headless: true, // 是否以无头模式运行浏览器,默认为true
executablePath: '', // 可执行文件路径,如果不指定则自动下载
args: [], // 命令行参数数组
ignoreDefaultArgs: false, // 是否忽略默认的命令行参数
defaultViewport: null, // 默认视窗大小,null表示自动设置
slowMo: 0, // 延迟毫秒数,用于调试
timeout: 30000, // 超时时间,单位为毫秒
devtools: false, // 是否打开DevTools面板,默认为false
pipe: false, // 是否将浏览器启动的I/O连接通过管道传递,默认为false
handleSIGINT: true, // 是否在收到SIGINT信号时关闭浏览器,默认为true
handleSIGTERM: true, // 是否在收到SIGTERM信号时关闭浏览器,默认为true
handleSIGHUP: true, // 是否在收到SIGHUP信号时关闭浏览器,默认为true
env: {}, // 环境变量对象
userDataDir: '', // 用户数据目录路径
dumpio: false, // 是否将浏览器I/O输出到进程的stdout和stderr中,默认为false
executablePath: '', // 可执行文件路径,如果不指定则自动下载
ignoreHTTPSErrors: false, // 是否忽略HTTPS错误,默认为false
ignoreCertificateErrors: false // 是否忽略SSL证书错误,默认为false
});
举个栗子🌰
下面是打开百度,并生成baidu.pdf
const puppeteer = require('puppeteer');
(async () => {
// 打开一个浏览器实例
const browser = await puppeteer.launch({ headless: false });
// 创建一个空白页面
const page = await browser.newPage();
// 访问百度首页,并等待页面加载完成
await page.goto('https://www.baidu.com/', { waitUntil: 'networkidle0' });
// 将页面保存为pdf格式
await page.pdf({ path: 'baidu.pdf', format: 'A4' });
// 关闭浏览器实例
await browser.close();
})();
效果
核心知识🧠
⚠️ 注意:以下示例为了简化代码,去除了部分async
Browser
Browser对象可以创建一个浏览器实例,它提供了创建和管理多个页面的API。可以使用该对象控制整个浏览器的生命周期,如启动、关闭、创建新页面、管理cookie等。
获取页面
返回一个Promise,包含当前所有打开的页面实例,以数组形式返回
const browser = await puppeteer.launch();
const pages = await browser.pages();
关闭浏览器
关闭浏览器实例
const browser = await puppeteer.launch();
await browser.close();
版本
返回浏览器实例的版本信息
const browser = await puppeteer.launch();
const version = await browser.version();
Target
返回浏览器实例的Target对象
const browser = await puppeteer.launch();
const target = await browser.target();
WaitForTarget
等待符合条件的Target对象,返回一个Promise,在目标被找到时resolve
const browser = await puppeteer.launch();
const target = await browser.waitForTarget(target => target.url() === 'https://baidu.com/');
连接状态
返回布尔值,表示浏览器实例是否连接
const browser = await puppeteer.launch();
console.log(browser.isConnected()); // true
Process
返回Node.js的ChildProcess实例,表示浏览器进程
const browser = await puppeteer.launch();
const process = browser.process();
监听事件
监听浏览器实例的事件
const browser = await puppeteer.launch();
browser.on('disconnected', () => console.log('Browser disconnected'));
单次监听
监听浏览器实例的单次事件
const browser = await puppeteer.launch();
browser.once('disconnected', () => console.log('Browser disconnected'));
移除监听
移除浏览器实例的事件监听器
function onDisconnected() {
console.log('Browser disconnected');
}
const browser = await puppeteer.launch();
browser.on('disconnected', onDisconnected);
browser.removeListener('disconnected', onDisconnected);
移除所有监听
移除浏览器实例的全部事件监听器
function onDisconnected() {
console.log('Browser disconnected');
}
const browser = await puppeteer.launch();
browser.on('disconnected', onDisconnected);
browser.removeAllListeners();
Page
Page对象提供了访问和控制浏览器页面的API,可以模拟用户行为并执行各种操作。
跳转
goto: 当前页面跳转到 www.baidu.com
await page.goto('https://www.baidu.com');
等待元素加载
waitForSelector: 等待页面上 container 元素出现
await page.waitForSelector('.container');
元素获取
$: 获取页面上第一个 container 元素
const element = await page.$('.container');
获取属性
$eval: 获取节点的所有属性,返回一个ElementHandle对象
const href = await page.$eval('#container", ele => ele.href);
点击
click: 模拟点击页面上的 container 元素
await page.click('.container');
视口大小
setViewport: 设置页面的视口大小为 1920x1080
await page.setViewport({width: 1920, height: 1080});
截图
screenshot: 对当前页面进行截图,并将截图保存在 picture.png 文件中
await page.screenshot({path: 'picture.png'});
可执行环境
evaluate: 可以在浏览器页面上下文中执行任意JavaScript代码的方法,它可以访问所有页面中的DOM元素和JavaScript对象
const title = await page.evaluate(() => {
return document.title;
});
注入函数
exposeFunction: 可以将nodejs的方法暴露给浏览器
const crypto = require('crypto');
...
await page.exposeFunction('md5', text =>
crypto.createHash('md5').update(text).digest('hex')
);
console.log(window.md5)
鼠标键盘
keyboard & mouse: 模拟用户鼠标键盘操作
// 模拟按下Esc键
await page.keyboard.down('Escape');
// 模拟松开Esc键
await page.keyboard.up('Escape');
// 模拟按下并松开Enter键
await page.keyboard.press('Enter');
// 模拟输入文本
await page.keyboard.type('hello, world');
// 将鼠标移动到指定位置
await page.mouse.move(100, 100);
// 模拟鼠标单击事件
await page.mouse.click(100, 100);
// 模拟鼠标按下事件
await page.mouse.down();
// 模拟鼠标松开事件
await page.mouse.up();
waitFor系列
waitFor:等待特定条件发生后再继续执行下一步操作
// 等待id为myButton的按钮出现并单击
await page.waitFor('#myButton');
await page.click('#myButton');
// 等待页面中的img元素加载完成
await page.waitForSelector('img', { visible: true });
console.log('All images loaded');
// 等待页面中的第一个a元素出现并单击
await page.waitForXPath('//a[1]');
await page.click('//a[1]');
// 等待页面标题包含“Puppeteer”的页面跳转完成
await page.waitForNavigation({ waitUntil: 'titleContains', url: /puppeteer/i });
console.log('Page navigation completed');
// 等待一个异步操作完成
await page.waitForFunction(() => {
return new Promise(resolve => {
setTimeout(() => {
resolve(true);
}, 1000);
});
}, { polling: 100 });
console.log('Async function completed');
// 等待指定的URL请求完成
await page.waitForRequest(request => {
return request.url().endsWith('.png');
}, { timeout: 5000 });
console.log('PNG request completed');
// 等待指定的URL响应完成
await page.waitForResponse(response => {
return response.url().endsWith('.js');
}, { timeout: 5000 });
console.log('JS response completed');
性能相关
metrics: 得到一些页面性能数据
const metrics = await page.metrics();
属性 | 类型 | 含义 |
---|---|---|
Timestamp | Number | 时间点 |
Documents | Number | 页面的documents数量 |
Frames | Number | 页面的iframe数量 |
JSEventListeners | Number | 页面的js事件数量 |
Nodes | Number | 页面的dom节点数量 |
LayoutCount | Number | 整页面或部分页面的布局数量 |
RecalcStyleCount | Number | 页面样式重新计算数量 |
LayoutDuration | Number | 页面布局总时间 |
RecalcStyleDuration | Number | 页面样式重新计算总时间 |
ScriptDuration | Number | 页面js代码执行总时间 |
TaskDuration | Number | 页面任务执行总时间 |
JSHeapUsedSize | Number | 页面占用堆内存大小 |
JSHeapTotalSize | Number | 总的页面堆内存大小 |
请求响应
我们可以分别对请求和响应做拦截
Request
await page.setRequestInterception(true); // 开启请求拦截
page.on('request', request => { // 监听请求事件
const headers = request.headers(); // 获取请求头部
headers['Authorization'] = 'Bearer ' + token; // 添加token
request.continue({ headers }); // 继续请求
});
Response
page.on('response', response => { // 监听响应事件
const status = response.status(); // 获取响应状态码
const url = response.url(); // 获取响应 URL
});