Introduzione al modulo

Scrapy è un modulo Python open-source per il web scraping. È stato progettato per estrarre i dati da siti web in modo efficiente e rapido. Scrapy offre una serie di funzionalità avanzate per il web scraping, tra cui la gestione delle richieste, la navigazione del sito, la selezione dei dati tramite XPath o CSS selector e la gestione dei cookie.

Il modulo Scrapy utilizza una struttura ad albero per rappresentare il contenuto di una pagina web. Ciò consente agli sviluppatori di selezionare facilmente i dati desiderati utilizzando i selettori CSS e XPath. Inoltre, Scrapy fornisce una serie di metodi per navigare il sito web come segue: seguire i link, inviare form e gestire i cookie.

Scrapy è anche progettato per essere utilizzato in modo scalabile, è in grado di gestire grandi quantità di dati e di supportare il parallelismo. Il modulo supporta anche la persistenza dei dati, consentendo di salvare i dati estratti in un formato specifico, come CSV, JSON o XML.

Per creare un progetto con Scrapy, è necessario creare un nuovo progetto utilizzando il comando scrapy startproject, quindi creare un nuovo spider utilizzando il comando scrapy genspider. In seguito si definiscono le regole di scraping nella funzione parse() del proprio spider.

In sintesi Scrapy è uno strumento molto potente per estrarre i dati dal web, grazie alla sua flessibilità e alle sue funzionalità avanzate, è possibile creare spider in grado di estrarre dati da siti web complessi e navigare attraverso più pagine.

Disclaimer: ⚠️ Il web scraping è una pratica legale a meno che non violi le condizioni d’uso del sito web in questione. Il presente software è stato creato per scopi educativi e di ricerca e non deve essere utilizzato per violare le leggi o i termini d’uso di un sito web. L’utente è responsabile dell’utilizzo del software e degli eventuali danni causati dall’utilizzo improprio. ⚠️

Per installare e configurare uno progetto con il modulo scrapy possiamo usare i seguenti step:

Installare Scrapy: è possibile farlo utilizzando il comando seguente nella riga di comando:

pip install scrapy

Oppure per Python3:

pip3 install scrapy

Creare un nuovo progetto: Per creare un progetto è possibile utilizzare il comando nella riga di comando:

scrapy startproject <nome_progetto>

Dove <nome_progetto> è il nome del progetto che si desidera creare. Questo comando creerà una nuova cartella con il nome del progetto, che conterrà i file di configurazione e la struttura del progetto.

Creare un nuovo spider: utilizzare il comando seguente nella riga di comando:

scrapy genspider <nome_spider> <dominio>

Dove <nome_spider> è il nome del spider che si desidera creare e è il dominio del sito web da cui si desidera estrarre i dati. Questo comando creerà un nuovo file spider nella cartella “spiders” del progetto. Il comando richiede il nome dello spider e l’URL di partenza per lo spider. Esempio:

scrapy genspider example example.com

Questo creerà un file chiamato example.py all’interno della cartella spiders del tuo progetto, con una classe di spider chiamata ExampleSpider che inizia a estrarre i dati dall’URL example.com.

Una volta creato lo spider, è possibile modificare il codice per soddisfare le esigenze del progetto. In seguito, lo spider può essere eseguito utilizzando il comando:

scrapy spiderun <nome_spider>.py

Extra: ExampleSpider è il nome della classe dello spider generato automaticamente dal comando scrapy genspider con il nome che gli hai dato quando hai creato lo spider.

La classe estende la classe base scrapy.Spider e include una serie di proprietà e metodi predefiniti che puoi utilizzare per configurare e eseguire lo spider.

Per esempio, nella classe è presente il nome dello spider name e gli url di partenza start_urls che specificano l’indirizzo web a cui si vuole fare scraping, la funzione di callback parse() che viene chiamata quando lo spider recupera una pagina web.

L’idea è che tu possa modificare questa classe e adattarlo alle tue esigenze specifiche, aggiungendo eventuali selettori CSS o XPath, gestione dei cookies, trattamento dei dati, ecc.

