脚本语言:Node.JS
使用到的模块:puppeteer、fs
先说一下整体思路:
- 使用puppeteer访问指定url
- 获取数据采用的方法是PerformanceAPI,当浏览器打开了指定页面之后使用page.evaluate()命令获取数据
- 捕获数据输出至json文件
首先安装puppeteer
npm install puppeteer
安装完之后可以开始写脚本,关于puppeteer的基本写法可以参考文档
先把框架写出来
const performance = (async (url) => {
let browser = await puppeteer.launch({
headless:false
})
let page = await browser.newPage()
await page.goto(url)
await browser.close()
})
由于是要获取性能数据,所以为了减少误差需要进行多次测试,思路是使用for循环迭代实现。
具体代码如下:
const performance = (async (url, times) => {
async function runPage (url) {
const browser = await puppeteer.launch({
headless:false
})
const page = await browser.newPage()
await page.setDefaultNavigationTimeout(0); // 防止访问超时
await page.waitForTimeout(3000)
await page.goto(url)
await page.waitForNetworkIdle() // 等待至网络连接断开
await browser.close()
}
for (let i = 0; i < times; i++) {
await runPage(url).then(() => {
console.log(`第${i+1}次访问完成`)
})
}
})
先把每次访问页面的操作单独封装成 runPage 函数,然后使用 for 循环,当 i 小于传入的 times 时循环执行runPage
在 goto(url) 前加入 waitForTimeout 等待3秒后再打开 URL 防止短时间内频繁访问页面触发网站防御机制,提升脚本稳定性。
打开链接并且指定执行次数解决了,接下来是获取每次访问链接时的Performance数据。
获取数据使用的是 PerformanceAPI中performance.getEntries()命令。
performance.getEntries()需要在浏览器的console中输入,所以这里需要用到page.evaluate()命令。
具体代码如下:
const performance = (async (url, times) => {
async function runPage (url) {
const browser = await puppeteer.launch({
headless:false
})
const page = await browser.newPage()
await page.setDefaultNavigationTimeout(0);
await page.waitForTimeout(3000)
await page.goto(url)
await page.waitForNetworkIdle() // 等待至网络连接断开
let timingData = JSON.parse(await page.evaluate(
() => JSON.stringify(window.performance.getEntries())
))
await browser.close()
return timingData
}
for (let i = 0; i < times; i++) {
await runPage(url).then(data => {
console.log(`第${i+1}次访问完成`)
console.log(data)
})
}
})
runPage函数内在网络连接断开后加入,执行了getEntries操作,并且使用了变量timingData来接收数据,最后在函数末尾return了timingData
在执行runPage时,then里面使用data接收runPage里面return的timingData
这样就拿到了每次打开页面时所有的PerformanceAPI数据,接下来就是处理数据了。
以百度为例子,执行上面的代码打印出来的data数据是这样的,其他的数据被我删了,这些数据的具体解释可以参考这篇文章
[
{
name: 'https://www.baidu.com/',
entryType: 'navigation',
startTime: 0,
duration: 557,
initiatorType: 'navigation',
nextHopProtocol: 'http/1.1',
workerStart: 0,
redirectStart: 0,
redirectEnd: 0,
fetchStart: 0.19999980926513672,
domainLookupStart: 31.5,
domainLookupEnd: 34.39999985694885,
connectStart: 34.39999985694885,
connectEnd: 78.19999980926514,
secureConnectionStart: 42.09999990463257,
requestStart: 78.29999995231628,
responseStart: 88.89999985694885,
responseEnd: 125,
transferSize: 86446,
encodedBodySize: 86146,
decodedBodySize: 345591,
serverTiming: [],
workerTiming: [],
unloadEventStart: 0,
unloadEventEnd: 0,
domInteractive: 434,
domContentLoadedEventStart: 474.7999999523163,
domContentLoadedEventEnd: 485.09999990463257,
domComplete: 552.2999999523163,
loadEventStart: 552.3999998569489,
loadEventEnd: 557,
type: 'navigate',
redirectCount: 0
},
{
name: 'first-paint',
entryType: 'paint',
startTime: 164.09999990463257,
duration: 0
},
{
name: 'first-contentful-paint',
entryType: 'paint',
startTime: 164.09999990463257,
duration: 0
}
]
因此,可以再写一个for循环,循环data里面的数据,判断type等于navigate时拿到整体数据,name等于first-paint时就拿到了FP数据,name等于first-contentful-paint时就拿到了FCP数据
其他的数据可以按照自己的实际需求进行计算。
由于是每次访问一次页面时都会输出一次data数据,我们要拿的是平均值,所以可以再for循环外面定义一个变量,基础数数值为0
然后将data里面每次的得到的数据,累加到for循环外面的变量里面,最后再除以运行的次数,就拿到了平均值。
具体代码如下:
const performance = (async (url, times, outPath) => {
async function runPage (url) {
const browser = await puppeteer.launch({
headless:false
})
const page = await browser.newPage()
await page.setDefaultNavigationTimeout(0);
await page.waitForTimeout(3000)
await page.goto(url)
await page.waitForNetworkIdle() // 等待至网络连接断开
let timingData = JSON.parse(await page.evaluate(
() => JSON.stringify(window.performance.getEntries())
))
await browser.close()
return timingData
}
let duration = 0
let firstPaint = 0
let firstContentfulPaint = 0
let name
for (let i = 0; i < times; i++) {
await runPage(url).then(data => {
console.log(`第${i+1}次访问完成`)
for (let dataKey in data){
let navigationData = data[dataKey]
if (navigationData.type === 'navigate'){
duration += navigationData.duration
name = navigationData.name
}
else if (navigationData.name === 'first-paint'){
firstPaint += navigationData.startTime
}
else if (navigationData.name === 'first-contentful-paint') {
firstContentfulPaint += navigationData.startTime
}
}
})
let PerformanceData = {}
PerformanceData.name = name
PerformanceData.fistPaint = firstPaint / times
PerformanceData.firstContentfulPaint = firstContentfulPaint / times
PerformanceData.duration = duration / times
let json_data = JSON.stringify(PerformanceData,null,'\t')
let filePath = outPath + '\\' + 'PerformanceReport.json'
fs.writeFile(filePath,json_data,function (err) {
if (err){
return console.log(err);
}
})
}
})
平均数值拿到了将储存在PerformanceData内,并且使用fs写入到文件内并且输出到指定的路径
到这里其实脚本的核心功能已经是实现了的,但是项目中不仅仅只有一个URL,所以这里需要让它自动访问指定的URL,并且执行指定的次数,最后输出报告文件
这里我的思路是,将URL写入到json文件内,用for循环遍历json文件里面的URL,再把URL传到runPage里面
具体代码如下:
const performance = (async (urlPath, times, outPath) => {
async function runPage (url) {
const browser = await puppeteer.launch({
headless:true
})
const page = await browser.newPage()
await page.setDefaultNavigationTimeout(0);
await page.waitForTimeout(3000)
await page.goto(url)
await page.waitForNetworkIdle() // 等待至网络连接断开
let timingData = JSON.parse(await page.evaluate(
() => JSON.stringify(window.performance.getEntries())
))
await browser.close()
return timingData
}
function readJson(path) {
let raw_data = fs.readFileSync(path)
raw_data = raw_data.toString()
return JSON.parse(raw_data)
}
let readJsonData = readJson(urlPath)
for (const readJsonDataKey in readJsonData){
let duration = 0
let firstPaint = 0
let firstContentfulPaint = 0
let name
for (let i = 0; i < times; i++) {
await runPage(readJsonData[readJsonDataKey]).then(data => {
console.log(`${readJsonDataKey}第${i+1}次访问完成`)
for (let dataKey in data){
let navigationData = data[dataKey]
if (navigationData.type === 'navigate'){
duration += navigationData.duration
name = navigationData.name
}
else if (navigationData.name === 'first-paint'){
firstPaint += navigationData.startTime
}
else if (navigationData.name === 'first-contentful-paint') {
firstContentfulPaint += navigationData.startTime
}
}
})
let PerformanceData = {}
PerformanceData.name = name
PerformanceData.fistPaint = firstPaint / times
PerformanceData.firstContentfulPaint = firstContentfulPaint / times
PerformanceData.duration = duration / times
let json_data = JSON.stringify(PerformanceData,null,'\t')
let filePath = outPath + '\\' +readJsonDataKey+ 'PerformanceReport.json'
fs.writeFile(filePath,json_data,function (err) {
if (err){
return console.log(err);
}
})
}
}
})
urlJson文件格式如下:
{
"JianShu": "https://www.jianshu.com/",
"BaiDu": "https://www.baidu.com",
"QQ": "https://www.qq.com"
}