基于前端神器-puppeteer搭建静态服务

4,636 阅读6分钟

前言

近期接触到了海报渲染及生成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

controller

router

router

service

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和其他功能。

参考文档