Modificare il codice del spider: aprire il file spider appena creato e modificare il codice per adattarlo alle esigenze del progetto. È possibile utilizzare il metodo start_requests() per specificare la URL iniziale da cui iniziare a estrarre i dati, il metodo parse() per specificare come estrarre i dati dalle pagine web, e il metodo parse_item() per specificare come estrarre i dati da un singolo elemento della pagina web.

Eseguire lo spider: Come citato in precedenza è possibile utilizzare il comando seguente nella riga di comando per eseguire lo spider:

scrapy spiderun <nome_spider>.py

Dove <nome_spider> è il nome del spider creato in precedenza. In questo modo, verranno stampati i dati estratti nella console o salvati in un file, a seconda delle impostazioni specificate nel codice. Per eseguire lo spider bisogna trovarsi nella directory spiders.

Ricordati che è possibile utilizzare anche il comando

scrapy shell <url>

Questo per testare il codice del tuo spider su una singola pagina web, prima di lanciare la scansione sull’intero sito.

Di seguito vediamo degli esempi usando come URL: http://books.toscrape.com/ che viene usato appositamente per il Web Scraping.

Nota: durante l’esecuzione del codice possono esserci errori per la mancanza del modulo attrs, per installarlo usiamo il comando:

pip install attrs

Oppure per Python3:

pip3 install attrs

Se abbiamo già questo modulo, possiamo aggiornarlo con il comando:

pip install --upgrade attrs

Esempi Pratici

Estrazione del titolo di una pagina web

import scrapy

class TitleSpider(scrapy.Spider):
    name = "titlespider"
    start_urls = [
        "http://books.toscrape.com/catalogue/category/books/science_22/index.html",
    ]

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "title": book.css("h3 > a::text").get(),
            }

Questo codice crea uno spider chiamato TitleSpider che estende la classe base scrapy.Spider. Nella classe, viene definito un nome per lo spider e una lista di URL di partenza per lo spider. Nel nostro caso, l’URL di partenza è una pagina web di un negozio di libri online.

La funzione parse() è chiamata ogni volta che lo spider recupera una pagina web. All’interno della funzione parse(), utilizziamo un ciclo for per scorrere tutti gli elementi HTML che corrispondono al selettore article.product_pod. Questo selettore seleziona tutti gli elementi < article > con la classe product_pod nella pagina web.

Per ogni elemento selezionato, utilizziamo il selettore h3 > a::text per selezionare il testo del primo elemento < a > all’interno dell’elemento < h3 > all’interno dell’elemento < article >. Il testo selezionato rappresenta il titolo del libro. Utilizziamo la funzione get() per restituire il valore del titolo del libro come una stringa. Infine, utilizziamo yield per restituire un dizionario contenente il titolo del libro. Alla fine del ciclo for, lo spider avrà raccolto tutti i titoli dei libri presenti nella pagina web di partenza.

Estrazione dei prezzi dei libri da una pagina web

import scrapy

class PriceSpider(scrapy.Spider):
    name = "pricespider"
    start_urls = [
        "http://books.toscrape.com/catalogue/category/books/science_22/index.html",
    ]

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "price": book.css(".price_color::text").get(),
            }

Questo codice è un esempio di uno spider di Scrapy che estrae i prezzi dei libri da una pagina web specifica.

La classe PriceSpider estende la classe base scrapy.Spider e include alcune proprietà e metodi specifici per questo spider.

La funzione parse() è il metodo di callback chiamato quando lo spider recupera una pagina web, questa funzione è responsabile di estrarre i dati dalla pagina.

La funzione utilizza un ciclo for per ciclare attraverso ogni elemento HTML con classe article.product_pod, questi elementi rappresentano ogni libro presente sulla pagina.

Per ogni libro, utilizza il selettore CSS .price_color per individuare il prezzo del libro, e utilizza il metodo get() per recuperare il testo del prezzo. Il prezzo estratto viene quindi aggiunto ad un dizionario come valore della chiave “price” e restituito attraverso l’istruzione yield.

In sintesi, questo spider recupera la pagina web specificata, estrae i prezzi di ogni libro presente sulla pagina e li restituisce come una serie di dizionari, dove ogni dizionario rappresenta un libro e contiene solo una chiave price con il relativo prezzo del libro.

