需求背景

需求前端提供计划,来支撑H5或Web静态页面的巡检功用

巡检服务首要包括对页面状况的检测(死链检测),页面的截图等功用

前端页面巡检-Puppeteer实战
后期可扩展的功用当然不止这些,还能够去加登陆/UI自动化测验等等….只需你想~

计划选型

计划上直接上Headless browser(无头浏览器),这也是现在比较通用、本钱最低的一种计划,无头浏览器的结构也不少,对几个比较大的做了调研:

HeadLess Browser 支撑言语 覆盖浏览器内核 支撑多标签+表单 录制脚本 文档资源和社区活跃
Puppeteer 只支撑 JavaScript & TypeScript python 只支撑 Chromium/Firefox API更友爱,更直观 支撑,根据Puppeteer Recorder录制脚本 文档比较完全,国内检索教程也不少(used by 213k)GitHub 78K Star
PlayWright JavaScript & TypeScriptpythonC#GoJava 支撑Chromium/WebKit/Firefox API更友爱,更直观 支撑,根据playwright codegen指令录制脚本 文档比较完全,教程也有一些但不多,比较新(used by 12.8k)Github 40K star
Selenium javapythonrubyC#C++JavaScript 运行在现在一切干流浏览器上 经过 switch_to 切换 支撑,Selenium IDE能够录制脚本 官方文档一般,但作为老牌的结构 教程多一些(used by 149k)Github 24K star
Cypress 只支撑 JavaScript & TypeScript 只支撑 Chrome/Firefox 没有真实支撑 不支撑(能够使用Cypress Studio,但这是一个实验性的功用) 官方文档质量、社区活跃度还不错 (used by 476k)Github 39K star

在这几大结构中个人更偏向于 puppetter 和 playwright:

puppetter 和 playwright都比较新一些,两者的API 也很相似;puppetter 由谷歌于2017年发布;playwright 由微软于2020年1月发布第一个公共版别。

Playwright有一个非常重要的功用,是它对浏览器Context的支撑。它能够在单个浏览器实例中运行阻隔的操作,因此您能够设置多个Context以一起测验多个Web页面。在每个Context中创立页面。页面支撑它们自己的单击交互,并且能够并行监督。进入页面后,能够使用CSS或XPath挑选器,HTML特点或文本,以不同的方式查找与之交互的内容。

Playwright 支撑的浏览器也比较多一些,不过终究我仍是挑选了 puppetter ,感兴趣的能够自行测验 Playwright 去做。

挑选 puppetter 的一个首要原因也是老练的社区和文档,有安稳的团队保护,还有就是我们仅需求跑Chrome浏览器就够了。

别的考虑到学习本钱及技能栈(NodeJS),仍是建议没玩过这种无头浏览器的前端同伴从 puppetter 开始。

技能完成

技能完成层面,server 端首要选用egg结构来启服务,没触摸过的能够检查Egg官方文档 对外暴露出一个 api 来对巡检服务的调用。

Service 中的首要逻辑就是中心了,是运用 puppetter 去敞开无头浏览器,所以封装了一个巡检的类PatrolCore,经过调用实例的 start 方法去开始巡检页面。

前端页面巡检-Puppeteer实战

下面说下PatrolCore的首要逻辑,第一步首要是敞开无头浏览器,这儿选用了puppeteer-cluster 这个包,这个包为你封装了一个类似线程池的这么一套机制,能够自己去界说敞开的worker数量,这在多页面巡检和爬取的时候非常有用,我这儿敞开了最大5个worker, 它的内部会按需复用并在呈现错误时去重启浏览器和重试,这样为我们节省了许多需求处理的逻辑。

const cluster = await Cluster.launch({
    concurrency: Cluster.CONCURRENCY_CONTEXT,
    maxConcurrency: 5,
    puppeteerOptions: {
      headless: true,
      args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage' ],
      ignoreDefaultArgs: [ '--disable-extensions' ],
      executablePath: '/usr/bin/chromium-browser', // 指定chromium途径
    }, // 传递给puppeteer.launch的目标
    // perBrowserOptions: [], // 传递给每个浏览器的puppeteer.launch的目标
    // retryLimit: 2, // 在将worker标记为失利之前,您希望多长时间重试一次作业
  });

一起puppeteer-cluster这个包还提供了API帮助你去对每个页面添加使命,或者为一切要巡检的页面一致添加使命

await cluster.task(async ({ page, data: url, worker }) => {
    await this.pageTask(page, url, worker);
});
this.urls.forEach(async url => {
    if (urlCheck(url)) {
      cluster.queue(url);
    } else {
      this.appContext.logger.warn(`The url -> ${url} is Illegal URL! Will not crawl !`);
    }
  });

