Ritorno
Tech 11 min di lettura - 20 apr. 21 - Nicolas Coulombeau

Scrapare un sito web protetto da ban dell'IP

Nell'ambito di una POC (proof of concept), di un progetto personale o di uno studio statistico, potresti trovarti a dover scraper un sito web. Lo scraping è l'automazione, tramite uno script, del recupero di alcuni dati messi a disposizione da un sito. In questo articolo consideriamo che tu abbia delle nozioni di base di web scraping. 
Essendo la legislazione vaga e la giurisprudenza incerta in termini di scraping, non possiamo che sconsigliarti di praticarlo a fini commerciali ma ti mostreremo comunque come implementare un sistema di scraping con rotazione di IP grazie a Python, Scrapy e Scrapoxy.  

Caso del Client Side Rendering: Sfruttare l'API

Per i siti che funzionano in client side rendering, puoi provare a sfruttare direttamente l'API che restituisce i dati grezzi (spesso in formato JSON o XML) al tuo browser che, a sua volta, si occuperà di iniettarli nell'HTML. Per farlo, puoi guardare (nello strumento di sviluppo del tuo browser nella sezione “network”) le richieste XHR che partono quando aggiorni la pagina web da scrapare. Una volta trovata la o le richieste che caricano i dati che desideri recuperare, non resta che provare a creare la stessa richiesta all'interno di uno script Python (ad esempio utilizzando la libreria request). In questo modo, non è necessario passare attraverso lo scraping vero e proprio (niente headless webdriver, parser, xpath, ecc).
chrome-dev-tool-request-imdb
Qui, ad esempio, sul sito di IMDB si potrebbe provare a sfruttare la loro API GraphQL.
È solo nel caso in cui l'API si riveli troppo difficile da sfruttare che tiro fuori l'artiglieria pesante per scrapare dati visualizzati grazie a Javascript: Selenium.

Caso del Server Side Rendering

Per i siti in server side rendering (e che funzionano senza chiamate API), non hai altra scelta che analizzare l'HTML della pagina tramite una libreria di parsing come Beautiful Soup ed estrarre i dati tramite il loro xpath. 
Se il tuo progetto di scraping è importante, ti consiglio di rivolgerti a Scrapy, un framework Python di crawling e scraping piuttosto completo che ti permette in particolare di testare i tuoi xpath tramite un terminale o di integrare diversi plugin come Splash, il che permette di attivare Javascript sulle pagine web.

Il nostro problema: il blocco degli IP

A volte i siti usano metodi di prevenzione contro gli scraper. Uno dei metodi più temibili che mi ha dato filo da torcere per molto tempo è il blocco degli IP. Il sito scrapato analizza in diretta le richieste che riceve. Se rileva che un IP effettua troppe richieste per essere un utente umano comune, può bloccare questo IP (per una durata più o meno lunga). Così, alla prossima richiesta, il tuo script solleverà un errore dovuto a una risposta 429 del server.

Prima soluzione - inserire dei sleep

Per contrastare questo metodo, la prima soluzione semplice a cui si potrebbe pensare sarebbe quella di inserire dei “sleep” tra le nostre diverse richieste per rallentare l'esecuzione del nostro script e non farsi rilevare dal sito scrapato. Ma supponiamo di avere 30.000 pagine da scrapare con 30 secondi di sleep tra due richieste, ci vorrebbero più di 10 giorni perché lo script arrivi alla fine dell'elenco delle pagine. Questa soluzione funzionerà certamente con un valore di “sleep” abbastanza grande, ma diventerà quindi interminabile nell'esecuzione.

Seconda soluzione - rotazione di IP con Scrapoxy

Un'altra soluzione molto più robusta e veloce nell'esecuzione (poiché non contiene “sleep”) è quella di utilizzare il servizio open source Scrapoxy. Scrapoxy permette di effettuare scraping reindirizzando le richieste attraverso un insieme di proxy. Questi proxy sono istanze di server ospitate dal servizio EC2 di AWS.
scraping-scrapoxy-gif