Estrazione di tutte le informazioni sui libri da una pagina web

import scrapy

class BookSpider(scrapy.Spider):
    name = "bookspider"
    start_urls = [
        "http://books.toscrape.com/catalogue/category/books/science_22/index.html",
    ]

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "title": book.css("h3 > a::text").get(),
                "price": book.css(".price_color::text").get(),
                "rating": book.css(".star-rating::attr(class)").get().split(" ")[-1],
            }

Questo codice mostra come creare uno spider chiamato “bookspider” utilizzando il modulo Scrapy in Python. Lo spider inizia a navigare nell’indirizzo web specificato in “start_urls”, che in questo caso è “http://books.toscrape.com/catalogue/category/books/science_22/index.html".

La funzione parse() viene chiamata per ogni pagina visitata dallo spider e consente di analizzare il contenuto della pagina. In questo esempio, la funzione parse() utilizza il metodo “css” per selezionare tutti gli elementi HTML con la classe “product_pod” e li itera uno per uno.

Per ogni elemento, vengono estratte tre informazioni:

  • Il titolo del libro utilizzando il selettore “h3 > a::text”, che seleziona il testo all’interno del tag “a” immediatamente figlio del tag “h3” all’interno dell’elemento “article”
  • Il prezzo utilizzando il selettore “.price_color::text”, che seleziona il testo all’interno del tag con classe “price_color” all’interno dell’elemento “article”
  • La valutazione utilizzando il selettore “.star-rating::attr(class)”, che seleziona il valore dell’attributo “class” del tag con classe “star-rating” all’interno dell’elemento “article”. Poi si utilizza il metodo “split” per dividere la stringa in una lista di stringhe, e si prende l’ultima parte della stringa cioè l’ultimo elemento della lista.

Tutte queste informazioni vengono quindi restituite come un dizionario utilizzando il comando “yield” all’interno del ciclo for.

Estrazione di tutte le informazioni sui libri da più pagine web

import scrapy

class MultiPageBookSpider(scrapy.Spider):
    name = "multipagebookspider"
    start_urls = [
        "http://books.toscrape.com/catalogue/category/books/science_22/index.html",
    ]

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "title": book.css("h3 > a::text").get(),
                "price": book.css(".price_color::text").get(),
                "rating": book.css(".star-rating::attr(class)").get().split(" ")[-1],
            }
        next_page = response.css("li.next > a::attr(href)").get()
        if next_page is not None:
            yield response.follow(next_page, self.parse)

Questo codice è un esempio di uno spider Scrapy che utilizza un ciclo for per estrarre informazioni da più pagine di un sito web.

Il nome dello spider è multipagebookspider e la pagina iniziale da cui inizia la scansione è “http://books.toscrape.com/catalogue/category/books/science_22/index.html".

La funzione parse è chiamata per ogni pagina e utilizza il metodo css per estrarre informazioni sui libri dalla pagina (titolo, prezzo e valutazione).

Successivamente, utilizza il metodo css per estrarre il link alla prossima pagina e utilizza il metodo response.follow per seguire il link e chiamare nuovamente la funzione parse per la pagina successiva. Ciò consente allo spider di continuare a estrarre informazioni dalle pagine successive finché non viene raggiunta la fine del sito.

Estrazione di informazioni sui libri da una pagina web utilizzando un’espressione regolare

import scrapy
import re

class RegexBookSpider(scrapy.Spider):
    name = "regexbookspider"
    start_urls = [
        "http://books.toscrape.com/catalogue/category/books/science_22/index.html",
    ]

    def parse(self, response):
        for book in response.css("article.product_pod"):
            title = book.css("h3 > a::text").get()
            price = book.css(".price_color::text").get()
            rating = book.css(".star-rating::attr(class)").get()
            availability = book.css("p.availability::text").get()
            match = re.search(r'\d+', availability)
            if match:
                stock = int(match.group(0))
            yield {
                "title": title,
                "price": price,
                "rating": rating,
                "stock": stock
            }