我这儿为一切要巡检的页面添加了一致的使命,就是检测页面状况和截图:

await page.setViewport({
    width: cWidth,
    height: cHeight });
  const gotoPageRes = await page.goto(url, { waitUntil: 'networkidle0' });
  this.appContext.logger.info(`Go to page: ${url}; And current worker id is ${worker.id} .`);
  // 页面打开状况
  this.results[url].status = gotoPageRes.status();
  if (gotoPageRes.status() >= 400) {
    this.appContext.logger.error(`${gotoPageRes.url()} error: status is ${gotoPageRes.status()}`);
  }
  // 页面翻滚(获取页面懒加载烘托的部分)
  await pageScroll(page, cHeight);
  ......省掉n行代码
  await page.screenshot({ path: imgPath, fullPage: true });

在截图的时候要留意,如果是有懒加载的页面,在截图前要模仿页面翻滚,模仿翻滚到底部后,截图才能够截取完好,以下部分为模仿翻滚的代码:

exports.pageScroll = async (page, cHeight) => {
  // 网页加载最大高度
  const max_height_px = 20000;
  // 翻滚高度
  const scrollStep = cHeight;
  const height_limit = false;
  let mValues = { scrollEnable: true, height_limit };
  while (mValues.scrollEnable) {
    mValues = await page.evaluate((scrollStep, max_height_px, height_limit) => {
      // 防止网页没有body时,翻滚报错
      if (document.scrollingElement) {
        const scrollTop = document.scrollingElement.scrollTop;
        document.scrollingElement.scrollTop = scrollTop + scrollStep;
        if (document.body !== null && document.body.clientHeight > max_height_px) {
          // eslint-disable-next-line no-param-reassign
          height_limit = true;
        } else if (document.scrollingElement.scrollTop + scrollStep > max_height_px) {
          // eslint-disable-next-line no-param-reassign
          height_limit = true;
        }
        let scrollEnableFlag = false;
        if (document.body !== null) {
          scrollEnableFlag = document.body.clientHeight > scrollTop + 1081 && !height_limit;
        } else {
          scrollEnableFlag = document.scrollingElement.scrollTop + scrollStep > scrollTop + 1081 && !height_limit;
        }
        return {
          scrollEnable: scrollEnableFlag,
          height_limit,
          document_scrolling_Element_scrollTop: document.scrollingElement.scrollTop,
        };
      }
    }, scrollStep, max_height_px, height_limit);
    await sleep(800);
  }
};

关于页面巡检的中心逻辑就是这些了,后续能够按自己的需求在从中添加功用,如自动化测验等等~

布置

巡检东西的终究布置是绕不开的一个环节,也是比较容易踩坑的一个环节

node 服务的布置选用 egg 内置的egg-cluster来发动 Master 进程,这儿基本按官方文档来就ok

Linux CentOS8下布置

服务器是 CentOS8 的体系,首要是 Puppeteer 依靠的装置和布置(依靠 chromium)。

中文字体

在Linux中布置后,巡检截图中的文字都是乱码的,这是由于没有中文字体的原因,所以需求我们手动装置中文字体。我这儿用的方式是从windows中将字体文件copy出来,上传到服务器,然后装置ttmkfdir 东西:

yum -yinstall ttmkfdir
ttmkfdir-e /usr/share/X11/fonts/encodings/encodings.dir

接着修改字体装备文件 vim /etc/fonts/fonts.conf:

<!-- Font directory list -->
        <dir>/usr/share/fonts</dir>
        <dir>/usr/share/X11/fonts/Type1</dir> <dir>/usr/share/X11/fonts/TTF</dir> <dir>/usr/local/share/fonts</dir><dir>/usr/local/share/fonts/chinese</dir>
        <dir prefix="xdg">fonts</dir>
        <!-- the following element will be removed in the future -->
        <dir>~/.fonts</dir>

最首要的是这部分:

前端页面巡检-Puppeteer实战

装备好后履行 fc-cache 指令(扫描字体目录并生成字体缓存); 终究能够经过 fc-list 指令来检查支撑的字体; 装备成功后截图出来的网页就不会有中文乱码啦~

Docker 布置

Puppeteer 的服务经过 Docker 布置也算踩了许多坑,这儿先说下node服务的布置,在大多数公司我们的服务都是选用 Docker 布置的,我们的也不例外,Docker 布置一般需求把 egg 服务修改为前台发动:

前端页面巡检-Puppeteer实战

前端页面巡检-Puppeteer实战

