Vai al contenuto principale

Gestione di Chrome (puppeteer)

A-Parser + Puppeteer

A-Parser consente di utilizzare il browser Chrome (Chromium) come motore per il download e il rendering delle pagine, utilizzando la popolare libreria puppeteer.

I principali vantaggi dell'utilizzo di puppeteer insieme ad A-Parser:

  • supporto per proxy separati per ogni scheda del browser
  • gestione multithreading delle schede del browser
  • intercettazione delle richieste
  • tutte le funzionalità di A-Parser per la gestione della coda, la formazione delle query e l'elaborazione dei risultati

L'utilizzo del browser Chrome apre le seguenti possibilità:

  • rendering di DOM e JavaScript
  • possibilità di interazione interattiva con gli elementi dei siti:
    • compilazione di moduli
    • navigazione tramite link
    • drag & drop
    • caricamento di file
    • emulazione del mouse
    • e molto altro, è possibile automatizzare qualsiasi azione standard
  • bypass più semplice di varie protezioni anti-scraping, poiché il browser Chromium è il più simile possibile a quello utilizzato dagli utenti
  • possibilità di lavorare in modalità Headless, ovvero senza l'interfaccia grafica del browser, il che consente di risparmiare risorse e di eseguire il browser su server senza ambiente grafico
nota

È necessario tenere presente che il funzionamento del browser è molto più esigente in termini di risorse (CPU, Memoria) rispetto ai normali thread di A-Parser

A seconda della complessità del sito, si raccomanda di utilizzare un numero di thread (schede del browser) non superiore a 1-2 per ogni core del processore disponibile, ad esempio per processori a 8 core - da 8 a 16 schede

Esempio di utilizzo

Analizziamo l'esempio dello scraper Chrome::ScreenshotMaker2:

  • lo scraper scatta screenshot dei siti della dimensione specificata e può anche rimpicciolire (scalare) l'immagine
  • può opzionalmente utilizzare proxy
  • crea una scheda del browser separata per ogni thread di 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() {
// inizializziamo il browser
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) {
// colleghiamo il modulo jimp se è necessario fare il resize dello screenshot
jimp = require('jimp');
};
};

async destroy() {
// chiudiamo il browser al termine del lavoro dell'attività
if (browser)
await browser.close();
}

page: PuppeteerTypes.Page;

async threadInit() {
// creiamo una pagina del browser durante l'inizializzazione del thread
this.page = await browser.newPage();

// metodi standard di puppeteer
await this.page.setCacheEnabled(true);
await this.page.setDefaultNavigationTimeout(this.conf.timeout * 1000);

// indichiamo ad A-Parser di usare il proxy per questa pagina
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}`);

// andiamo alla pagina specificata nella query
await page.goto(set.query);
// nascondiamo la barra di scorrimento per lo screenshot
await page.evaluate(() => { document.querySelector('html').style.overflow = 'hidden'; });

// otteniamo lo screenshot
results.screenshot = await page.screenshot();

if (parseInt(conf.resize_width)) {
// se necessario ridimensioniamo l'immagine
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;

// chiudiamo le connessioni correnti, poiché il browser usa keep-alive
await self.puppeteer.closeActiveConnections();
break;
}
catch (error) {
self.logger.put(`Fetch page error: ${error}`);
// chiudiamo le connessioni correnti, poiché il browser usa keep-alive
await self.puppeteer.closeActiveConnections();
// cambiamo il proxy per la scheda del browser
await self.proxy.next();
}
}

return results;
}
}

Questo esempio dimostra la semplicità di utilizzare diversi proxy per ogni scheda, così come il lavoro multithreading (1 thread = 1 scheda del browser)

Descrizione dei metodi

await this.puppeteer.launch(opts?)

Questo metodo è analogo al metodo .launch della libreria puppeteer, avvia il browser Chromium con le opzioni opts necessarie. La differenza principale consiste nell'integrazione con A-Parser e nel supporto dei proxy per ogni scheda, oltre alla presenza di opzioni aggiuntive:

logConnections?: boolean

Abilita il logging di tutte le connessioni (indipendentemente dall'uso del proxy o meno), l'output del log viene effettuato separatamente per thread

stealth?: boolean

Utilizza il plugin puppeteer-extra per mascherare Chromium come un vero Chrome

stealthOpts?: any

Opzioni aggiuntive per il plugin puppeteer-extra

extraPlugins?: array

Utilizzo di plugin aggiuntivi, come ad esempio puppeteer-extra/packages

A-Parser + Puppeteer

Altre opzioni

Tutte le altre opzioni di avvio possono essere consultate nella documentazione originale di puppeteer

await this.puppeteer.setPageUseProxy(page)

Questo metodo collega la pagina del browser con il thread di A-Parser per il corretto funzionamento del proxy, deve essere chiamato subito dopo la creazione della pagina:

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

await this.puppeteer.closeActiveConnections(page?)

Questo metodo deve essere chiamato dopo il completamento dell'elaborazione della query o prima di cambiare il proxy per elaborare il tentativo successivo

Il browser Chrome lascia per impostazione predefinita aperte le connessioni con i siti a cui si collega, questo metodo consente di controllare il numero di risorse utilizzate e riduce il carico sui proxy

L'argomento page è opzionale; se chiamato senza argomento, A-Parser chiuderà le connessioni per la scheda associata al thread corrente

await this.puppeteer.logScreenshot()

Il metodo registra lo screenshot della pagina corrente