使用无头浏览器 puppeteer 定时截图

本贴最后更新于 728 天前,其中的信息可能已经天翻地覆

项目: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 上滚动才行
有更好的方法欢迎讨论

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...