前端页面巡检-Puppeteer实战
需求背景
需要前端提供方案,来支持H5或Web静态页面的巡检功能
巡检服务主要包含对页面状态的检测(死链检测),页面的截图等功能
后期可扩展的功能当然不止这些,还可以去加登陆/UI自动化测试等等....只要你想~
方案选型
方案上直接上Headless browser(无头浏览器),这也是目前比较通用、成本最低的一种方案,无头浏览器的框架也不少,对几个比较大的做了调研:
HeadLess Browser | 支持语言 | 覆盖浏览器内核 | 支持多标签+表单 | 录制脚本 | 文档资源和社区活跃 |
---|---|---|---|---|---|
Puppeteer | 只支持 JavaScript & TypeScript \ python | 只支持 Chromium/Firefox | API更友好,更直观 | 支持,基于Puppeteer Recorder录制脚本 | 文档比较齐全,国内检索教程也不少(used by 213k)GitHub 78K Star |
PlayWright | JavaScript & TypeScript\python\C#\Go\Java | 支持Chromium/WebKit/Firefox | API更友好,更直观 | 支持,基于 playwright codegen 命令录制脚本 | 文档比较齐全,教程也有一些但不多,比较新(used by 12.8k)Github 40K star |
Selenium | java\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-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 -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 , 已跑通。
- 首先是镜像选择,在镜像选择之前你需要了解 alpine/buster/stretch/jessie/bullseye 版本的区别,这里有一篇博客列出来了可以参考:Docker镜像版本区别 这里我选择公司内部的 apline-node 镜像源(你可以替换为公共node镜像,这里用的是node 16 版本):
FROM xxx.xxx.com/alpine-node
- 安装 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
- 依赖包安装成功后同样需要来搞字体的问题,这里我用 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 的命令,这有些命令是会报错的从而导致你的部署失败。
关于镜像源强烈建议切换为国内镜像源,可以看看阿里云镜像源 或 清华大学开源镜像站
关于中文字体,尽量选择去安装开源的中文字体。
结语
巡检服务对于前端页面有很多用处,可以做一些检测、自动化测试、产出性能报告等等,可以作为前端开发过程中一个有效的工具去进行应用,对于个人来说在技术的广度上也能得到很大的提升~~
转载自:https://juejin.cn/post/7154303747177316383