Questo è un esempio di codice scritto in Python utilizzando la libreria Scrapy per creare un spider chiamato RegexBookSpider.

In particolare, questo spider è impostato per iniziare la navigazione nella pagina “http://books.toscrape.com/catalogue/category/books/science_22/index.html" e utilizza l’espressione regolare (regex) per estrarre informazioni sul numero di copie disponibili di ciascun libro.

Il metodo parse è chiamato per ogni pagina visitata dallo spider e utilizza il selettore CSS per selezionare i dati di interesse nella pagina. In questo caso, il spider estrae il titolo, il prezzo, la valutazione e la disponibilità di ogni libro. L’espressione regolare re.search(r’\d+’, availability) è utilizzata per cercare una corrispondenza di un numero intero all’interno della stringa di disponibilità. Se una corrispondenza viene trovata, il numero di stock disponibili viene salvato in una variabile chiamata “stock”. Infine, le informazioni estratte vengono restituite come un dizionario.

Estrazione di informazioni sui libri da una pagina web e salvataggio in formato CSV

import scrapy

class BookSpiderCSV(scrapy.Spider):
    name = "bookspidercsv"
    start_urls = [
        "http://books.toscrape.com/catalogue/category/books/science_22/index.html",
    ]

    custom_settings = {
        "FEED_FORMAT": "csv",
        "FEED_URI": "books.csv",
    }

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "title": book.css("h3 > a::text").get(),
                "price": book.css(".price_color::text").get(),
                "rating": book.css(".star-rating::attr(class)").get().split(" ")[-1],
            }

Questo codice è un esempio di uno spider Scrapy che raccoglie informazioni sui libri presenti su un sito web di esempio “http://books.toscrape.com/catalogue/category/books/science_22/index.html" e le salva in un file CSV. Il nome dello spider è “bookspidercsv” e la classe principale è BookSpiderCSV che estende la classe base scrapy.Spider.

La proprietà start_urls specifica l’URL iniziale del sito web da cui lo spider inizierà a raccogliere i dati.

La funzione parse viene eseguita per ogni pagina del sito web raccolta dallo spider. In questo caso, utilizza il metodo response.css per selezionare tutti gli elementi HTML con la classe product_pod dalla pagina, che rappresentano i libri. Per ogni libro, raccoglie il titolo, il prezzo e la valutazione utilizzando il metodo “css” e salva i dati raccolti in un dizionario.

La proprietà custom_settings imposta il formato del file di output e il nome del file di output. In questo caso, il formato è impostato su csv e il nome del file su books.csv.

Alla fine, tutti i dati raccolti dalla funzione parse vengono scritti nel file CSV books.csv nella cartella radice del progetto.

Scraping di dati da più siti

import scrapy

class MultiSiteBookSpider(scrapy.Spider):
    name = "multisitebookspider"
    start_urls = ["http://books.toscrape.com/catalogue/category/books/science_22/index.html",
                  "http://books.toscrape.com/catalogue/category/books/science_23/index.html"]

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "title": book.css("h3 a::attr(title)").get(),
                "price": book.css("p.price_color::text").get()
            }

In questo esempio, lo spider utilizza un elenco di URL di partenza start_urls che contiene più di un sito web. Utilizzando un solo metodo parse() per estrarre i dati, lo spider esegue la scansione di entrambi i siti web specificati nel elenco e estrae i dati dei libri dalle pagine web.

Scraping di dati da una pagina con autenticazione

import scrapy
from scrapy.http import FormRequest

class MySpider(scrapy.Spider):
    name = "myspider"
    start_urls = [
        "https://mywebsite.com/login",
    ]

    def parse(self, response):
        # estraiamo il token CSRF dalla pagina di login
        csrf_token = response.css("input[name='csrf_token']::attr(value)").get()

        # creiamo una richiesta di form con i dati di login e il token CSRF
        yield FormRequest.from_response(response, formdata={
            "username": "myusername",
            "password": "mypassword",
            "csrf_token": csrf_token
        }, callback=self.after_login)

    def after_login(self, response):
        # controlliamo se siamo stati autenticati con successo
        if "Incorrect username or password" in response.body:
            self.logger.error("Login fallito")
            return

        # se siamo stati autenticati con successo, possiamo iniziare a fare scraping
        for item in response.css("div.item"):
            yield {
                "title": item.css("h3 > a::text").get(),
                "price": item.css("span.price::text").get(),
                "image_url": item.css("img::attr(src)").get()
            }

        # e possiamo anche seguire i link per le pagine successive
        next_page = response.css("a.next_page::attr(href)").get()
        if next_page is not None:
            yield response.follow(next_page, self.after_login)

