Puppeteer + Nodejs 通用全屏网页截图方案(四)页面处理

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

# (一) 基本功能

# (二) 常用参数实现

# (三) 进阶处理

如何在页面中判断图片已加载完成

主动调用注入函数进行截图的场景,通常都是我们自己的业务页面,这时我们可以在页面中对资源加载情况进行判断,来决定截图的时机。

起初我的想法是把图片url链接传进一个处理函数,函数中用Image对象加载src,当实例对象触发onload时就接着下一个,直到全部处理完毕那么也就算资源加载完成,但是很快我发现这样处理并不对,因为这个函数实际是异步的,很可能先加载完了资源但是该资源在页面中却并未加载完成,所以正确的方式应该是获取到页面当中真实的资源DOM节点,传入这个函数中处理,而且onload回调也不一定正确,经测试最稳定的方式是轮询complete属性来确定是否真的加载完成。

// Preload.ts 参考代码
export default class PreLoad {
  private i: number
  private arr: any[]
  constructor(arr: string[]) {
    this.i = 0
    this.arr = arr
  }
  public doms() {
    return new Promise((resolve: Function) => {
      const work = () => {
        if (this.i < this.arr.length) {
          this.arr[this.i].complete && this.i++ // 核心是轮询img节点的complete属性
          setTimeout(() => {
            work()
          }, 100)
        } else {
          resolve()
        }
      }
      work()
    })
  }
}

假设业务页面当中,内容是通过接口请求到前端渲染,为每个图片的div容器我都加上了img__box这个class样式,即<div class="img__box"> <img /> </div>这种形式,那么我可以这么处理:

const imgsData = []
const cNodes = document.querySelectorAll('.img__box')
        for (const el of cNodes) {
            imgsData.push(el.firstChild)
        }
        
const preload = new Preload(imgsData)
await preload.doms() // 实例化上面的Preload函数,开始轮询资源

console.log('--> 加载完成,可以开始截图')
try {
     window.loadFinishToInject('done') // 触发`Puppeteer`的注入方法
} catch (err) {}

懒加载页面处理方法

有时我们会遇到截取页面资源是懒加载的情况,像生成第三方网页文章时会非常不稳定,而且也不能通过单纯的sleep等待函数来解决问题。

所以我们需要加一个自动滚动的方法,来模拟真实的页面浏览触发页面的资源懒加载。

实现的核心是利用 Puppeteerevaluate 函数,改方法可以在目标页面上下文中执行JS代码,简单的触底判断:比较两次滚动后的scrollTop是否一致,如果一致就是页面不再往下滚了,即判断为触底。

// 创建自动滚动函数
async function autoScroll() {
  await page.evaluate(async () => {
    await new Promise((resolve, reject) => {
      try {
        const maxScroll = Number.MAX_SAFE_INTEGER
        let lastScroll = 0
        const interval = setInterval(() => {
          window.scrollBy(0, 100)
          const scrollTop = document.documentElement.scrollTop || window.scrollY
          if (scrollTop === maxScroll || scrollTop === lastScroll) { // 判断触底,或超出js最大安全长度
            clearInterval(interval)
            resolve()
          } else {
            lastScroll = scrollTop
          }
        }, 100) // 100毫秒执行间隔
      } catch (err) {
        console.log(err)
        reject(err)
      }
    })
  })
}

在截图前加入该函数:

page.on('load', async () => {
        await autoScroll() // 自动截图时先模拟滚动
        await sleep(wait) // 前面实现的等待方法
        // 开始截图
        await page.screenshot({ path, fullPage: true })
        // 关闭浏览器
        await browser.close()
})

这样当页面加载完成后,就会触发自动滚动,每100毫秒向下滚100像素,直到触底为止,跳出Promise,配合前面我们实现的wait参数,等待 x 毫秒后开始截图(这里滚动只是触发了资源加载,如果不等待一下资源有可能没加载完)

最终结果如下,大部分页面的情况应该都差不多:

正常截图 加入自动滚动
image.png image.png

下一篇文章讲讲服务器部署的相关细节。

猜你喜欢

转载自juejin.im/post/7112855391700008990