前端页面巡检-Puppeteer实战

需求背景

需要前端提供方案,来支持H5或Web静态页面的巡检功能

巡检服务主要包含对页面状态的检测(死链检测),页面的截图等功能

方案选型

方案上直接上Headless browser(无头浏览器),这也是目前比较通用、成本最低的一种方案,无头浏览器的框架也不少,对几个比较大的做了调研:

HeadLess Browser支持语言覆盖浏览器内核支持多标签+表单录制脚本文档资源和社区活跃
Puppeteer只支持 JavaScript & TypeScript \ python只支持 Chromium/FirefoxAPI更友好,更直观支持,基于Puppeteer Recorder录制脚本文档比较齐全,国内检索教程也不少(used by 213k)GitHub 78K Star
PlayWrightJavaScript & TypeScript\python\C#\Go\Java支持Chromium/WebKit/FirefoxAPI更友好,更直观支持,基于 playwright codegen 命令录制脚本文档比较齐全,教程也有一些但不多,比较新(used by 12.8k)Github 40K star
Seleniumjava\python\ruby\C#\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 方法去开始巡检页面。

下面说下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-reassignheight_limit = true;} else if (document.scrollingElement.scrollTop + scrollStep > max_height_px) {// eslint-disable-next-line no-param-reassignheight_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 -y install 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> 

最主要的是这部分:

配置好后执行 fc-cache 命令(扫描字体目录并生成字体缓存); 最后可以通过 fc-list 命令来检查支持的字体; 配置成功后截图出来的网页就不会有中文乱码啦~

Docker 部署

Puppeteer 的服务通过 Docker 部署也算踩了很多坑,这里先说下node服务的部署,在大多数公司我们的服务都是采用 Docker 部署的,我们的也不例外,Docker 部署一般需要把 egg 服务修改为前台启动:

DockerFile

踩了很多坑,也查了不少资料,这里说下我的 DockerFile , 已跑通。

1.首先是镜像选择,在镜像选择之前你需要了解 alpine/buster/stretch/jessie/bullseye 版本的区别,这里有一篇博客列出来了可以参考:Docker镜像版本区别 这里我选择公司内部的 apline-node 镜像源(你可以替换为公共node镜像,这里用的是node 16 版本):

FROM xxx.xxx.com/alpine-node 

2.安装 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 

3.依赖包安装成功后同样需要来搞字体的问题,这里我用 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 的命令,这有些命令是会报错的从而导致你的部署失败。

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

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

结语

巡检服务对于前端页面有很多用处,可以做一些检测、自动化测试、产出性能报告等等,可以作为前端开发过程中一个有效的工具去进行应用,对于个人来说在技术的广度上也能得到很大的提升~~

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取


版权声明:本文为web22050702原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。