In questo modo, il spider inizia visitando la pagina di login, estrae il token CSRF e invia una richiesta di form con i dati di login e il token. Quindi, utilizza la funzione after_login come callback per gestire la risposta della richiesta di form. In questa funzione, il spider controlla se l’autenticazione è stata eseguita con successo e, in caso contrario, registra un errore. In caso contrario, il spider inizia a fare scraping dei dati e a seguire i link per le pagine successive.

Scraping di dati da una pagina AJAX

import scrapy

class AJAXBookSpider(scrapy.Spider):
    name = "ajaxbookspider"
    start_urls = ["http://books.toscrape.com/catalogue/category/books/ajax"]

    def parse(self, response):
        # Invia una richiesta POST per ottenere i dati dei libri tramite AJAX
        yield scrapy.FormRequest(
            "http://books.toscrape.com/catalogue/category/books/ajax",
            formdata={"page": "2"},
            callback=self.parse_ajax_response
        )

    def parse_ajax_response(self, response):
        # Estraiamo i dati dei libri dalla risposta AJAX
        for book in response.css("article.product_pod"):
            yield {
                "title": book.css("h3 a::attr(title)").get(),
                "price": book.css("p.price_color::text").get()
            }

In questo esempio, lo spider utilizza il metodo scrapy.FormRequest() per inviare una richiesta POST con i dati del form AJAX alla pagina web. La risposta AJAX contiene i dati dei libri che vengono estratti dalla pagina utilizzando il metodo parse_ajax_response().

Scraping di dati da una pagina utilizzando una sessione

import scrapy

class SessionBookSpider(scrapy.Spider):
    name = "sessionbookspider"
    start_urls = ["http://books.toscrape.com/catalogue/category/books/science_22/index.html"]

    def start_requests(self):
        # Iniziamo una sessione
        self.session = scrapy.Session()
        # Effettuiamo il login
        return [self.session.post("http://books.toscrape.com/login", data={"username": "user", "password": "pass"})]

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "title": book.css("h3 a::attr(title)").get(),
                "price": book.css("p.price_color::text").get()
            }

In questo esempio, lo spider utilizza una sessione per effettuare il login alla pagina web prima di iniziare a estrarre i dati. Utilizzando il metodo start_requests() per effettuare il login e il metodo parse() per estrarre i dati, lo spider mantiene la sessione attiva durante tutto il processo di scraping.

Scraping di tutti i prezzi dei libri su tutte le pagine

import scrapy

class AllPricesSpider(scrapy.Spider):
    name = "allpricesspider"
    start_urls = [
        "http://books.toscrape.com/catalogue/category/books/science_22/index.html",
    ]

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "price": book.css(".price_color::text").get(),
            }
        next_page = response.css("li.next > a::attr(href)").get()
        if next_page is not None:
            yield response.follow(next_page, self.parse)

Questo codice è un esempio di uno spider Scrapy che estrae i prezzi di tutti i libri presenti sul sito “http://books.toscrape.com/catalogue/category/books/science_22/index.html" e li salva in un formato predefinito.

Il codice importa la libreria di scrapy, quindi definisce una classe AllPricesSpider che estende la classe base scrapy.Spider.

La classe ha un nome univoco name e una lista di URL da cui iniziare a raccogliere i dati start_urls.

Il metodo parse è chiamato per ogni pagina visitata e utilizza la funzione response.css per selezionare tutti gli elementi article.product_pod sulla pagina. Per ogni elemento selezionato, estrae il prezzo utilizzando la selezione .price_color::text e lo salva in un dizionario.

