weoyk / note

0 stars 0 forks source link

我用puppeteer自动化完成了老板的任务最近老板给了我一个枯燥的任务,我用puppeteer插件结合nestjs,既 - 掘金 #2

Open weoyk opened 2 weeks ago

weoyk commented 2 weeks ago

背景

最近我老板让我每周四统计一下小程序的性能数据,汇总到表格中,通过飞书表格来维护各个团队的小程序性能指标数据,比如:启动时间、首次渲染耗时、页面白屏占比等等。

这个工作在一定程度上是有意义的,因为通过这些指标可以复盘一个月或者两个月来,前端对小程序方面的优化是否对性能有提升,结合飞书表格,可以呈现一个趋势图,非常直观。

但是,有一个低效的统计问题,我无法接受,就是每周四我需要登录微信公众号平台,打开性能菜单,找到每一个指标,手动复制到表格中,这个过程大概每次耗时5分钟左右,时间虽然也不长,但是周复一周,每年如此,还是会觉得挺乏味。

于是,我想了很多骚主意,比如:能不能调用微信开放接口?能不能写个chrome插件?能不能用自动化工具?我尝试了每一个方案,最终决定使用 puppeteer自动化工具来实现。

目标

统计小程序性能指标数据。

性能评估 总启动耗时 首次渲染耗时 JS注入耗时 代码包下载耗时 5秒启动占比 页面切换耗时 切换较慢占比 页面进入白屏占比
良好 2000 100 20 300 95% 270 16 1.51

注:以上数字均为随机填写,并不代表标准性能数据。

最终通过自动化方案,实现数据抓取,并返回:

抓取到数据后,绘制小程序性能趋势图:

方案

微信开放接口

在小程序开发文档找到了一个接口:获取小程序性能数据,这个接口的调用也非常简单,只有一个api

调用方式:

POST https://api.weixin.qq.com/wxa/business/performance/boot?access_token=ACCESS_TOKEN 

使用问题

使用以后发现几个问题:access_token返回数据

考虑到上面两个因素,决定暂缓使用API的方式。

chrome 插件

思路是,打开浏览器,登录公众号平台,借助chrome插件来实现对指标数据的获取。

实现思路

  1. 创建一个文件夹,定义:manifest.json文件。
{
  "name": "性能抓取插件",
  "version": "2.0.0",
  "description": "抓取微信数据",
  "manifest_version": 3,
  "icons": {
    "16": "images/icon.png",
    "48": "images/icon.png",
    "128": "images/icon.png"
  },
  "action": {
    "default_icon": "images/icon.png",
    "default_title": "汽销",
    "default_popup": "index.html"
  },
  "background": {
    "service_worker": "service-worker.js"
  },
  "content_scripts":[
    {
      "matches":["https://wedata.weixin.qq.com/*"],
      "js":["content-script.js"],
      "run_at":"document_end"
    }
  ],
  "permissions": [
    "cookies",
    "tabs",
    "notifications",
    "storage"
  ],
  "host_permissions":[]
}

background 是设置插件在后台运行,对于微信数据抓取,此处用不上。 content_scripts 用来匹配对应的网站,然后再run_at时机下,执行脚本。这个是高频功能。 permissions 用来设置插件用到的权限,比如:cookiestabsnotifications等,此处也用不上。

  1. 定义content-script.js文件

此文件会在微信的性能页面打开后,内容加载完成时执行,我们可以通过传统的DOM API来获取数据,比如:

  1. 按照这个方式,依次获取剩下的指标数据。

使用问题:

在使用的过程中,同样发现了一些问题,因为微信性能页面,都是后端渲染的,比如性能报告、启动性能、网络性能、运行性能和体验分析他们都是点击以后,才会渲染页面,那就意味着,chrome插件有获取难度。

总结:考虑上面问题后,此方案也暂缓使用。

使用 puppeteer 自动化插件

为什么会想到这个方案呢?人工获取数据,需要先登录,依次点击各个Tab页面,然后复制数据,这个过程刚好是puppeteer最擅长的,所以技术吻合,省时省力。

实现方案:

创建一个nestjs项目,安装puppeteer插件,除了扫码以外,其它都是自动化实现:自动点击菜单、自动抓取数据、自动截图、自动点击按钮、自动切换Tab、自动打开新窗口等。

  1. 安装nestjs脚手架,并创建项目。
npm i -g @nestjs/cli
$ nest new puppeteer

如果对于nestjs 不了解,可以去官网看一下文档,写几个Demo就能入门,实现这个插件实际上并不需要很了解nestjs,只是用它当做node服务而已,你也可以用Express或者koa去做。

  1. 安装puppeteer插件。
pnpm add puppeteer
  1. 打开app.controller.ts文件,创建有一个有头浏览器,并启动。
@Get()
async getPerformanceData() {

    const browser = await puppeteer.launch({
      headless: false,
    });
}

@Get() 是一个注解函数,表示这个方法是get请求。 headless设置为false表示启动的浏览器是能打开,看见的。如果是无头,则不会打开浏览器。

  1. 打开chrome后,新建一个页面,跳转到微信公众号平台。

const page = await browser.newPage();

await page.goto('https://mp.weixin.qq.com/', {
  waitUntil: 'domcontentloaded', 
});

