Обобщите некоторые советы по использованию 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
Таким образом, вы можете понять конкретную ситуацию в сканере. Чтобы лучше управлять статусом сканера, некоторые форматы сообщений для доставки также могут быть разработаны в соответствии с ситуацией.