Vai al contenuto principale

Richieste HTTP (+gestione cookie, proxy, sessioni)

Metodi della classe base

Per raccogliere dati da una pagina web è necessario eseguire una richiesta HTTP. In JavaScript API v2 di A-Parser è implementato un metodo di facile utilizzo per l'esecuzione di richieste HTTP, che restituisce come risposta un oggetto JSON a seconda degli argomenti specificati del metodo. Di seguito scoprirai: come viene effettuata una richiesta HTTP, quali argomenti e opzioni ha il metodo, i risultati delle opzioni specificate, come specificare la condizione di successo di una richiesta HTTP e altro ancora.

Vengono inoltre descritti i metodi che consentono di manipolare facilmente cookie, proxy e sessioni nello scraper creato. Dopo l'esecuzione con successo di una richiesta HTTP, o prima dell'esecuzione, è possibile impostare/modificare i dati di proxy/cookie/sessione per l'esecuzione delle richieste HTTP o salvarli per l'esecuzione da parte di un altro thread utilizzando il Gestore sessioni.

Questi metodi sono ereditati da BaseParser e costituiscono la base per la creazione di scraper personalizzati

await this.request(method, url[, queryParams][, opts])

await this.request(method, url, queryParams, opts)

Ottenimento di una risposta HTTP su richiesta, come argomenti si specifica:

  • method - metodo della richiesta (GET, POST...)
  • url - link per la richiesta
  • queryParams - hash con parametri get o hash con il corpo della richiesta post
  • opts - hash con le opzioni della richiesta

opts.check_content

check_content: [ condizione1, condizione2, ...] - array di condizioni per verificare il contenuto ricevuto; se la verifica non va a buon fine, la richiesta verrà ripetuta con un altro proxy.

Possibilità:

  • utilizzo di stringhe come condizioni (ricerca per occorrenza della stringa)
  • utilizzo di espressioni regolari come condizioni
  • utilizzo di proprie funzioni di verifica, a cui vengono passati i dati e gli header della risposta
  • è possibile impostare contemporaneamente diversi tipi di condizioni
  • per la negazione logica, inserire la condizione in un array, ovvero check_content: ['xxxx', [/yyyy/]] significa che la richiesta sarà considerata riuscita se i dati ricevuti contengono la sottostringa xxxx e allo stesso tempo l'espressione regolare /yyyy/ non trova corrispondenze nella pagina

Per una richiesta riuscita, devono passare tutte le verifiche specificate nell'array

Esempio (nei commenti è indicato cosa serve affinché la richiesta sia considerata riuscita):

let response = await this.request('GET', set.query, {}, {
check_content: [
/<\/html>|<\/body>/, // questa espressione regolare deve corrispondere nella pagina ricevuta
['XXXX'], // questa sottostringa non deve essere presente nella pagina ricevuta
'</html>', // questa sottostringa deve essere presente nella pagina ricevuta
(data, hdr) => {
return hdr.Status == 200 && data.length > 100;
} // questa funzione deve restituire true
]
});

opts.decode

decode: 'auto-html' - rilevamento automatico della codifica e conversione in utf8

Valori possibili:

  • auto-html - basato su header, tag meta e contenuto della pagina (opzione consigliata ottimale)
  • utf8 - indica che il documento è in codifica utf8
  • <encoding> - qualsiasi altra codifica

opts.headers

headers: { ... } - hash con gli header, il nome dell'header va specificato in minuscolo, è possibile indicare anche i cookie.

Esempio:

headers: {
accept: 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
cookie: 'a=321; b=test',
'user-agent' 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
}

opts.headers_order

headers_order: ['cookie', 'user-agent', ...] - consente di ridefinire l'ordine di ordinamento degli header

opts.onlyheaders

onlyheaders: 0 - determina la lettura di data, se abilitato (1), riceve solo gli header

opts.recurse

recurse: N - numero massimo di passaggi per i redirect, predefinito 7, usa 0 per disabilitare il passaggio per i redirect

opts.proxyretries

proxyretries: N - numero di tentativi di esecuzione della richiesta, per impostazione predefinita viene preso dalle impostazioni dello scraper

opts.parsecodes

