前言
近期接触到了海报渲染及生成pdf的业务需求,发现公司前端大佬搭建了一套静态化服务直接暴露出接口调用即可,简单过了一遍,里面掺杂着业务逻辑和其他功能没看太懂😂,但出于对于puppeteer的好奇,于是用了两个周末的时间自己搭建一套简单服务,本文主要目的是记录一次从0到1搭建基本服务的过程,具体内部功能可根据实际业务增加。本文主要分为如下几个部分:
- puppeteer简介
- puppeteer能做什么
- puppeteer入门
- egg脚手架搭建服务
- 日志查看
- 服务器部署
温馨提示:关于puppeteer有很多相关文章,但是相对比较零散,小编刚开始构建也是查阅了好多文档,本文将这几部分整合到一起,希望能够从0到1构建一个完整的服务,麻雀虽小,五脏俱全😄
puppeteer简介
Puppeteer(中文翻译”操纵木偶的人”) 是 Google Chrome 团队官方的无界面(Headless)Chrome 工具,它是一个 Node 库,提供了一个高级的 API 来控制 DevTools协议上的无头版 Chrome 。也可以配置为使用完整(非无头)的 Chrome。Chrome 素来在浏览器界稳执牛耳,因此,Chrome Headless 必将成为 web 应用自动化测试的行业标杆。
puppeteer能做什么
使用 Puppeteer,相当于同时具有 Linux 和 Chrome 双端的操作能力,应用场景可谓非常之多, 正如其翻译为“操纵木偶的人”一样, 你可以通过Puppeteer 提供的API 直接控制Chrome,模拟大部分用户操作来进行UI 测试或者作为爬虫访问页面等等常用功能如下:
- 生成页面截图或PDF
- 抓取SPA 并生成预渲染内容(SSR)
- 自动化表单提交、UI测试,键盘输入 等
- 创建最新的自动化测试环境。 使用最新的JavaScript和浏览器功能直接在最新版本的Chrome中运行测试。
- 捕获站点的时间线跟踪,以帮助诊断性能问题。
- 测试Chrome扩展程序。
puppeteer入门
先上张图,这张图完美诠释了puppeteer的工作原理,后面会越来越理解这张图
我们看下核心功能代码
# 创建浏览器
const browser = await launch({
headless: true, // 默认为 true 打开浏览器,设置 false 不打开
args: [
'--no-sandbox',
'--disable-setuid-sandbox'
]
})
复制代码
# 创建页面
const page = await browser.newPage()
await page.setViewport({
width: params.width,
height: params.height,
deviceScaleFactor: params.ratio
})
复制代码
# 打开传入的网址
await page.goto(params.html)
await this.waitForNetworkIdle(page, 50)
复制代码
# 截图api
await page.screenshot({
path: filePath,
fullPage: false,
omitBackground: true
})
复制代码
温馨提示:使用puppeteer之前我们先基于egg搭建一个node服务暴露出接口,然后利于postman本地测试下,下面我们以生成截图为例。
egg脚手架搭建服务
1.创建项目
$ mkdir puppeteer-sever && cd puppeteer-sever
$ npm init egg --type=simple
$ npm i
复制代码
安装依赖如图:
项目init结构如图:
2.安装puppeteer
npm i puppeteer
复制代码
3.改造下service、controller和router的代码
改造目的:暴露出一个接口,入参为html,值为已有页面链接,这里以www.google.com/为例
controller
router
service
到这我们已经改造完成,下面我们启动服务,并且在postman里调用下暴露的接口
# 启动服务
npm run dev
复制代码
postman 发送请求-端口7001 服务正常启动,如下图
postman 发送请求 地址换成我们暴露出的地址:/api/poster/get 入参数为html=www.google/com,回参:img后… 如下图:
-
接口请求示例
-
我们打开回参的img链接,如下图:
到这里,我们已经成功将谷歌搜索的首页页面转成了图片,还有点小激动😄。实际业务场景也是对应一个独立的海报页面,通过调用服务以参数形式将链接传入即可。
温馨提示:生成图片的方式之前用过html2Canvas的方式生成图片,基本原理是将dom节点生成canvas,再canvas转成图片,这种方式有三个弊端:1.当dom内容过大时,调dpi和scale都无法解决底部内容生成不全(dpi太小会导致图片模糊);2.当生成图片过程中滑动页面,会导致生成图片顶部截取,底部空白,这个是官方一直未解决的问题,有许多hack的方式,比如限制挂载dom的父级滚动,window.scroll(0,0),但是都无法100%解决问题3.因为是基于dom生成,所以在不同终端浏览器访问(比如说ios和安卓以及不同浏览器)会和页面有同样的兼容性问题,而puppeteer则是多端一致的。
再看一个简单生成pdf的例子:
await page.pdf({
width: '1500px',
height: '2122px',
path: filePath,
margin: {
top: '195px',
right: '85px',
bottom: '125px',
left: '85px'
},
printBackground: true,
displayHeaderFooter: true,
headerTemplate,
footerTemplate
})
复制代码
(pdf的生成效果稍后补上)
日志查看
本地服务启动后,只要iterm的窗口不关闭或者不手动结束进程,当调用接口时会自动打出log,可以用console或者debugger的方式直接在node服务端打出log;当部署到服务时需要将进程会在服务器后台启动,这会无法看到实时日志,可以找到对应的日志文件,或者结合三方工具入kibana来监控日志。(配置后面有时间补)
服务器部署
官方建议使用docker部署,流程可参考官方文档:Running Puppeteer in Docker;小编使用的是阿里云的ECS部署,部署到服务器后接口调用成功,但是返回为空值,最终在官方文档里找到了答案:为了保护主机环境免受不受信任的Web内容的侵害,Chrome使用了多层沙箱。为了使其正常工作,应首先配置主机。如果没有可用于Chrome的优质沙箱,它将因错误崩溃,如果您完全信任在Chrome中打开的内容,则可以使用以下--no-sandbox参数启动Chrome :
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
复制代码
总结
整体搭建流程并不复杂,只要跟着小编的思路做就能搞定,但是需要把流程结合业务需求进行改造,期间就会有好多坑,个人建议优先从官方文档中去找答案,因为你凝视着bug的同时bug也在凝视这别人😄,官方会针对集中性问题给出合理的解决方案;后续将结合pupeteer尝试自动化测试demo和其他功能。