20分钟玩转Puppeteer

2,978 阅读6分钟

简介📝

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

参考puppeteer.bootcss.com/api#puppete…

举个栗子🌰

下面是打开百度,并生成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();
})();

效果

img

核心知识🧠

⚠️ 注意:以下示例为了简化代码,去除了部分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();
属性类型含义
TimestampNumber时间点
DocumentsNumber页面的documents数量
FramesNumber页面的iframe数量
JSEventListenersNumber页面的js事件数量
NodesNumber页面的dom节点数量
LayoutCountNumber整页面或部分页面的布局数量
RecalcStyleCountNumber页面样式重新计算数量
LayoutDurationNumber页面布局总时间
RecalcStyleDurationNumber页面样式重新计算总时间
ScriptDurationNumber页面js代码执行总时间
TaskDurationNumber页面任务执行总时间
JSHeapUsedSizeNumber页面占用堆内存大小
JSHeapTotalSizeNumber总的页面堆内存大小

请求响应

我们可以分别对请求和响应做拦截

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

参考资料🎓