parsecodes: { ... } - elenco dei codici di risposta HTTP che lo scraper considererà riusciti, per impostazione predefinita viene preso dalle impostazioni dello scraper. Se si specifica '*': 1 allora tutte le risposte saranno considerate riuscite.

Esempio:

parsecodes: {
200: 1,
403: 1,
500: 1
}

opts.timeout

timeout: N - timeout della risposta in secondi, per impostazione predefinita viene preso dalle impostazioni dello scraper

opts.do_gzip

do_gzip: 1 - determina se utilizzare la compressione (gzip/deflate/br), per impostazione predefinita abilitato (1), per disabilitare è necessario impostare il valore 0

opts.max_size

max_size: N - dimensione massima della risposta in byte, per impostazione predefinita viene preso dalle impostazioni dello scraper

opts.cookie_jar

cookie_jar: { ... } - hash con i cookie. Esempio di hash:

"cookie_jar": {
"version": 1,
".google.com": {
"/": {
"login": {
"value": "true"
},
"lang": {
"value": "ru-RU"
}
}
},
".test.google.com": {
"/": {
"id": {
"value": 155643
}
}
}

opts.attempt

attempt: N - indica il numero del tentativo corrente, quando si utilizza questo parametro il gestore dei tentativi integrato per questa richiesta viene ignorato

opts.browser

browser: 1 - emulazione automatica degli header del browser (1 - abilitato, 0 - disabilitato)

opts.use_proxy

use_proxy: 1 - ridefinisce l'uso del proxy per una singola richiesta all'interno dello scraper JS rispetto al parametro globale Use proxy (1 - abilitato, 0 - disabilitato)

opts.noextraquery

noextraquery: 0 - disabilita l'aggiunta di Extra query string all'url della richiesta (1 - abilitato, 0 - disabilitato)

opts.save_to_file

save_to_file: file - consente di scaricare un file direttamente su disco, evitando la scrittura in memoria. Al posto di file si specifica il nome e il percorso con cui salvare il file. Quando si utilizza questa opzione, viene ignorato tutto ciò che riguarda data (la verifica del contenuto in opts.check_content non verrà eseguita, response.data sarà vuoto, ecc.)

opts.bypass_cloudflare

bypass_cloudflare: 0 - bypass automatico della protezione JavaScript di CloudFlare utilizzando il browser Chrome (1 - abilitato, 0 - disabilitato)

Il controllo di Chrome Headless in questo caso è gestito dalle impostazioni dello scraper bypassCloudFlareChromeMaxPages e bypassCloudFlareChromeHeadless, che devono essere specificate in static defaultConf e static editableConf:

static defaultConf: typeof BaseParser.defaultConf = {
version: '0.0.1',
results: {
flat: [
['title', 'Title'],
]
},
max_size: 2 * 1024 * 1024,
parsecodes: {
200: 1,
},
results_format: "$title\n",
bypass_cloudflare: 1,
bypassCloudFlareChromeMaxPages: 20,
bypassCloudFlareChromeHeadless: 0
};

static editableConf: typeof BaseParser.editableConf = [
['bypass_cloudflare', ['textfield', 'bypass_cloudflare']],
['bypassCloudFlareChromeMaxPages', ['textfield', 'bypassCloudFlareChromeMaxPages']],
['bypassCloudFlareChromeHeadless', ['textfield', 'bypassCloudFlareChromeHeadless']],
];

async parse(set, results) {
const {success, data, headers} = await this.request('GET', set.query, {}, {
bypass_cloudflare: this.conf.bypass_cloudflare
});
return results;
}

opts.follow_meta_refresh

follow_meta_refresh: 0 - consente di seguire i redirect dichiarati tramite il meta tag HTML:

<meta http-equiv="refresh" content="time; url=..."/>

opts.redirect_filter

redirect_filter: (hdr) => 1 | 0 - consente di impostare una funzione di filtraggio per il passaggio dei redirect; se la funzione restituisce 1, lo scraper passerà al redirect (considerando il parametro opts.recurse), se restituisce 0 il passaggio per i redirect si interromperà:

redirect_filter: (hdr) => {
if (hdr.location.match(/login/))
return 1;
return 0;
}

opts.follow_common_rediects

opts.follow_common_rediects: 0 - determina se seguire i redirect standard (ad esempio http -> https e/o www.domain.com -> domain.com); se si specifica 1 lo scraper seguirà i redirect standard senza considerare il parametro opts.recurse

opts.http2

opts.http2: 0 - determina se utilizzare il protocollo HTTP/2 durante l'esecuzione delle richieste; per impostazione predefinita viene utilizzato HTTP/1.1

opts.randomize_tls_fingerprint

opts.randomize_tls_fingerprint: 0 - questa opzione consente di bypassare il ban dei siti tramite l'impronta TLS (1 - abilitato, 0 - disabilitato)

opts.tlsOpts

tlsOpts: { ... } – consente di passare le impostazioni per le connessioni https ​

await this.cookies.*

Lavoro con i cookie per la richiesta corrente

.getAll()

Ottenimento di un array di cookie

await this.cookies.getAll();
Esempio del risultato dell'ottenimento di un array di cookie

.setAll(cookie_jar)

Impostazione dei cookie, come argomento deve essere passato un hash con i cookie

async parse(set, results) {
this.logger.put("Start scraping query: " + set.query);

await this.cookies.setAll({
"version": 1,
".google.com": {
"/": {
"login": {
"value": "true"
},
"lang": {
"value": "ru-RU"
}
}
},
".test.google.com": {
"/": {
"id": {
"value": 155643
}
}
}
});

let cookies = await this.cookies.getAll();

this.logger.put("Cookies: " + JSON.stringify(cookies));

results.SKIP = 1;
return results;
}
Esempio del risultato dell'impostazione di un array di cookie

.set(host, path, name, value)

await this.cookies.set(host, path, name, value) - impostazione di un singolo cookie.

L'ambito di visibilità del cookie dipende direttamente dal formato del dominio specificato, pertanto in host si tiene conto della presenza del punto prima dell'host:

  • se il punto è specificato (this.cookies.set('.domain.com', ...)), il cookie verrà utilizzato per tutti i sottodomini (ad esempio a.domain.com, b.a.domain.com)
  • se l'host è specificato senza punto davanti (this.cookies.set('site.com', ...)), il cookie verrà utilizzato rigorosamente per l'host specificato (host-only cookie) e non viene trasmesso ai sottodomini
informazione

Questa differenza è di fondamentale importanza, poiché l'esistenza simultanea di cookie con e senza punto può portare alla loro duplicazione e a un comportamento imprevedibile del sito. Per una corretta emulazione, controlla sempre come esattamente il sito di destinazione imposta i cookie (con l'attributo Domain o senza) e utilizza il formato corrispondente.