Riepilogo della soluzione studiata in questo articolo

  • Uno spider (istanza di scraping di Scrapy) si muove di link in link (“crawl” un insieme di pagine)
  • Le richieste di Scrapy verso il server del sito target non vengono effettuate direttamente dalla tua macchina ma vengono reindirizzate verso container EC2 grazie al servizio Scrapoxy.
  • Non appena un container vede il suo IP bloccarsi, Scrapoxy rilancia un nuovo esercito di container per sostituire i vecchi diventati inutilizzabili.

Prerequisiti:

Per seguire questo tutorial, devi avere Python e l'utilità pip installati sulla tua macchina.

Creazione e configurazione dell'account AWS

Creazione dell'account

Se non ne hai già uno, creane uno AWS per poter accedere ai diversi servizi disponibili sulla console AWS. In particolare EC2, il servizio che fornisce server su richiesta e che sarà utilizzato da Scrapoxy per creare i proxy che reindirizzeranno le nostre richieste. Per creare un account si procede qui.
Nota: Dovrai inserire i tuoi dati della carta di credito, quindi cerca di essere vigile sull'utilizzo del servizio EC2 che ha un buon Free Tier (750h di utilizzo al mese cumulato su tutte le tue istanze) ma che non è gratuito indefinitamente. Così se hai 10 istanze EC2 che girano in parallelo, inizierai ad essere fatturato dopo 750 / 10 = 75h. 

Creazione delle credenziali IAM

Affinché Scrapoxy abbia il diritto di gestire per te le tue istanze EC2, devi generare una coppia di chiavi access key e secret access key. Per farlo, vai nella console AWS, nelle tue credenziali di sicurezza (qui)
Espandi la sezione Access Key e clicca su aggiungi una chiave. Tieni da parte il valore dell'access key id. Clicca poi su “Show secret access key” e annota bene il valore che ti dà o scarica il key file.
Attenzione: Non potrai più visualizzarlo in seguito. 

Creazione di un Security Group

