可爱的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

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


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

查看所有标签

猜你喜欢:

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

Head First Design Patterns

Head First Design Patterns

Elisabeth Freeman、Eric Freeman、Bert Bates、Kathy Sierra、Elisabeth Robson / O'Reilly Media / 2004-11-1 / USD 49.99

You're not alone. At any given moment, somewhere in the world someone struggles with the same software design problems you have. You know you don't want to reinvent the wheel (or worse, a flat tire),......一起来看看 《Head First Design Patterns》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具