可爱的puppeteer使用小技巧

栏目: Node.js · 发布时间: 5年前

内容简介:总结一些使用puppetter的小技巧,从下面几个角度:默认的puppeteer会在module内部下载一个满足当前版本的chromium,有时由于网络的原因还经常下载失败,民生怨道。从v1.7.0开始,有了

总结一些使用puppetter的小技巧,从下面几个角度:

  • 浏览器的启动与请求
  • 页面的加载与渲染
  • 执行优化与状态管理

浏览器的启动与请求

自定义chromium/chrome路径

默认的puppeteer会在module内部下载一个满足当前版本的chromium,有时由于网络的原因还经常下载失败,民生怨道。

从v1.7.0开始,有了 puppeteer-core 这个轻量级使用puppeteer的方案,可以用它来指定chromium/chrome路径。这样就可以使用系统中所安装的chrome了(puppeteer内部会使用 child_process.spawn() 开启使用指定可执行文件的子进程)。

需要注意以下几点:

PUPPETEER_*
import puppeteer from 'puppeteer-core'
const getDefaultOsPath = () => {
    if (process.platform === 'win32') {
        return 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
    } else {
        return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
    }
}
let browser = await puppeteer.launch({
    executablePath: getDefaultOsPath()
}))

UA相关

获取UA

async function getPuppeteerChromeUA() {
  const browser = await puppeteer.launch();
  const ua = await browser.userAgent();
  await browser.close();
  return ua;
}

使用匿名UA

封装一个函数来设置匿名UA:

async function setAnonymizeUA (page, opts) {
  let ua = await page.browser().userAgent()
  // 1. 替换headless标识
  if (opts.stripHeadless) {
    ua = ua.replace('HeadlessChrome/', 'Chrome/')
  }
  // 2. 设为win10平台
  if (opts.makeWindows) {
    ua = ua.replace(/\(([^)]+)\)/, '(Windows NT 10.0; Win64; x64)')
  }
  // 3. 使用自定义函数处理ua
  if (opts.customFn) {
    ua = opts.customFn(ua)
  }
  await page.setUserAgent(ua)
}

页面的加载与渲染

屏蔽指定类型资源的请求

使用 setRequestInterception() 拦截请求,屏蔽指定类型请求来加快加载速度

...
const blockTypes = new Set(['image', 'media', 'font'])
await page.setRequestInterception(true)
page.on('request', request => {
  const type = request.resourceType()
  const shouldBlock = blockedTypes.has(type)
  this.debug('onRequest', { type, shouldBlock })
  return shouldBlock ? request.abort() : request.continue()
})
...

注意: 启用请求拦截会使页面缓存不可用

控制page对象加载页面的阶段

通过设置 goto 函数中的 waitUntil 参数,使page在 DOMContentLoaded 事件触发时就返回结果,而无需等到Load事件,这样就节省了等待构建渲染树与页面绘制的时间。

对应CDP中的 Page.lifecycleEvent

...
let page = await browser.newPage()
await page.goto('http://some.site', {waitUntil: 'domcontentloaded'})
...

执行优化与状态管理

使用单例浏览器实例

在同一个程序中使用多个爬虫对象时,某些情况下可以选择复用同一个浏览器实例,而不用每启动一个爬虫都new一个browser实例出来。

// instance.js
const pptr = require('puppeteer');
let instance = null;
module.exports.getBrowserInstance = async function() {
  if (!instance)
    instance = await pptr.launch();
  return instance;
}

使用:

const {getBrowserInstance} = require('./instance');

async function doWork() {
  // ....
  const browser = await getBrowserInstance(); // this will reuse single browser
  // ....
}

也可以使用如下简便方法:

let browserInstance = null
const getSingleBrowser = async option => {
    if (!browserInstance) {
        browserInstance && browserInstance.close()
        browserInstance = await puppeteer.launch()
    }
    return browser
}

有一种情况:若同时启动多个爬虫,需要等第一个执行完成后,接下来的任务再一起执行,否则同时执行会启动多个浏览器实例。如下:

async searchHandle() {
    await bing('hello world') // 创建了browser instance
    duckduckgo('hello world') // 使用上面的browser instance
    google('hello world') // 使用上面的browser instance
}

使用Transform Stream掌握爬虫执行进度

若使用promise封装爬虫对象后,想知道爬虫内部执行到哪一步了,可以使用自定义的 Transform Stream 来统一接收状态信息,在electron中使用还可以与渲染进程同步状态信息。

初始化Stream

// main.js
export const statusStream = new Transform({
    // 读写流均开启对象模式
    writableObjectMode: true, 
    readableObjectMode: true,

    transform(chunk, encoding, callback) {
        callback(null, chunk)
    }
})

// 设置编码类型与回调
stream.setEncoding('utf-8')
stream.on('data', chunk => {
    handle_func(chunk) // 处理数据
})

// 若在electron中使用,需要在BrowserWindow创建后进行设置
const initStatusPipe = (stream, win) => {
    stream.setEncoding('utf-8')
    stream.on('data', chunk => {
        // 通过ipc发送给渲染进程
        win.webContents.send(IPC_RENDERER_SIGNAL.MESSAGE, { message: chunk })
    })
}
// 主窗口创建后初始化流
app.on('ready', () => {
  let mainWindow = new BrowserWindow(...)
  ...
  initStatusPipe(statusStream, mainWindow)
})

若在electron中使用,可以在渲染进程中监听对应事件

this.$electron.ipcRenderer.on(IPC_RENDERER_SIGNAL.MESSAGE, (e, arg) => {
    console.log(arg.message)
})

在爬虫中使用stream

传入之前定义的stream对象,使用write方法将状态信息写入stream中。

// crawler.js
const google = (pipe, option) => {
    return new Promise(async(resolve, reject) => {
        try {
            ...
            await page.goto(url, {waitUntil: 'domcontentloaded'})
            pipe.write(`page: open ${url}`)
            ...
            pipe.write(`page: crwaled ${number} results from google`)
            ...
            await page.close()
            pipe.write('page: closed')
            // return results
            resolve(...)
        } catch (err) {
            reject(...)
        }
    })
}

export default google

这样,就可以掌握爬虫内的具体情况了。为了更好的管理爬虫状态,也可以针对情况设计一些传递时的消息格式。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

啊哈!算法

啊哈!算法

啊哈磊 / 人民邮电出版社 / 2014-6-1 / 45.00元

这不过是一本有趣的算法书而已。和别的算法书比较,如果硬要说它有什么特点的话,那就是你能看懂它。 这是一本充满智慧和趣味的算法入门书。没有枯燥的描述,没有难懂的公式,一切以实际应用为出发点, 通过幽默的语言配以可爱的插图来讲解算法。你更像是在阅读一个个轻松的小故事或是在玩一把趣味解谜 游戏,在轻松愉悦中便掌握算法精髓,感受算法之美。 本书中涉及到的数据结构有栈、队列、链表、树......一起来看看 《啊哈!算法》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具