DockerFile

踩了许多坑,也查了不少材料,这儿说下我的 DockerFile , 已跑通。

  1. 首先是镜像挑选,在镜像挑选之前你需求了解 alpine/buster/stretch/jessie/bullseye 版别的差异,这儿有一篇博客列出来了能够参考:Docker镜像版别差异 这儿我挑选公司内部的 apline-node 镜像源(你能够替换为公共node镜像,这儿用的是node 16 版别):
FROM xxx.xxx.com/alpine-node
  1. 装置 chromium 相关的包,这儿牢记切换为国内镜像源,不然你的装置速度巨慢,并且不一定成功!下面的 DockerFile 代码包括了装置依靠包(包括 chromium 和 字体相关的装备包等)和设置时区
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories 
    && sed -i '/dl-4.alpinelinux.org/d' /etc/apk/repositories 
    && apk update 
    && apk add tzdata 
    && apk add --update 
    && apk -U --no-cache update && apk -U --no-cache --allow-untrusted add 
      zlib-dev 
      xorg-server 
      dbus 
      chromium 
      bash 
      bash-doc 
      bash-completion -f 
      font-adobe-100dpi 
      fontconfig 
      xfonts-utils 
      dpkg 
      wget 
      unzip 
    && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 
    && echo "Asia/Shanghai" > /etc/timezone
  1. 依靠包装置成功后同样需求来搞字体的问题,这儿我用 wget 来装置中文字体,并且经过 fc-cache 指令来更新字体
RUN cd /tmp && wget http://ftp.cn.debian.org/debian/pool/main/f/fonts-noto-cjk/fonts-noto-cjk_20170601+repack1-3+deb10u1_all.deb && 
    dpkg -i fonts-noto-cjk_20170601+repack1-3+deb10u1_all.deb    && 
    wget https://github.com/adobe-fonts/source-sans-pro/releases/download/2.040R-ro%2F1.090R-it/source-sans-pro-2.040R-ro-1.090R-it.zip && 
    unzip source-sans-pro-2.040R-ro-1.090R-it.zip && cd source-sans-pro-2.040R-ro-1.090R-it  && mv ./OTF /usr/share/fonts/  && 
    fc-cache -f -v

上面三部分的 DockerFile 基本就大功告成了,这个在我们的 k8s 下经过 Docker 布置完全 ok,终究跑出来的页面截图也不会有乱码的问题呈现,这儿贴出完好的 DockerFile :

FROM xxx.xxx.com/alpine-node
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories 
    && sed -i '/dl-4.alpinelinux.org/d' /etc/apk/repositories 
    && apk update 
    && apk add tzdata 
    && apk add --update 
    && apk -U --no-cache update && apk -U --no-cache --allow-untrusted add 
      zlib-dev 
      xorg-server 
      dbus 
      chromium 
      bash 
      bash-doc 
      bash-completion -f 
      font-adobe-100dpi 
      fontconfig 
      xfonts-utils 
      dpkg 
      wget 
      unzip 
    && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 
    && echo "Asia/Shanghai" > /etc/timezone
RUN cd /tmp && wget http://ftp.cn.debian.org/debian/pool/main/f/fonts-noto-cjk/fonts-noto-cjk_20170601+repack1-3+deb10u1_all.deb && 
    dpkg -i fonts-noto-cjk_20170601+repack1-3+deb10u1_all.deb    && 
    wget https://github.com/adobe-fonts/source-sans-pro/releases/download/2.040R-ro%2F1.090R-it/source-sans-pro-2.040R-ro-1.090R-it.zip && 
    unzip source-sans-pro-2.040R-ro-1.090R-it.zip && cd source-sans-pro-2.040R-ro-1.090R-it  && mv ./OTF /usr/share/fonts/  && 
    fc-cache -f -v
RUN fc-list :lang=zh
WORKDIR /opt/www/xxx-server
COPY . /opt/xxx-server
CMD npm run start

留意事项

在写 DockerFile 的时候需求留意 你的指令和你的体系是否匹配,比如说你的体系是 ubuntu 的 ,而你在这儿写了 centos 的指令,这有些指令是会报错的然后导致你的布置失利。

关于镜像源强烈建议切换为国内镜像源,能够看看阿里云镜像源 或 清华大学开源镜像站

关于中文字体,尽量挑选去装置开源的中文字体。

结语

巡检服务对于前端页面有许多用途,能够做一些检测、自动化测验、产出功能陈述等等,能够作为前端开发过程中一个有用的东西去进行应用,对于个人来说在技能的广度上也能得到很大的提升~~