async parse(set, results) {
this.logger.put("Start scraping query: " + set.query);

await this.cookies.set('.a-parser.com', '/', 'Test-cookie-1', 1);
await this.cookies.set('.a-parser.com', '/', 'Test-cookie-2', 'test-value');

let cookies = await this.cookies.getAll();

this.logger.put("Cookies: " + JSON.stringify(cookies));

results.SKIP = 1;
return results;
}
Esempio del risultato dell'impostazione di un singolo cookie

await this.proxy.*

Lavoro con i proxy

.next()

Cambia il proxy con il successivo, il vecchio proxy non verrà più utilizzato per la richiesta corrente

.ban()

Cambia e banna il proxy (necessario quando il servizio blocca l'attività per IP), il proxy verrà bannato per il tempo indicato nelle impostazioni dello scraper (proxybannedcleanup)

.get()

Ottieni il proxy corrente (l'ultimo proxy con cui è stata effettuata la richiesta)

.set(proxy, noChange?)

await this.proxy.set('http://127.0.0.1:8080', true) - imposta il proxy per la richiesta successiva. Il parametro noChange è facoltativo; se impostato su true, il proxy non cambierà tra i tentativi. Per impostazione predefinita noChange = false

await this.sessionManager.*

Metodi per lavorare con le sessioni. Ogni sessione memorizza obbligatoriamente il proxy e i cookie utilizzati. È inoltre possibile salvare dati arbitrari aggiuntivi. Per utilizzare le sessioni nello scraper JS, è prima necessario inizializzare il Gestore sessioni. Questo viene fatto utilizzando il metodo await this.sessionManagerinit() in init()

.init(opts?)

Inizializzazione del Gestore sessioni. Come argomento è possibile passare un oggetto (opts) con parametri aggiuntivi (tutti i parametri sono facoltativi):

  • name - consente di ridefinire il nome dello scraper a cui appartengono le sessioni, per impostazione predefinita è uguale al nome dello scraper in cui avviene l'inizializzazione
  • waitForSession - indica allo scraper di attendere una sessione finché non appare (questo è rilevante solo quando sono in esecuzione più task, ad esempio uno genera sessioni, il secondo le utilizza), ovvero .get() e .reset() aspetteranno sempre una sessione
  • domain - indica di cercare le sessioni tra tutte quelle salvate per questo scraper (se il valore non è impostato), o solo per un dominio specifico (è necessario specificare il dominio con un punto davanti, ad esempio .site.com)
  • sessionsKey - consente di impostare manualmente il nome del database delle sessioni; se non impostato, il nome viene formato automaticamente in base a name (o al nome dello scraper se name non è impostato), al dominio e al proxychecker
  • expire - imposta il tempo di vita della sessione in minuti, per impostazione predefinita illimitato

Esempio di utilizzo:

async init() {
await this.sessionManager.init({
name: 'JS::test',
expire: 15 * 60
});
}

.get(opts?)

Ottenimento di una nuova sessione, deve essere chiamato prima di effettuare la richiesta (prima del primo tentativo). Restituisce un oggetto con dati arbitrari salvati nella sessione. Come argomento è possibile passare un oggetto (opts) con parametri aggiuntivi (tutti i parametri sono facoltativi):

  • waitTimeout - possibilità di specificare quanti minuti attendere la comparsa di una sessione, funziona indipendentemente dal parametro waitForSession in .init() (lo ignora); alla scadenza verrà utilizzata una sessione vuota
  • tag - ottenimento di una sessione con un tag specificato; è possibile utilizzare ad esempio il nome del dominio per collegare le sessioni ai domini da cui sono state ottenute

Esempio di utilizzo:

await this.sessionManager.get({
waitTimeout: 10,
tag: 'test session'
})

.reset(opts?)

Pulizia dei cookie e ottenimento di una nuova sessione. Deve essere utilizzato se con la sessione corrente la richiesta non è andata a buon fine. Restituisce un oggetto con dati arbitrari salvati nella sessione. Come argomento è possibile passare un oggetto (opts) con parametri aggiuntivi (tutti i parametri sono facoltativi):

  • waitTimeout - possibilità di specificare quanti minuti attendere la comparsa di una sessione, funziona indipendentemente dal parametro waitForSession in .init() (lo ignora); alla scadenza verrà utilizzata una sessione vuota
  • tag - ottenimento di una sessione con un tag specificato; è possibile utilizzare ad esempio il nome del dominio per collegare le sessioni ai domini da cui sono state ottenute

Esempio di utilizzo:

await this.sessionManager.reset({
waitTimeout: 5,
tag: 'test session'
})

.save(sessionOpts?, saveOpts?)

Salvataggio di una sessione riuscita con la possibilità di salvare dati arbitrari nella sessione. Supporta 2 argomenti facoltativi:

  • sessionOpts - dati arbitrari da memorizzare nella sessione, può essere un numero, una stringa, un array o un oggetto
  • saveOpts - oggetto con i parametri di salvataggio della sessione:
    • multiply - parametro facoltativo, consente di moltiplicare la sessione; come valore è necessario indicare un numero
    • tag - parametro facoltativo, imposta un tag per la sessione salvata; è possibile utilizzare ad esempio il nome del dominio per collegare le sessioni ai domini da cui sono state ottenute

Esempio di utilizzo:

await this.sessionManager.save('some data here', {
multiply: 3,
tag: 'test session'
})

.count()

Restituisce il numero di sessioni per il Gestore sessioni corrente

Esempio di utilizzo:

let sesCount = await this.sessionManager.count();

.removeById(sessionId)

Elimina tutte le sessioni con un determinato id. Restituisce il numero di sessioni eliminate. L'id della sessione corrente è contenuto nella variabile this.sessionId Esempio di utilizzo:

const removedCount = await this.sessionManager.removeById(this.sessionId);

Esempio complesso di utilizzo del Gestore sessioni

async init() {
await this.sessionManager.init({
expire: 15 * 60
});
}

async parse(set, results) {
let ses = await this.sessionManager.get();

for(let attempt = 1; attempt <= this.conf.proxyretries; attempt++) {
if(ses)
this.logger.put('Data from session:', ses);
const { success, data } = await this.request('GET', set.query, {}, { attempt });
if(success) {
// process data here
results.success = 1;
break;
} else if(attempt < this.conf.proxyretries) {
const removedCount = await this.sessionManager.removeById(this.sessionId);
this.logger.put(`Removed ${removedCount} bad sessions with id #${this.sessionId}`);
ses = await this.sessionManager.reset();
}
}

if(results.success) {
await this.sessionManager.save('Some data', { multiply: 2 });
this.logger.put(`Total we have ${await this.sessionManager.count()} sessions`);
}

return results;
}
Esempio di salvataggio di dati arbitrari e del loro successivo ottenimento

Metodi di richiesta await this.request

Metodo GET

È possibile passare i parametri della richiesta direttamente nella stringa di richiesta https://a-parser.com/users/?type=staff:

const { success, data, headers } = await this.request('GET', 'https://a-parser.com/users/?type=staff');

Oppure come oggetto in queryParams, dove key: value è uguale a param=value:

const { success, data, headers } = await this.request('GET', 'https://a-parser.com/users/', {
type: 'staff'
});

Metodo POST

Se viene utilizzato il metodo POST, il corpo della richiesta può essere passato in due modi:

  • Elencare i nomi delle variabili e i loro valori in queryParams, ad esempio:

    {
    "key": set.query,
    "id": 1234,
    "type": "text"
    }
  • Elencarli in opts.body, ad esempio:

    body: 'key=' + set.query + '&id=1234&type=text'

Se il corpo della richiesta viene passato come oggetto, viene automaticamente convertito nel formato form-urlencoded; inoltre, se viene specificato body e non viene specificato l'header content-type, verrà assegnato automaticamente content-type: application/x-www-form-urlencoded: Se il corpo della richiesta POST è una stringa o un buffer, viene trasmesso così com'è:

const { success, data, headers } = await this.request('POST', 'https://jsonplaceholder.typicode.com/posts', {
title: 'foo,',
body: 'bar',
userId: 1
});

Caricamento file

// richiesta con stringa
const string = 'title=foo&body=bar&userId=1';
const { success, data, headers } = await this.request('POST', 'https://jsonplaceholder.typicode.com/posts', {}, {
body: string
});

// richiesta con buffer
const string = 'title=foo&body=bar&userId=1';
const buf = Buffer.from(string, 'utf8');
const { success, data, headers } = await this.request('POST', 'https://jsonplaceholder.typicode.com/posts', {}, {
body: buf
});

Caricamento dei file

Invio di un file tramite richiesta POST utilizzando il modulo form-data:

const file = fs.readFileSync('pathToFile');
const FormData = require('form-data');
const format = new FormData();
format.append('file', file, 'fileName.ext');

const { success, data, headers } = await this.request('POST', 'https://file.io', {}, {
headers: format.getHeaders(),
body: format.getBuffer()
});

Esempio di invio di un file in una richiesta POST con tipo di contenuto multipart/form-data:

const EOL = '\r\n';
const file = fs.readFileSync('pathToFile');
const boundary = '----WebKitFormBoundary' + String(Math.random()).slice(2);
const requestHeaders = {
'content-type': 'multipart/form-data; boundary=' + boundary
};

const body = '--'
+ boundary
+ EOL
+ 'Content-Disposition: form-data; name="file"; filename="fileName.ext"'
+ EOL
+ 'Content-Type: text/html'
+ EOL
+ EOL
+ file
+ EOL
+ '--'
+ boundary
+ '--';

const { success, data, headers } = await this.request('POST', 'https://file.io', {}, {
headers: requestHeaders,
body
});