内容简介:总结一些使用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
这样,就可以掌握爬虫内的具体情况了。为了更好的管理爬虫状态,也可以针对情况设计一些传递时的消息格式。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。