puppeteer浅谈

puppeteer浅谈

Puppeteer 是什么

  • Puppeteer 是 Node.js 工具引擎
  • Puppeteer 提供了一系列 API,通过 Chrome DevTools Protocol 协议控制 Chromium/Chrome 浏览器的行为
  • Puppeteer 默认情况下是以 headless 启动 Chrome 的,也可以通过参数控制启动有界面的 Chrome
  • Puppeteer 默认绑定最新的 Chromium 版本,也可以自己设置不同版本的绑定
  • Puppeteer 让我们不需要了解太多的底层 CDP 协议实现与浏览器的通信

Puppeteer 能做什么

  • 网页截图或者生成 PDF
  • 爬取 SPA 或 SSR 网站
  • UI 自动化测试,模拟表单提交,键盘输入,点击等行为
  • 捕获网站的时间线,帮助诊断性能问题
  • 创建一个最新的自动化测试环境,使用最新的 js 和最新的 Chrome 浏览器运行测试用例
  • 测试 Chrome 扩展程序

Puppeteer常用类

  • Browser: 对应一个浏览器实例,一个 Browser 可以包含多个 BrowserContext
  • BrowserContext: 对应浏览器一个上下文会话,就像我们打开一个普通的 Chrome 之后又打开一个隐身模式的浏览器一样,BrowserContext 具有独立的 Session(cookie 和 cache 独立不共享),一个 BrowserContext 可以包含多个 Page
  • Page:表示一个 Tab 页面,通过 browserContext.newPage()/browser.newPage() 创建,browser.newPage() 创建页面时会使用默认的 BrowserContext,一个 Page 可以包含多个 Frame
  • Frame: 一个框架,每个页面有一个主框架(page.MainFrame()),也可以多个子框架,主要由 iframe 标签创建产生的
  • ExecutionContext: 是 javascript 的执行环境,每一个 Frame 都一个默认的 javascript 执行环境
  • ElementHandle: 对应 DOM 的一个元素节点,通过该该实例可以实现对元素的点击,填写表单等行为,我们可以通过选择器,xPath 等来获取对应的元素
  • JsHandle:对应 DOM 中的 javascript 对象,ElementHandle 继承于 JsHandle,由于我们无法直接操作 DOM 中对象,所以封装成 JsHandle 来实现相关功能
  • CDPSession:可以直接与原生的 CDP 进行通信,通过 session.send 函数直接发消息,通过 session.on 接收消息,可以实现 Puppeteer API 中没有涉及的功能
  • Coverage:获取 JavaScript 和 CSS 代码覆盖率
  • Tracing:抓取性能数据进行分析
  • Response: 页面收到的响应
  • Request: 页面发出的请求

创建一个 Browser 实例

puppeteer.launch: 每次都启动一个 Chrome 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const puppeteer = require('puppeteer');
let request = require('request-promise-native');

//使用 puppeteer.launch 启动 Chrome
(async () => {
const browser = await puppeteer.launch({
headless: false, //有浏览器界面启动
slowMo: 100, //放慢浏览器执行速度,方便测试观察
args: [ //启动 Chrome 的参数,详见上文中的介绍
'–no-sandbox',
'--window-size=1280,960'
],
});
const page = await browser.newPage();
await page.goto('https://www.baidu.com');
await page.close();
await browser.close();
})();

等待元素、请求、响应

  • page.waitForXPath:等待 xPath 对应的元素出现,返回对应的 ElementHandle 实例
  • page.waitForSelector :等待选择器对应的元素出现,返回对应的 ElementHandle 实例
  • page.waitForResponse :等待某个响应结束,返回 Response 实例
  • page.waitForRequest:等待某个请求出现,返回 Request 实例
  • page.waitForFunction:等待在页面中自定义函数的执行结果,返回 JsHandle 实例
  • page.waitFor:设置等待时间,实在没办法的做法

具体用例

截图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
//设置可视区域大小
await page.setViewport({width: 1920, height: 800});
await page.goto('https://www.baidu.com/');
//对整个页面截图
await page.screenshot({
path: './capture.png', //图片保存路径
type: 'png',
fullPage: true //边滚动边截图
// clip: {x: 0, y: 0, width: 1920, height: 800}
});
//对页面某个元素截图
let [element] = await page.$x('/html/body/div/div[1]/div[5]');
await element.screenshot({
path: './element.png'
});
await page.close();
await browser.close();
})();

模拟用户登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(async () => {
const browser = await puppeteer.launch({
slowMo: 100, //放慢速度
headless: false,
defaultViewport: {width: 1440, height: 780},
ignoreHTTPSErrors: false, //忽略 https 报错
args: ['--start-fullscreen'] //全屏打开页面
});
const page = await browser.newPage();
await page.goto('https://passport.bilibili.com/login');
//输入账号密码
const uniqueIdElement = await page.$('#uniqueId');
await uniqueIdElement.type('admin@admin.com', {delay: 20});
const passwordElement = await page.$('#password', {delay: 20});
await passwordElement.type('123456');
//点击确定按钮进行登录
let okButtonElement = await page.$('#btn-ok');
//等待页面跳转完成,一般点击某个按钮需要跳转时,都需要等待 page.waitForNavigation() 执行完毕才表示跳转成功
await Promise.all([
okButtonElement.click(),
page.waitForNavigation()
]);
console.log('admin 登录成功');
await page.close();
await browser.close();
})();

请求拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setRequestInterception(true); //开启请求拦截
page.on('request', request => {
const method = request._method;
if(method){
//直接阻止请求
return request.abort();
}else{
//对请求重写
return request.continue({
//可以对 url,method,postData,headers 进行覆盖
headers: Object.assign({}, request.headers(), {
'puppeteer-test': 'true'
})
});
}
});
await page.goto('https://www.bilibili.com/');
await page.close();
await browser.close();
})();

植入 javascript 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.bilibili.com/');
//注册一个 Node.js 函数,在浏览器里运行
//通过 page.evaluate 在浏览器里执行代码
await page.evaluate(async () => {
setTimeout(()=>{
document.querySelector('.logout-face').src='https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=185623326,2667787122&fm=26&gp=0.jpg';
document.querySelector('#van-popover-9482').innerHTML='全是我干的';
},3000)
});
await page.close();
await browser.close();
})();

模拟不同的设备

1
2
3
4
5
6
7
8
9
const puppeteer = require('puppeteer');
const iPhone = puppeteer.devices['iPhone X'];
puppeteer.launch({
headless:false
}).then(async browser => {
const page = await browser.newPage();
await page.emulate(iPhone);
await page.goto('https://www.bilibili.com/');
});
觉得不错的话可以打赏哦