介绍
- 在实际的项目中有很多需要截图的场景,例如分享图片(图片是某个页面动态的数据)、打卡、保存
HTML
页面等。 - 文末有对整个库的一个归纳总结,哪些是什么(属性、方法...),哪个属性属于哪个类,以及这些都是怎么调用的。
Puppeteer 是什么
Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。Puppeteer 默认以 headless 模式运行,但是可以通过修改配置文件运行“有头”模式。
Puppeteer 能做什么
- 可以在浏览器中手动执行的绝大多数操作都可以使用
Puppeteer
来完成! 例如: - 生成页面截图、
PDF
。 - 抓取
SPA
(单页应用)并生成预渲染内容(即“SSR
”(服务器端渲染))。 - 自动提交表单,进行
UI
测试,键盘输入等。 - 创建一个时时更新的自动化测试环境。 使用最新的
JavaScript
和浏览器功能直接在最新版本的Chrome
中执行测试。 - 捕获网站的 timeline trace,用来帮助分析性能问题。
- 测试浏览器扩展。
官方演示地址: try-puppeteer.appspot.com/
开始使用
- Puppeteer 至少需要 Node v6.4.0,下面的示例使用 async / await,它们仅在 Node v7.6.0 或更高版本中被支持。
截取为图片
// example.js
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'example.png'});
await browser.close();
})();
// 执行
node example.js
- 创建自执行函数;
- 使用默认配置创建一个浏览器的实例;
- 打开一个链接,并截图配置保存地址及名称;
- 关闭浏览器。
截取为PDF
// hn.js
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com', {waitUntil: 'networkidle2'});
await page.pdf({path: 'hn.pdf', format: 'A4'});
await browser.close();
})();
// 执行
node hn.js
在打开的页面中执行脚步
// get-dimensions.js
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// Get the "viewport" of the page, as reported by the page.
const dimensions = await page.evaluate(() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio
};
});
console.log('Dimensions:', dimensions);
await browser.close();
})();
// 执行
node get-dimensions.js
截图功能扩展
launch 常用配置
headless
:是否使用无头模式,默认为true
;executablePath
:使用的chrome
路径;defaultViewport
:默认视图的配置(width、height、deviceScaleFactor、isMobile
等);- 更多 launch 配置。
screenshot 常用配置
path
:保存的路径;type
:保存图片的类型(png | jpeg | webp
);fullpage
:是否截取全屏;HTML body 的高度,如果不是body滚动,内容无法被完整截取!!!omitBackgroud
:隐藏默认的白色背景,背景透明。- 更多 screenshot 配置
page 页面设置
- 设置打开页面的设备类型
console.log('puppeteer.devices',puppeteer.devices)
await page.emulate(puppeteer.devices['Galaxy S8'])
await page.goto('https://www.baidu.com')
2. 获取输入框并输入内容,截取操作结果
await page.type('#index-kw', 'puppeteer')
await page.click('#index-bn')
- 截取指定元素
/** content 截取指定元素 querySelector */
const content = await page.$('.poster-preview-content');
await content.screenshot({
path: './full-baidu.jpg',
type: 'png', // png | jpeg | webp
// fullPage: true,
omitBackground: true, // 隐藏默认的白色背景,背景透明
})
- 获取页面样式,动态设置截图的宽高等
const dom = await page.$eval('.poster-preview-content',(x) => {
return JSON.parse(JSON.stringify(window.getComputedStyle(x)))
})
console.log('dom', dom.width, dom.height)
await page.setViewport({
width: Number(dom.width.slice(0, -2)),
height: Number(dom.height.slice(0, -2))
})
- 设置截图等待时间
try {
await page.waitForNavigation({ timeout: 1000 })
} catch (e) {
console.log('err', e)
}
API 介绍
Browser
- 当
Puppeteer
连接到一个Chromium
实例的时候会通过puppeteer.launch
或puppeteer.connect
创建一个 Browser 对象。
const browser = await puppeteer.launch({
headless: true
})
- 事件
- browser.on('disconnected')从 Chromium 实例断开连接时被触发(Chromium 关闭或崩溃、调用
browser.disconnect
方法)。 - browser.on('targetchanged')当目标的 url 改变时被触发。
- browser.on('targetcreated')当目标被创建时被触发,例如当通过
window.open
或browser.newPage
打开一个新的页面。 - browser.on('targetdestroyed')当目标被销毁时被触发,例如当一个页面被关闭时。
- 方法
- browser.browserContexts()
- browser.close()
- browser.createIncognitoBrowserContext()
- browser.defaultBrowserContext()
- browser.disconnect()
- browser.newPage()
- browser.pages()
- browser.process()
- browser.target()
- browser.targets()
- browser.userAgent()
- browser.version()
- browser.wsEndpoint()
Page
Page
提供操作一个tab
页或者 extension background page 的方法。一个 Browser 实例可以有多个 Page 实例。Page
拥有很多的事件和方法,例如上文使用的goto
、waitForNavigation
、screenshot
等等都是常用的方法,更多请查阅官网 Page
Worker
Worker
类表示一个 WebWorker。在页面对象上workercreated
和workerdestroyed
事件被触发,以标识worker
的生命周期。
page.on('workercreated', worker => console.log('Worker created: ' + worker.url()));
page.on('workerdestroyed', worker => console.log('Worker destroyed: ' + worker.url()));
console.log('Current workers:');
for (const worker of page.workers()){
console.log(' ' + worker.url());
}
- 方法
- worker.evaluate(pageFunction, ...args)
- worker.evaluateHandle(pageFunction, ...args)
- worker.executionContext()
- worker.url()
Keyboard、Mouse、Touchscreen
- 跟据名称我们就能清楚知道这是和键盘、鼠标、触摸相关的
API
,当我们在浏览器中有这些操作的时候就需要用到对应的API
了。
// Keyboard
await page.keyboard.type('Hello World!');
await page.keyboard.press('ArrowLeft');
await page.keyboard.down('Shift');
for (let i = 0; i < ' World'.length; i++)
await page.keyboard.press('ArrowLeft');
await page.keyboard.up('Shift');
await page.keyboard.press('Backspace');
// 结果字符串最终为 'Hello!'
// 使用 ‘page.mouse’ 追踪 100x100 的矩形。
await page.mouse.move(0, 0);
await page.mouse.down();
await page.mouse.move(0, 100);
await page.mouse.move(100, 100);
await page.mouse.move(100, 0);
await page.mouse.move(0, 0);
await page.mouse.up();
// touchscreen
touchscreen.tap(x, y)
Tracing
- 你可以使用
tracing.start
和tracing.stop
创建一个可以在Chrome DevTools
or timeline viewer 中打开的跟踪文件。
await page.tracing.start({path: 'trace.json'});
await page.goto('https://www.google.com');
await page.tracing.stop();
Dialog
const puppeteer = require('puppeteer');
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.dismiss();
await browser.close();
});
page.evaluate(() => alert('1'));
});
ConsoleMessage
- ConsoleMessage 对象由页面通过 'console' 事件分发。
Frame
- 在每一个时间点,页面通过 page.mainFrame() 和 frame.childFrames() 方法暴露当前框架的细节。
- Frame 对象的生命周期由
3
个事件控制,它们通过 page 对象监听:- 'frameattached' - 当框架被页面加载时触发。一个框架只会被加载一次。
- 'framenavigated' - 当框架改变URL时触发。
- 'framedetached' - 当框架被页面分离时触发。一个框架只会被分离一次。
ExecutionContext
-
该类表示一个
JavaScript
执行的上下文。 Page 可能有许多执行上下文:- 每个 frame 都有 "默认" 的执行上下文,它始终在将帧附加到
DOM
后创建。该上下文由frame.executionContext()
方法返回。 - Extensions 的内容脚本创建了其他执行上下文。
- 每个 frame 都有 "默认" 的执行上下文,它始终在将帧附加到
-
除了页面,执行上下文可以在 workers 中找到。
JSHandle
JSHandle
表示页面内的JavaScript
对象。JSHandles
可以使用 page.evaluateHandle 方法创建。
const windowHandle = await page.evaluateHandle(() => window);
// ...
-
JSHandle
可防止引用的JavaScript
对象被垃圾收集,除非是句柄 disposed。 当原始框架被导航或父上下文被破坏时,JSHandles
会自动处理。 -
JSHandle
实例可以使用在page.$eval()
,page.evaluate()
和page.evaluateHandle
方法。
ElementHandle
注意 ElementHandle 类继承自 JSHandle。
ElementHandle
表示一个页内的DOM
元素。ElementHandles
可以通过 page.$ 方法创建。
const puppeteer = require('puppeteer');
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
await page.goto('https://google.com');
const inputElement = await page.$('input[type=submit]');
await inputElement.click();
// ...
});
-
除非处理了句柄 disposed,否则
ElementHandle
会阻止垃圾收集中的DOM
元素。ElementHandles
在其原始帧被导航时将会自动处理。 -
ElementHandle
实例可以在page.$eval()
和page.evaluate()
方法中作为参数。
Request、Response
- 请求相关,当页面发起请求、请求响应时触发。
SecurityDetails
- SecurityDetails 类表示通过安全连接收到响应时的安全性详细信息。
page.on('response', res => {
console.log('SecurityDetails', res.securityDetails()?.protocol())
})
Target
const browser = await puppeteer.launch({
headless: true
})
const page = await browser.newPage()
console.log('Target',page['target']())
CDPSession
CDPSession
实例用于与Chrome Devtools
协议的原生通信:- 协议方法可以用
session.send
方法调用。 - 协议事件可以通过
session.on
方法订阅。
- 协议方法可以用
const client = await page.target().createCDPSession()await client.send('Animation.enable');
client.on('Animation.animationCreated', () => console.log('Animation created!'));
const response = await client.send('Animation.getPlaybackRate');
console.log('playback rate is ' + response.playbackRate);
await client.send('Animation.setPlaybackRate', {
playbackRate: response.playbackRate / 2
});
Coverage
Coverage
收集相关页面使用的JavaScript
和CSS
部分的信息。
TimeoutError
- 某些操作因超时而终止时,就会触发
TimeoutError
。例如 page.waitForSelector(selector[, options]) 或者 puppeteer.launch([options])。
归纳总结
- Event:
Worker/Dialog/ConsoleMessage/Frame/ExecutionContext[frame/worker]/Request/Response
; - page Property:
Accessibility/Keyboard/Mouse/Touchscreen/Tracing/Coverage
; - page Function:
Target
; - page.target() Function:
CDPSession
; - Event Response 返回值 Function:
SecurityDetails
; - ExecutionContext:
frame/worker
; JSHandle、ElementHandle
。
事例代码完整版
const puppeteer = require('puppeteer');
/** BrowserFetcher */
// const browserFetcher = puppeteer.createBrowserFetcher();
(async () => {
const browser = await puppeteer.launch({
headless: true
})
const version = await browser.version()
console.log('browser.version()', version)
const page = await browser.newPage()
/** Event Worker/Dialog/ConsoleMessage/Frame/ExecutionContext[frame/worker]/Request/Response */
// page.on('workercreated', frame => console.log('worker: ' + worker.type()));
/** ExecutionContext frame/worker */
// page.on('frameattached', frame => console.log('console: ' + frame.ExecutionContext()));
/** JSHandle */
// const windowHandle = await page.evaluateHandle(() => window);
/** ElementHandle */
// const inputElement = await page.$('input[type=submit]');
// await inputElement.click();
/** page Property Accessibility/Keyboard/Mouse/Touchscreen/Tracing/Coverage */
// page['accessibility'/'keyboard'/'mouse'/'touchscreen'/'tracing'/'coverage']
/** page Function Target */
console.log('Target',page['target']())
/** page.target() Function CDPSession */
// const client = await page.target().createCDPSession()
/** SecurityDetails -> Event Response 返回值 Function */
// page.on('response', res => {
// console.log('SecurityDetails', res.securityDetails()?.protocol())
// })
// console.log('puppeteer.devices',puppeteer.devices)
await page.emulate(puppeteer.devices['iPhone 8 Plus'])
await page.goto('https://www.baidu.com')
/** 输入框 */
// await page.type('#index-kw', 'puppeteer')
// await page.click('#index-bn')
/** 超时 TimeoutError */
// try {
// await page.waitForNavigation({ timeout: 1000 })
// } catch (e) {
// console.log('err', e)
// }
/** 获取样式 */
// const dom = await page.$eval('.poster-preview-content',
// (x) => {return JSON.parse(JSON.stringify(window.getComputedStyle(x)))})
// console.log('dom', dom.width, dom.height)
// await page.setViewport({width: Number(dom.width.slice(0, -2)), height: Number(dom.height.slice(0, -2))})
// await page.goto('https://mobiledev.hongsong.club/hs-hybrid-app/performance/posterImage?partyCode=p_B480O59G1002&stationId=s_A148H2B84O02&image=https://hs-headlesschrome.hongsong.club/screenshot/FaFEe6Z3H6G4wNht6e2hZwsNE33ry4jE.jpg&screenShot=true')
/** content 截取指定元素 querySelector */
// const content = await page.$('.poster-preview-content');
/** page 截取body */
await page.screenshot({
path: './full-baidu.jpg',
type: 'png', // png | jpeg | webp
// fullPage: true,
omitBackground: true, // 隐藏默认的白色背景,背景透明
})
await browser.close();
})()
往期精彩
- Node.js 版本管理工具 n 最全使用手册
- video 标签在项目中实战、属性及事件详解
- 磕磕绊绊的 4 年前端er,一次含泪总结
- 前端还不会 Nginx 吗?快来学起来
- 金九前端面试总结!
- 从0搭建Vite + Vue3 + Element-Plus + Vue-Router + ESLint + husky + lint-staged
- 「前端进阶」JavaScript手写方法/使用技巧自查
- 公众号打开小程序最佳解决方案(Vue)
- Axios你可能不知道使用方式
「点赞、收藏和评论」
❤️关注+点赞收藏+评论+分享❤️,手留余香,谢谢🙏大家。