项目:FasterBi
一、需求
对生成的大屏或者报表定时发送给用户,数据需要是时时的,也就是在页面没有打开的情况下,将大屏截下来,保存为图片发送邮件,因为涉及到图形的渲染,后端无法完成,无头浏览器便派上用场
二、无头浏览器(headless browser)
无头 Headless 就是没有图形交互界面 GUI 的意思,去掉 GUI 是因为 Headless 模式下的 Chrome 开放了编程接口,可以通过代码控制它的行为而不再需要图形界面,也就是所谓的浏览器自动化。
主要用于测试、截屏、服务端渲染,也可以被用来自动执行恶意任务。最常见的形式是做网络爬虫,Selenium+PhantomJS 是 Python 爬虫届人尽皆知的无头浏览器。我选择了 puppeteer.js
三、puppeteer
puppeteer 是一个 node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome,有点类似于 PhantomJS,但 Puppeteer 是 Chrome 官方团队进行维护的,前景更好。
安装
npm i puppeteer npm i puppeteer-core
js 文件放在服务器上,后端用 JAVA 调 node 命令运行 js 文件
node puppeteer.js
加上页面地址和导出类型的参数命令改为 node puppeteer.js url pdf
js 代码如下:
const puppeteer = require('puppeteer'); (async () => { // launch方法创建浏览器实例 const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']}); // newPage()方法获取page对象 const page = await browser.newPage(); await page.setViewport({ width: 1920, height: 1080 }); // 获取命令行传入的参数url和文件类型 const args = process.argv.slice(2) // page对象上调用goto()方法加载页面 await page.goto(args[0], { timeout: 10000, waitUntil: [ 'load', 'domcontentloaded', 'networkidle0', 'networkidle2' ] }); if(args.length < 2) { console.log(`少文件名称参数`) await browser.close() return } let address = args[1] const index = address.lastIndexOf('.') const str = address.substring(index+1) const scrollableSectionEl = await page.$('#Puppeteer_Page_Box'); // 获取滚动容器的scrollHeight值 const dimensions = await autoScroll(page); // 设置视口高度 // await page.setViewport(dimensions); const bounding_box = await scrollableSectionEl.boundingBox(); if(str === 'pdf') { // 生成pdf await page.pdf({ path: address, format: "a4", printBackground: true, "-webkit-print-color-adjust": "exact", }); } if(['png', 'jpg', 'png'].includes(str)) { // 截图 await scrollableSectionEl.screenshot({ path: address, clip: { x: bounding_box.x, y: bounding_box.y, height: dimensions.height, // 滚动元素截图高度为scrollHeight值 width: dimensions.width, }, // fullPage: true }); } await browser.close(); })();
四、遇到的问题
1、截图不全问题
由于图片没有渲染完成,部分 echarts 图还没有渲染出来就截图了,导致截的图片显示不全或者就是一片白色,这要在加载页面的方法 goto 里面寻找解决方法
goto 有两个参数,第一个是要加载的页面地址,第二个是非必填的配置项,解决问题的关键配置是 waitUntil:以什么来判断页面加载结束
waitUntil: [
'load', //等待 “load” 事件触发
'domcontentloaded', //等待 “domcontentloaded” 事件触发
'networkidle0', //在 500ms 内没有任何网络连接
'networkidle2' //在 500ms 内网络连接个数不超过 2 个
]
本来以为只配了 domcontentloaded 就可以,最后发现要全部配上才行。
2、页面很长有滚动条的时候只截取第一屏在 screenshot 方法里面有一个 fullPage 的配置,设置 true 就是截全屏,但是这个全屏指的是 body,也就是滚动条在 body 上才会截取所有内容,如果是在 div 里面的滚动条是无效的,刚好这个项目是在 div 里面滚动,外面包了好几层,只能尝试其他的方法:在截图之前将页面滚动到底。
添加 autoScroll
方法
const autoScroll = (page) => { return page.evaluate(() => { return new Promise((resolve, reject) => { const boxEl = document.querySelector('#Puppeteer_Page_Box'); const scrollHeight = boxEl.scrollHeight; const scrollWidth = boxEl.scrollWidth; let distance = 100; let totalHeight = 0 var timer = setInterval(() => { // boxEl.scrollBy(0, scrollHeight); boxEl.scrollBy(0, distance); totalHeight += distance; if (totalHeight >= scrollHeight) { clearInterval(timer); resolve({ width: scrollWidth, height: scrollHeight, deviceScaleFactor: window.devicePixelRatio }); } }, 100); }) }); }
这样还是没有截出全部,查各种资料还是要在 body 上滚动才行
有更好的方法欢迎讨论
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于