Симпатичный кукольник использует трюки

Node.js Puppeteer
Симпатичный кукольник использует трюки

Обобщите некоторые советы по использованию puppetter со следующих точек зрения:

  • Запуск браузера и запрос
  • Загрузка страницы и рендеринг
  • Выполнять оптимизацию и управление состоянием

Запуск браузера и запрос

Пользовательский путь хром/хром

Кукловод по умолчанию загрузит внутри модуля хром, который удовлетворяет текущей версии, и иногда загрузка часто не удается из-за сетевых причин, и люди жалуются на средства к существованию.

Начиная с версии 1.7.0, естьpuppeteer-coreЭто легкое решение с использованием puppeteer можно использовать для указания пути хром/хром. Таким образом, вы можете использовать хром, установленный в системе (кукловод будет использовать его внутри).child_process.spawn()запустить подпроцесс, используя указанный исполняемый файл).

Необходимо отметить следующие моменты:

  • При указании хрома в системе нужно обратить внимание соответствует ли его версия требованиям puppetter
  • puppeteer-core не загружает хром автоматически
  • будет игнорировать все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()
})
...

Примечание. Включение перехвата запросов сделает кеш страниц недоступным.

Управляет этапом, на котором объект страницы загружает страницу

установивgotoв функцииwaitUntilпараметр, чтобы сделать страницу вDOMContentLoadedРезультат возвращается при запуске события вместо ожидания события Load, что экономит время ожидания построения дерева рендеринга и отрисовки страницы.

Соответствует CDPPage.lifecycleEvent

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

Выполнять оптимизацию и управление состоянием

Использовать одноэлементный экземпляр браузера

При использовании нескольких объектов искателя в одной и той же программе в некоторых случаях можно выбрать повторное использование одного и того же экземпляра браузера вместо создания нового экземпляра браузера при каждом запуске искателя.

// 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, чтобы управлять ходом выполнения обходчика

Если вы используете обещание для инкапсуляции объекта искателя и хотите знать, какой шаг выполнил искатель внутри, вы можете использовать пользовательскийTransform 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中使用,通过ipc发送给渲染进程
    // win.webContents.send(IPC_RENDERER_SIGNAL.MESSAGE, { message: chunk })
})

// 若在electron中使用,需要在BrowserWindow创建后进行设置
const initStatusPipe = (stream, win) => {
    stream.setEncoding('utf-8')
    stream.on('data', chunk => {
        handle_func(chunk) // 处理数据
        // 若在electron中使用,通过ipc发送给渲染进程
        // win.webContents.send(IPC_RENDERER_SIGNAL.MESSAGE, { message: chunk })
    })
}

app.on('ready', () => {
  let mainWindow = new BrowserWindow(...)
  ...
  initStatusPipe(statusStream, mainWindow)
})

Если используется в электроне, вы можете прослушать соответствующие события в процессе рендеринга

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

Использование потока в сканере

Передайте ранее определенный объект потока и используйте метод записи для записи информации о состоянии в поток.

// 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

Таким образом, вы можете понять конкретную ситуацию в сканере. Чтобы лучше управлять статусом сканера, некоторые форматы сообщений для доставки также могут быть разработаны в соответствии с ситуацией.