注:每一步的代码,都是接着上一步的代码编写的。

  1. 设置窗口大小

await page.setViewport({ width: 1400, height: 1200 });
  1. 等待用户扫码

await page.waitForFunction(() => {
  return document.querySelectorAll('.data-overview__title').length > 0;
});

这里要注意,扫码只能用户来做,插件没办法实现扫码功能,而且微信公众平台,也无法用账号密码的方式来登录,大家可以去测试。

通过waitForFunction函数,可以实现异步等待,当用户扫码成功后,会进入到首页,我们只需要判断首页某一个节点存在就相当于扫码成功。

  1. 进入首页后,点击左侧性能统计菜单

await page.waitForSelector('dt[class="menu_title clickable menu_data"]>a');
await page.click('dt[class="menu_title clickable menu_data"]>a');

  1. 异步打开页面,并设置窗口。
const newPagePromise: Promise<Page> = new Promise((resolve) =>
  browser.on('targetcreated', (target) => resolve(target.page())),
);

const newPage = await newPagePromise;

await newPage.setViewport({ width: 1400, height: 2100 });

当模拟点击【统计】菜单时,微信会另开一个页面,上面代码中的page对象指的是第一个窗口,如果微信另开一个窗口,我们是无法使用的,所以必须一步接收一个新的窗口页面,当做上下文对象。

  1. 等待页面加载

await newPage.waitForSelector('div[id="outer_view"]', {
timeout: 5000,
});

平台数据这个页面数据比较多,加载时间比较久,我们多等待一下。

  1. 依次点击性能质量、点击性能数据 获取首页性能报告。

await newPage.locator('div ::-p-text(性能质量)').click();

await newPage.locator('div ::-p-text(性能数据)').click();
await newPage.waitForSelector('.performance_evaluate');
await new Promise((resolve) => setTimeout(resolve, 3000));

await newPage.screenshot({
    path: resolve(__dirname, `./首页性能报告.png`),
    type: 'png',
    fullPage: true,
});

点击性能质量菜单,会展开性能数据菜单,再次点击,才会加载首页性能数据。通过screenshot可以对首页进行截图,中间有一个延迟3秒,也是为了等待页面加载完成,否则截图不全。

  1. 获取首页的相关指标数据

const data = await newPage.evaluate(() => {
    const obj = {};

    const nodes = document.querySelector(
      '.weui-desktop-popover__desc .row',
    ).childNodes;
    obj['性能评估'] = nodes[1].textContent.trim();
    obj['启动耗时'] = nodes[3].textContent.trim();
    obj['首次渲染耗时'] = nodes[4].textContent.trim();
    obj['JS注入耗时'] = nodes[5].textContent.trim();
    obj['代码包下载耗时'] = nodes[6].textContent.trim();
    return obj;
});

首页是包含各种指标数据的,我们通过变量保存并返回。

  1. 点击运行性能按钮,获取数据并截图

await newPage
.locator('.board-nav-container__nav ::-p-text(运行性能)')
.click();

await new Promise((resolve) => setTimeout(resolve, 3000));
await newPage.waitForSelector('span[data-short=切换耗时分析]');

await newPage.setViewport({ width: 1400, height: 5100 });

await newPage.screenshot({
    path: resolve(__dirname, `./运行性能.png`),
    type: 'png',
    fullPage: true,
});

const switchData = await newPage.evaluate(() => {

    const nodes = document.querySelectorAll('.num_1t7ooPGwJu');
    return [nodes[0].textContent.trim(), nodes[2].textContent.trim()];
});
data['页面切换耗时'] = switchData[0];
data['切换较慢占比'] = switchData[1];
  1. 点击体验分析按钮,获取数据并截图

await newPage
.locator('.board-nav-container__nav ::-p-text(体验分析)')
.click();

await new Promise((resolve) => setTimeout(resolve, 3000));

const whiteData = await newPage.evaluate(() => {

    const nodes = document.querySelectorAll('.num_1t7ooPGwJu');
    return nodes[1].textContent.trim();
});
data['页面进入白屏占比'] = whiteData;

await newPage.setViewport({ width: 1400, height: 2400 });

await newPage.screenshot({
    path: resolve(__dirname, `./体验分析.png`),
    type: 'png',
    fullPage: true,
});
  1. 点击启动性能并截图

await newPage
    .locator('.board-nav-container__nav ::-p-text(启动性能)')
    .click();

await new Promise((resolve) => setTimeout(resolve, 5000));

const path = resolve(__dirname, `./启动性能.png`);
    await newPage.setViewport({
    width: 1400,
    height: 8300,
});

await newPage.screenshot({
    path,
    type: 'png',
    fullPage: true,
});
  1. 关闭浏览器,接口返回数据。

await browser.close();
return data;

通过上面一系列的操作,插件就自动完成了所有数据的抓取,并通过接口返回,我们只需要拷贝数据到表格即可,当然也可以进一步对接飞书表格,通过API完成读写,此处不再扩展了。

总结

其实这个工作量并不大,每周也只是耽误5分钟,但是作为程序员,如果有更快的方式,岂不美哉。我们通过puppeteer插件结合nestjs,除了启动后的扫码以外,剩下的全程自动化实现。 既完成了工作,又学习了新技能。

感谢大家观看,我是河畔一角,一名普通的前端工程师。