Devi anche creare un gruppo di sicurezza. Per farlo, vai nella console AWS sul servizio EC2 e più precisamente sui gruppi di sicurezza (su questo URL, sostituendo il parametro region con la regione che utilizzi: https://console.aws.amazon.com/ec2/v2/home?region=us-east-1#SecurityGroups)
Nota: Ti consigliamo di posizionarti nella regione eu-west-1 
Clicca sul pulsante “Crea un gruppo di sicurezza”, poi:  
  • Immetti “forward-proxy” nei campi nome e descrizione. 
  • Aggiungi una Inbound rule, con: 
    • tipo = TCP personalizzato
    • intervallo di porte = 3128
    • fonte = ovunque

Scelta di un AMI

Più tardi, durante la configurazione di Scrapoxy, avrai bisogno di un “AMI”.
Se ti trovi nella regione eu-west-1, puoi scegliere uno di questi tre AMI a seconda del tipo di istanza che vuoi creare per il tuo script: 
  • t1.micro ⇒ ami-c74d0db4
  • t2.micro ⇒ ami-485fbba5
  • t2.nano ⇒ ami-06220275
Tieni a mente il nome dell'AMI scelto per dopo!
Consiglio: Prova con il primo (ami-c74d0db4), che genera istanze t1.micro (le più piccole possibili). Infatti chiederemo a questi server di fare semplici richieste HTTP; non c'è bisogno di lanciare macchine da guerra.

Installazione e configurazione di Scrapoxy

Installazione di Scrapoxy

Per installare Scrapoxy, inserisci i seguenti comandi in un terminale: sudo apt-get install build-essential sudo npm install -g scrapoxy

Configurazione di Scrapoxy

Per usare Scrapoxy e collegarlo al tuo account AWS, devi generare e compilare un file di configurazione. Inizia inserendo il seguente comando:  scrapoxy init conf.json
Poi nel file conf.json appena generato: 
  • cambia il password nella sezione commander (metti quello che vuoi, questa password servirà per accedere all'interfaccia grafica di Scrapoxy)
  • nella sezione provider, conserva solo l'oggetto con come type : “awsec2”
  • poi sostituisci le accessKeyId e secretAccessKey di questo oggetto con le credentials recuperate poco prima su AWS
  • sostituisci anche il valore dell'AMI (campo ImageId) con il valore corretto
scraping-conf-json
Non hai bisogno di modificare i parametri relativi al numero di istanze EC2, potrai regolarli direttamente tramite l'interfaccia grafica dopo aver lanciato Scrapoxy.

Avvio di Scrapoxy in locale

Per avviare un'istanza di Scrapoxy sulla tua macchina, inserisci in un nuovo terminale il comando: scrapoxy start conf.json -d
Questo terminale deve rimanere aperto per tutta la durata dell'esecuzione dello script che ti presenteremo subito dopo. L'istanza di Scrapoxy avrà il ruolo di distribuire le richieste tra i diversi container EC2, analizzare le risposte e riavviare i container quando una richiesta fallisce.
Nota: Quando Scrapoxy è in esecuzione, puoi accedere a un'interfaccia grafica (GUI) tramite l'indirizzo: http://localhost:8889/. Inoltre, il servizio sarà accessibile per Scrapy tramite l'indirizzo: http://localhost:8888/.

Installazione e utilizzo di Scrapy

Installare Scrapy

Per installare Scrapy con l'utility pip, inserisci il comando: pip install scrapy scrapoxy

Creare un nuovo progetto Scrapy

Per creare un nuovo progetto (una nuova “spider”), inserisci in un terminale il comando:  scrapy startproject myscraper
Questo comando creerà una cartella myscraper che avrà questa struttura: 
scraping-arborescence
Crea una cartella data nella sottocartella myscraper e un file scraper.py nella sottocartella spider per ottenere questa struttura:
scraping-arborescence-fin

Integrare Scrapoxy alla Spider

Per integrare Scrapoxy alla tua spider e affinché le richieste siano fatte dalle tue istanze EC2 piuttosto che dalla tua macchina, in myscraper/myscraper/settings.py, aggiungi le righe: language=python CONCURRENT_REQUESTS_PER_DOMAIN = 1 RETRY_TIMES = 0 # PROXY PROXY = 'http://127.0.0.1:8888/?noconnect' # SCRAPOXY API_SCRAPOXY = 'http://127.0.0.1:8889/api' API_SCRAPOXY_PASSWORD = 'CHANGE_THIS_PASSWORD' # BLACKLISTING BLACKLIST_HTTP_STATUS_CODES = [ 429 ] DOWNLOADER_MIDDLEWARES = {     'scrapoxy.downloadmiddlewares.proxy.ProxyMiddleware': 100,     'scrapoxy.downloadmiddlewares.wait.WaitMiddleware': 101,     'scrapoxy.downloadmiddlewares.scale.ScaleMiddleware': 102,     'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': None,     'scrapoxy.downloadmiddlewares.blacklist.BlacklistDownloaderMiddleware': 950 }
Se il sito che stai scarpando restituisce un codice di stato diverso da 429 per indicare che il tuo IP è stato bloccato, puoi specificarlo aggiungendo il valore del codice di stato restituito dal sito nella variabile BLACKLIST_HTTP_STATUS_CODES.

Scrittura della Spider

Passiamo ora al cuore dello script di scraping: la spider.
Una spider Scrapy è composta da diverse parti: 
  • Importazione dei moduli necessari al funzionamento dello script (come per qualsiasi file Python) 
  • Una classe Scraper: contiene tutta la logica dello scraper
    • Attributi (name, custom_settings, allowed_domains, …)
    • Una prima metodo start_requests() che restituisce un iterabile di scrapy.Requests.
    • Un secondo metodo parse() che sarà chiamato per ogni risposta di scrapy.Request e che ha il compito di analizzare la risposta ed estrarne i dati desiderati. Puoi restituire un iterabile di dizionari, per esempio, per poi salvare le tue risposte in un CSV. 
Ecco un esempio di codice di una spider con, nel metodo parse(), esempi di come recuperare i dati su una pagina analizzata. Per saperne di più, consulta direttamente la documentazione di Scrapy o informati sulla nozione di xpath (qui).
Puoi copiare questo codice nel file scraper.py che hai creato un po' prima. Poi ti basta riprendere il codice per adattarlo al tuo sito di destinazione. language=python import scrapy import csv class Scraper(scrapy.Spider):    name = u'scraper'    custom_settings = {        'FEED_FORMAT': "csv",        'FEED_URI': './data.csv',        'FEED_EXPORT_ENCODING': 'utf-8'    }    allowed_domains = ['www.site-url.com']    start_urls = [f"www.site-url.com/page?id={id}" for id in range(100)]    def start_requests(self):        total = len(self.start_urls)        for i in range(total):            print(f"##############################         {i}/{total}        #############################")            url = self.start_urls[i]            yield scrapy.Request(url, self.parse, meta={"url": url})      def parse(self, response):        # Recupera l'intero HTML di un div (N.B.: copia/incolla il risultato in un editor HTML        # per trovare più facilmente gli xpath dei dati da raccogliere)        data_0 = response.xpath("//div[@id='main-content']").text          # Recupera l'attributo src di un tag img        data_1 = response.xpath("//div[@id='main-content']/div/div/div/div[2]/div/div/div/div/img/@src").extract_first()          # Recupera i titoli delle categorie (titolo presente nell'attributo title del tag category-div)        # e gli id degli elementi presenti nella categoria (id presenti nell'url dell'elemento)        data_2_raw = response.xpath("//category-div")        data_2 = ""        for data_2_item in data_2_raw:            title = data_2_item.xpath("./@title").extract_first()            data_2 += "{}:".format(title)            links = data_2_item.xpath("./div/div/div[2]/a/@href").extract()            for link in links:                data_2 += "{}/".format(link.split("/")[-1].split(".")[0].split("-")[-1])            data_2 = data_2[:-1]        data_2 = data_2[:-1]        yield {            "page_url": response.meta['url'],            "data_0": data_0,            "data_1": data_1,            "data_2": data_2        }

Avvio della Spider

Per avviare la spider, esegui in un terminale nella directory radice del dossier myscraper il comando:   scrapy crawl scraper 

Crawling

Quando avvii la Spider con Scrapoxy attivato sulla tua macchina, dovrai inizialmente attendere due minuti affinché i container EC2 siano avviati. (Puoi disattivare questo tempo di attesa per testare il tuo script più facilmente tramite il file myscraper/myscraper/settings.py commentando la riga language=python 'scrapoxy.downloadmiddlewares.wait.WaitMiddleware': 101)
Poi monitora che tutto si svolga come previsto: una serie di richieste che restituiscono risposte 200 e poi, dopo un certo tempo, una richiesta che fallisce a seguito di una risposta 429 seguita da un sleep di 2-3 minuti per riavviare i container EC2.
Puoi cambiare in qualsiasi momento il numero di istanze EC2 desiderato tramite la GUI di Scrapoxy (normalmente accessibile all'indirizzo http://localhost:8889/). Su questa GUI puoi anche consultare le statistiche delle tue richieste dall'avvio del servizio.
scraping-stats-scrapoxy

Conclusione

Se devi ricordare tre cose da questo articolo, sono le seguenti:
  • Se devi fare scraping dei dati di un sito, non partire direttamente con strumenti di scraping avanzati! Prova inizialmente a sfruttare l'API del sito. Per farlo, usa qualsiasi libreria di richieste HTTP del tuo linguaggio di programmazione preferito.
  • Se non funziona, passa allo scraping più tradizionale (parsing di HTML, headless browser, ...).
  • Se il sito di cui stai facendo scraping blocca il tuo IP quando fai troppe richieste, due possibilità si presentano a te:
    • Se devi fare scraping di un numero ridotto di pagine e/o il tempo di esecuzione dello script non è un problema, prova a inserire dei "sleep" nel tuo codice tra le richieste.
    • Se non funziona o devi fare scraping di troppe pagine perché questa tecnica sia praticabile (tempo di esecuzione dello script troppo lungo), allora, configura Scrapoxy per far ruotare l'IP utilizzato dal tuo script per effettuare le sue richieste.

Desideri essere accompagnato per lanciare il tuo progetto digitale ?

Invia il tuo progetto ora