Infine, il codice cerca il link per la pagina successiva, se esiste, utilizzando la funzione “response.css(“li.next > a::attr(href)”).get()” e segue il link utilizzando la funzione “response.follow(next_page, self.parse)” per continuare a raccogliere dati dalle successive pagine.

Scraping di tutti i titoli e le categorie dei libri

import scrapy

class TitleCategorySpider(scrapy.Spider):
    name = "titlecategoryspider"
    start_urls = [
        "http://books.toscrape.com/catalogue/category/books/science_22/index.html",
    ]

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "title": book.css("h3 > a::text").get(),
                "category": response.css("nav > ul > li.active > a::text").get(),
            }

Questo codice utilizza la libreria scrapy per estrarre dati da una pagina web. In particolare, il codice crea una classe chiamata TitleCategorySpider che estrae i titoli dei libri e la categoria dei libri dalla pagina web “http://books.toscrape.com/catalogue/category/books/science_22/index.html".

La classe TitleCategorySpider estende la classe scrapy.Spider e definisce un metodo parse che viene chiamato quando la pagina specificata in start_urls è scaricata.

Il metodo parse utilizza il metodo response.css per selezionare gli elementi delle pagine web. Utilizza il metodo css per selezionare gli elementi article.product_pod dalla pagina web. Per ogni elemento selezionato, utilizza il metodo css di nuovo per estrarre il titolo del libro (“h3 > a::text”) e la categoria del libro (“nav > ul > li.active > a::text”) e quindi li salva in un dizionario.

Il metodo yield è utilizzato per restituire il dizionario come risultato del metodo parse. In questo modo, scrapy sa che il risultato deve essere raccolto e utilizzato per qualcos’altro, come la scrittura su un file o la memorizzazione in un database.

Scraping delle immagini dei libri

import scrapy

class BookImageSpider(scrapy.Spider):
    name = "bookimagespider"
    start_urls = [
        "http://books.toscrape.com/catalogue/category/books/science_22/index.html",
    ]

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "title": book.css("h3 > a::text").get(),
                "image_urls": book.css("img::attr(src)").getall(),
            }

Il codice utilizza la libreria scrapy per creare uno spider chiamato bookimagespider che inizia a navigare dalla pagina “http://books.toscrape.com/catalogue/category/books/science_22/index.html".

Il metodo parse(self, response) viene chiamato per ogni pagina visitata dallo spider. Il metodo cerca tutti gli elementi HTML con classe product_pod che rappresentano i libri nella pagina. Per ogni elemento trovato, estrae il titolo del libro e l’URL dell’immagine del libro.

L’URL dell’immagine del libro viene estratto tramite il metodo .css(“img::attr(src)”).getall() che seleziona tutti gli elementi img e estrae l’attributo src. Il risultato è una lista di URL delle immagini dei libri.

Questi dati estratti vengono quindi restituiti come un dizionario con le chiavi title e image_urls.

Scraping dei prezzi dei libri in un formato specifico (es. JSON)

import scrapy

class PriceSpiderJSON(scrapy.Spider):
    name = "pricespiderjson"
    start_urls = [
        "http://books.toscrape.com/catalogue/category/books/science_22/index.html",
    ]

    custom_settings = {
        "FEED_FORMAT": "json",
        "FEED_URI": "prices.json",
    }

    def parse(self, response):
        for book in response.css("article.product_pod"):
            yield {
                "price": book.css(".price_color::text").get(),
            }

Questo codice utilizza la libreria scrapy per creare uno spider chiamato PriceSpiderJSON, che inizia a navigare nella pagina web “http://books.toscrape.com/catalogue/category/books/science_22/index.html" e recupera i prezzi dei libri presenti su quella pagina. Utilizza il metodo parse() per elaborare la risposta ottenuta dalla pagina web e recuperare i prezzi. Il metodo yield {} serve per generare un dizionario di output contenente la proprietà “price” con il prezzo del libro.

Il dizionario custom_settings definisce il formato del feed e il nome del file in cui verranno salvati i dati recuperati dallo spider. In questo caso il feed verrà salvato in formato JSON con nome prices.json.