如何自动化获取接口请求时间的详细数据

一、背景

  • 背景:由于内部的图片加载时间过长,后续经过压缩优化后,有效降低了前后的请求速度和图片大小,现在要收集每个图片的请求时间。
  • 整体思路:采用浏览器自动化运行访问并收集数据

二、方案

  • 采用Performance API进行收集 (无法统计单个请求的时间)
  • 采用puppteer控制浏览器进行收集,通过timing API进行获取

三、开发

启动浏览器并获取接口时间

前期调研过发现puppteer无法获取content_download数据。这里采用playwright 进行操控浏览器。

首先先安装playwright ,选择不安装浏览器,我们会启动自己本地的浏览器来进行爬取。

npm init playwright@latest

先启动浏览器

const playwright = require('playwright');

const CHROME_PATH = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'; // chrome路径

// 根据用户的数据进行chrome启动
const ctx = playwright.chromium.launch({
  channel: 'chrome',
  headless: false, // 把无头模式关闭
  executablePath: CHROME_PATH, // chrome的可执行文件路径
  devtools: true,
})
// 上面的ctx就是一个浏览器实例,可以打开多个页面

// 开启了一个页面
const page = await browser.newPage();
// 跳转到对应的链接
page.goto('跳转到你的链接')

上面就已经成功启动了本地的chrome,但是会发现页面的状态是没有登录的,那可以不可以用本地的cookie呢,答案是可以的,首先要找到浏览器的cookie存放在哪,可以在 chrome://version 中看到个人资料路径就是你的数据存放地。

如何自动化获取接口请求时间的详细数据

在playwright也是可以根据用户的数据存放地进行启动的。

const PROFILE_PATH = '~/Library/Application Support/Google/Chrome/';
// 只需要启动的时候launchPersistentContext 使用函数传递数据地址即可
const ctx= playwright.chromium.launchPersistentContext(PROFILE_PATH,{
   ...config
})

再次启动就能看到有带上用户数据了,下一步就是该去获取接口数据了,可以利用request事件进行监听,然后利用timing API进行获取时间。

如何自动化获取接口请求时间的详细数据

page.on('request', request => {
  const log = {
    time: JSON.stringify(request.timing()),
    url: request.url(),
    // dns解析时间
    dns: request.timing().domainLookupEnd - request.timing().domainLookupStart,
    // 建立TCP链接的时间,包含SSL握手时间
    tcp: request.timing().connectEnd - request.timing().connectStart,
    // SSL握手时间
    ssl: request.timing().connectEnd - request.timing().secureConnectionStart,
    // 发出请求耗时+首个字节时间+下载耗时
    request_TTFB_download: request.timing().responseStart - request.timing().requestStart,
    // 上面的所有时间
    total: request.timing().responseEnd,
  }
  console.table(log)
})

根据上面的代码和时间就能获取到对应的阶段时间。但是实际的运行结果却是0或者-1,我们查阅文档发现为-1的时候代表该值无效。

如何自动化获取接口请求时间的详细数据

那么可以猜测这个是请求已发出就触发了该事件,那当然没有具体的时间,我们需要等待请求完成后再进行时间的获取。


  1. 方法一:查阅文档我们发现Response中的API有finished函数可以等待完成,代码稍做改造

如何自动化获取接口请求时间的详细数据

page.on('request', async request => {
  request.response().then(async response => {
      if(!response) return;
      await response.finished()
      // log
  })
})
  1. 方法二:可以直接使用requestfinished事件进行监听回调,requestfinished会在请求结束后触发

改造后就能成功获取时间了

如何自动化获取接口请求时间的详细数据


下一个问题就是这个页面的图片是懒加载的,我们要等待到当前可视区域的所有的图片请求完成后,让页面往下滚动一屏幕,直到最后一个图片请求完成。那我们如何知道当前页面图片已经加载完成了呢,我们的请求都发出去了,只需要判断当前页面是否还存在未完成的请求即可,如果都完成了就开始向下翻页,直到页面最底完成最后一个请求。

