Перейти к основному содержимому

Управление Chrome (puppeteer)

A-Parser + Puppeteer

A-Parser позволяет использовать браузер Chrome (Chromium) в качестве движка для скачивания и рендеринга страниц, используя популярную библиотеку puppeteer.

Основные преимущества работы puppeteer совместно с A-Parser:

  • поддержка раздельных прокси для каждой вкладки браузера
  • многопоточное управление вкладками браузера
  • перехват запросов
  • все возможности A-Parser по управлению очередью, фомированию запросов и обработке результатов

Использование браузера Chrome открывает следующие возможности:

  • рендеринг DOM и JavaScript
  • возможность интерактивной работы с элементами сайтов:
    • заполнение форм
    • переходы по ссылкам
    • drag & drop
    • загрузка файлов
    • эмуляция мыши
    • и многое другое, автоматизировать можно любые стандартные действия
  • более простой обход разнообразных защит от парсинга, т.к. браузер Chromium максимально похож на используемый пользователями
  • возможность работы в Headless режиме, т.е. без графической браузера, что позволяет экономить ресурсы, а также запускать браузер на серверах без графического окружения
примечание

Необходимо учитывать, что работа браузера намного более требовательна к ресурсам (CPU, Memory), чем обычные потоки A-Parser

В зависимости от сложности сайта, рекомендуется использовать количество потоков (вкладок браузера) не более чем 1-2 на каждое доступное ядро процессора, например для 8 ядерных процессоров - от 8 до 16 вкладок

Пример использования

Разберем на примере парсера Chrome::ScreenshotMaker2:

  • парсер делает скриншоты сайтов указанного размера, а также может уменьшать (масштабировать) картинку
  • может опционально использовать прокси
  • создает отдельную вкладку браузера на каждый поток A-Parser'а
import { BaseParser, PuppeteerTypes } from 'a-parser-types';

let browser: PuppeteerTypes.Browser;
let jimp;

class JS_Chrome_ScreenshotsMaker2 extends BaseParser {
static defaultConf: typeof BaseParser.defaultConf = {
version: '0.2.1',
results: {
flat: [
['screenshot', 'PNG screenshot'],
]
},
results_format: '$screenshot',
load_timeout: 30,
width: 1024,
height: 768,
log_screenshots: 0,
headless: 1,
};

static editableConf: typeof BaseParser.editableConf = [
['log_screenshots', ['checkbox', 'Log Screenshots']],
['width', ['textfield', 'Viewport Width']],
['height', ['textfield', 'Viewport Height']],
['resize_width', ['textfield', 'Resize Width']],
['resize_height', ['textfield', 'Resize Height']],
['headless', ['checkbox', 'Chrome Headless']],
];

async init() {
// инициализируем браузер
browser = await this.puppeteer.launch({
headless: this.conf.headless,
logConnections: false,
defaultViewport: {
width: parseInt(this.conf.width),
height: parseInt(this.conf.height),
}
});

if (this.conf.resize_width) {
// подключаем модуль jimp если необходимо делать resize скриншота
jimp = require('jimp');
};
};

async destroy() {
// закрываем браузер по завершению работы задания
if (browser)
await browser.close();
}

page: PuppeteerTypes.Page;

async threadInit() {
// создаем страницу браузера при инициализауии потока
this.page = await browser.newPage();

// стандартные методы puppeteer
await this.page.setCacheEnabled(true);
await this.page.setDefaultNavigationTimeout(this.conf.timeout * 1000);

// указываем A-Parser использовать прокси для данной страницы
await this.puppeteer.setPageUseProxy(this.page);


this.logger.put(`New page created for thread #${this.threadId}`);
}

async parse(set, results) {
const self = this;
const { conf, page } = self;

for (let attempt = 1; attempt <= conf.proxyretries; attempt++) {
try {
self.logger.put(`Attempt #${attempt}`);

// переходим на страницу, указанную в запросе
await page.goto(set.query);
// скрываем скролл бар для скриншота
await page.evaluate(() => { document.querySelector('html').style.overflow = 'hidden'; });

// получаем скриншот
results.screenshot = await page.screenshot();

if (parseInt(conf.resize_width)) {
// при необходимости ресайзим изображение
let image = await jimp.read(results.screenshot);
image.resize(parseInt(conf.resize_width), parseInt(conf.resize_height));
results.screenshot = await image.getBufferAsync('image/png');
}

self.logger.put(`Screenshot(${attempt}): OK, size: ${parseInt("" + (results.screenshot.length / 1024))}KB`);
if (conf.log_screenshots)
self.logger.putHTML("<img src='data:image/png;base64," + results.screenshot.toString('base64') + "'>");

results.success = 1;

// закрываем текущие соединения, т.к. браузер использует keep-alive
await self.puppeteer.closeActiveConnections();
break;
}
catch (error) {
self.logger.put(`Fetch page error: ${error}`);
// закрываем текущие соединения, т.к. браузер использует keep-alive
await self.puppeteer.closeActiveConnections();
// меняем прокси для вкладки браузера
await self.proxy.next();
}
}

return results;
}
}

Данный пример демонстрирует простоту использования разных прокси для каждой вкладки, а также многопоточную работу (1 поток = 1 вкладка браузера)

Описание методов

await this.puppeteer.launch(opts?)

Данный метод аналогичен методу .launch библиотеки puppeteer, он запускает браузер Chromium с необходимыми опциями opts. Основное отличие состоит в интегации с A-Parser и поддержки прокси для каждой вкладки, а также в наличии дополнительных опций:

logConnections?: boolean

Включает логирование всех подключений (независимо с использованием прокси или без), вывод лога осуществляется раздельно по потокам

stealth?: boolean

Использует плагин puppeteer-extra для маскировки Chromium под настоящий Chrome

stealthOpts?: any

Дополнительные опции для плагина puppeteer-extra

extraPlugins?: array

Использование дополнительных плагинов, как например puppeteer-extra/packages

A-Parser + Puppeteer

Остальные опции

Все остальные опции запуска можно посмотреть в оригинальной документации puppeteer

await this.puppeteer.setPageUseProxy(page)

Данный метод связывает страницу браузера с потоком A-Parser для корректной работы прокси, его необходимо вызывать сразу после создания страницы:

const page = await browser.newPage();
await this.puppeteer.setPageUseProxy(page);

await this.puppeteer.closeActiveConnections(page?)

Данный метод необходимо вызывать после завершения обработки запроса или перед сменой прокси для обработки следующий попытки

Браузер Chrome по умолчанию оставляет открытые соединения с сайтами, к которым подключается, данный метод позволяет контролировать число используемых ресурсов, а также снижает нагрузку на прокси

Аргумент page является опциональным, при вызове без аргумента A-Parser закроет подключения для вкладки, связанной с текущим потоком

await this.puppeteer.logScreenshot()

Метод логирует скриншот текущей страницы