项目: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 上滚动才行
有更好的方法欢迎讨论
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于