首先可以通过 waitForRequest 这个API进行检查

// 先等待1s,让之前的tcp连接断开
await page.waitForTimeout(1000)
// domain 是来判断对应的域名
page.waitForRequest(request => request.url().includes(domain), { timeout });

有了判断条件接下来就是当条件满足的时候去滚动对应的页面,由于这个项目是有iframe的,需要等待iframe先加载完成。没有iframe的话直接等待对应的元素加载完成获取即可

const frame = await page.waitForSelector('iframe')

// 获取iframe的节点的元素句柄的内容框架
const frameContent = await frame.contentFrame()

接下来就是等待滚动区域

await frameContent.waitForSelector('你的选择器')

// 这里需要当前的一屏幕高度,滚动总高度,当前滚动高度
const heightInfo = await frameContent.evaluate((selector) => {
  const element = document.querySelector(selector);
  return element ? {
    scrollHeight: element.scrollHeight,
    scrollTop: element.scrollTop,
    clientHeight: element.clientHeight,
  } : null;
}, '你的选择器');

有了这些信息后只要当没有请求的时候,就让页面进行滚动就可以直到完成并打开下一页。

现在已经获取到了时间了,那么我们就需要统计数据到excel表格里面。

生成数据

由于我们需要对比前后的数据,我们将每个链接作为一个sheet,对比的时候只需要获取对应sheet的值即可,每列的数据按下面的格式通过xlsx库输出到excel即可

const log = {
    time: JSON.stringify(request.timing()),
    url: request.url(),
    // dns解析时间
    dns: request.timing().domainLookupEnd - request.timing().domainLookupStart,
    // 建立TCP链接的时间,包含SSL握手时间
    tcp: request.timing().connectEnd - request.timing().connectStart,
    // SSL握手时间
    ssl: request.timing().connectEnd - request.timing().secureConnectionStart,
    // 发出请求耗时+首个字节时间+下载耗时
    request_TTFB_download: request.timing().responseStart - request.timing().requestStart,
    // 上面的所有时间
    total: request.timing().responseEnd,
  }
  const dataList = []
  
  dataList.push(log)

初始化文件

const xlsx = require('xlsx')
const FILE_NAME = '你要的初始化文件名'

//检查文件是否存在
function checkFileExist() {
  return fs.existsSync(FILE_NAME)
}


function createFile() {
  if (!checkFileExist()) {
    const workbook = xlsx.utils.book_new();
    const worksheet = xlsx.utils.json_to_sheet([]);
    xlsx.utils.book_append_sheet(workbook, worksheet, 'sheet1');
    xlsx.writeFile(workbook, FILE_NAME);
  }
}

try {
  createFile()
} catch {
}

数据覆盖和写入

function appendFile(data, sheetName, fileName = FILE_NAME) {
  const workbook = xlsx.readFile(fileName);
  const worksheet = xlsx.utils.json_to_sheet(data);

  // 如果表里有重名的需要先删掉再覆盖
  if (workbook.Sheets[sheetName]) {
    // 删除表
    delete workbook.Sheets[sheetName];
    // 更新 SheetNames 数组
    workbook.SheetNames = workbook.SheetNames.filter(sheetNameItem => sheetNameItem !== sheetName);

    // 保存更新后的工作簿
    xlsx.writeFile(workbook, fileName, {bookType: 'xlsx'});
  }
  xlsx.utils.book_append_sheet(workbook, worksheet, sheetName);
  xlsx.writeFile(workbook, fileName)
}

总结

playwright或者puppteer可以做的事情不止这么点,我们要多发现工作中的一些重复而且繁琐的事情,让脚本代替我们完成,这样可以节省时间去干别的更加有意义的事情

原文链接:https://juejin.cn/post/7332403912270004263 作者:sxuan

(0)
上一篇 2024年2月8日 下午4:42
下一篇 2024年2月8日 下午4:53

相关推荐

发表回复

登录后才能评论