puppeteer 的奇技淫巧

VVl96f.png

# 前言

Puppeteer(中文翻译"木偶") 是 Google Chrome 团队官方的无界面(Headless)Chrome 工具,它是一个 Node 库,提供了一个高级的 API 来控制 DevTools 协议上的无头版 Chrome

# 开启一个页面

;(async function() {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  await page.goto('https://bougieL.github.io')
}

# 获取一个页面的所有链接

由于 puppeteer 是无头浏览器,所以对于非服务端渲染的页面,也是能获取页面所有 link 的,我们可以很轻松的获取一个页面所有链接

async function getUrlsFromPage(page: puppeteer.Page) {
  return await Promise.all(
    (await page.$$('a')).map(async (el) => {
      return await (await el.getProperty('href')).jsonValue()
    })
  )
})

# 获取一个页面所有资源文件的链接(爬虫必备)

利用性能分析工具获取所有资源链接

async function getStaticUrlsFromPage(page: puppeteer.Page) {
  return await page.evaluate(() => {
    return performance
      .getEntries()
      .filter((e) => e.entryType === 'resource')
      .map((e) => e.name)
  })
}

# 生成站点地图

page 开多了内存会爆,利用 async.waterfall 进行流程控制

const urls = []
;(async function sitemap(oUrls) {
  const fns = oUrls.map((url, idx) => {
    return async (prev = []) => {
      await page.goto(url, { waitUntil: 'domcontentloaded' })
      const urls = await getUrlsFromPage(page)
      return [...prev, ...urls]
    }
  })
  async.waterfall(fns, (err, res: string[]) => {
    res = (res || []).filter((url) => {
      return !urls.includes(url)
    })
    urls = [...urls, ...oUrls, ...res]
    if (res.length === 0) {
      browser.close()
      callback(urls)
    } else {
      sitemap(res)
    }
  })
})([domain])

# 预渲染 SPA 页面

如果你的 SPA 应用页面较少,不需从后端抓取数据生成动态页面,那么可以用 puppeteer 进行页面的预渲染。适用于部署在 github pages 上的页面。

yarn add @bougiel/puppeteer-prerenderer -D

参考 bougie-design/scripts/prerender.js (opens new window).

自动获取路由,自动渲染页面

const path = require('path')
const {
  createSPAServer,
  renderUrlsToString,
  getRelativePathFromUrl,
  getUrlsFromSite,
  writeFile
} = require('@bougiel/puppeteer-prerenderer')

const dist = path.resolve(__dirname, '../.docz')
const port = 4200

const server = createSPAServer({
  dist,
  port,
  base: '/bougie-design',
  onCreated: render
})

function render() {
  getUrlsFromSite(`http://localhost:${port}/bougie-design`, (urls) => {
    renderUrlsToString({
      urls,
      onItemRendered(content, url) {
        const rp = getRelativePathFromUrl(url)
        const p = path.join(dist, rp)
        writeFile(p, content)
      },
      onFinished() {
        server.close()
        process.exit(0)
      }
    })
  })
}

VJx0wF.png