需求背景
需求前端提供计划,来支撑H5或Web静态页面的巡检功用
巡检服务首要包括对页面状况的检测(死链检测),页面的截图等功用
后期可扩展的功用当然不止这些,还能够去加登陆/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 方法去开始巡检页面。
下面说下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>
最首要的是这部分:
装备好后履行 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 的指令,这有些指令是会报错的然后导致你的布置失利。
关于镜像源强烈建议切换为国内镜像源,能够看看阿里云镜像源 或 清华大学开源镜像站
关于中文字体,尽量挑选去装置开源的中文字体。
结语
巡检服务对于前端页面有许多用途,能够做一些检测、自动化测验、产出功能陈述等等,能够作为前端开发过程中一个有用的东西去进行应用,对于个人来说在技能的广度上也能得到很大的提升~~