Vai al contenuto principale

Scraping di Reddit con Python e BeautifulSoup 4

In questo tutorial imparerai a ottenere pagine web con requests, ad analizzare le pagine nel browser e a estrarre informazioni dall'HTML grezzo con BeautifulSoup.
Aggiornato 29 set 2026  · 13 min leggi

Scopri con l'IA

ChatGPTClaudePerplexity

Illustrazione scraping di Reddit

Puoi trovare un esempio completo e funzionante dello script che scriveremo qui.

Cos'è il web scraping?

Bene, quindi cos'è esattamente il web scraping? Come suggerisce il nome, è un metodo per "raschiare" o estrarre dati dalle pagine web. Qualsiasi cosa tu possa vedere su internet con il tuo browser, incluso questo tutorial, può essere salvata sul tuo hard disk locale.

Ci sono molti usi per il web scraping. Per qualsiasi analisi dei dati, il primo passo è l'acquisizione dei dati. Internet è un enorme archivio della storia e della conoscenza dell'umanità, e hai i mezzi per estrarre quello che vuoi e farne l'uso che preferisci.

Nel nostro tutorial useremo Python e il pacchetto BeautifulSoup 4 per ottenere informazioni da un subreddit. Siamo interessati al subreddit datascience. Vogliamo ottenere i primi 1000 post del subreddit ed esportarli in un file CSV. Vogliamo sapere chi li ha pubblicati, oltre a quante like e commenti hanno.

Cosa tratteremo nel tutorial:

  • Recuperare pagine web usando requests
  • Analizzare le pagine web nel browser alla ricerca di informazioni
  • Estrarre informazioni dall'HTML grezzo con BeautifulSoup

Nota: useremo la versione vecchia del sito di Reddit perché è più leggera da caricare e quindi meno gravosa per la tua macchina.

Prerequisiti

Questo tutorial presuppone che tu sappia le seguenti cose:

  • Eseguire script Python sul tuo computer
  • Una conoscenza di base della struttura HTML

Puoi imparare le competenze sopra nel corso per principianti di Python di DataCamp. Detto questo, i concetti usati qui sono molto basilari e te la caverai anche con pochissima esperienza di Python.

Ora che abbiamo fatto questo, possiamo passare alla prima parte della creazione del nostro web scraper. In effetti, la prima parte di qualsiasi script Python: gli import.

Nel nostro scraper useremo i seguenti pacchetti:

  • requests
  • beautifulsoup4

Puoi installare questi pacchetti con pip, ovviamente, così:

pip install package_name

Dopo aver scaricato i pacchetti, importali nel tuo codice.

import requests
import csv
import time
from bs4 import BeautifulSoup4

Useremo il modulo integrato di Python csv per scrivere i risultati in un file CSV. Avrai notato qualcosa di curioso nello snippet sopra. Ovvero, abbiamo scaricato un pacchetto chiamato beautifulsoup4, ma abbiamo importato da un modulo chiamato bs4. Questo in Python è consentito e, anche se in generale non è ben visto, non è certo contro la legge.

Nota importante sulle policy della piattaforma

Prima di usare le tecniche di scraping che mostrerò nei prossimi passaggi, assicurati di controllare i termini di servizio attuali di Reddit o di qualsiasi altro sito che intendi analizzare. Le policy dei siti possono cambiare nel tempo. Il Reddit User Agreement di settembre 2024 vieta lo scraping senza previo consenso scritto (vedi sezione 7). Assicurati sempre di rispettare le linee guida più recenti del sito prima di procedere.

Primi passi per esplorare Reddit

Abbiamo l'ambiente pronto. Ora ci serve l'URL della pagina web che vogliamo analizzare. Per il nostro tutorial, usiamo il subreddit "datascience" di Reddit. Prima di iniziare a scrivere lo script, c'è un po' di lavoro preliminare da fare. Apri un browser web e vai al subreddit in questione.

Nota che useremo la versione vecchia del subreddit per il nostro scraper. La versione nuova nasconde alcune informazioni cruciali nelle profondità della pagina. È possibile estrarre queste informazioni anche dal nuovo sito, ma per semplicità useremo la versione vecchia, che espone tutto chiaramente.

Aprendo il link, ti trovi davanti a un flusso di informazioni. Cosa ci serve in tutto questo? Esaminando i vari link, scoprirai che i post di Reddit sono di due tipi:

  • Inbound
  • Outbound

I link inbound puntano a contenuti interni a Reddit, mentre gli outbound sono l'opposto. Questo è importante perché vogliamo solo i link inbound. Post che contengono testo scritto dagli utenti, non solo link ad altri siti.

Quindi, sappiamo cosa vogliamo: come lo estraiamo? Se guardi il titolo dei post, noterai che è seguito da un testo tra parentesi. I post che ci interessano sono seguiti da "(self.datascience)". Logicamente, possiamo supporre che "self" si riferisca alla directory root di Reddit e ".datascience" al subreddit.

Ottimo, abbiamo un modo per identificare i post inbound e quelli outbound. Ora dobbiamo trovarlo nel DOM. Possiamo rintracciare questi identificatori nel DOM cercando tag, classi o id nella struttura? Certo!

Nel tuo browser, fai clic destro su un qualsiasi link "self.datascience" e scegli "ispeziona elemento". La maggior parte dei browser moderni ha questo strumento che permette agli sviluppatori web di esplorare dinamicamente le parti significative del sorgente della pagina. Se usi Safari, assicurati di aver attivato gli strumenti sviluppatore nelle preferenze. Vedrai un pannello scorrere alla parte del codice responsabile dell'identificatore "(self.datascience)".

Nel pannello, puoi vedere che l'identificatore è caricato in un tag ancora.

<a href="/r/datascience">self.datascience</a>

Ma questo è racchiuso in un tag span.

<span class="domain">
"("
<a href="/r/datascience">self.datascience</a>
")"
</span>

Questo tag span contiene il testo che vediamo nella pagina, cioè "(self.datascience)". Come vedi, è contrassegnato con la classe "domain". Puoi controllare anche gli altri link per vedere se seguono lo stesso formato e, infatti, è così.

Ottenere la pagina con BeautifulSoup

Sappiamo cosa vogliamo nella pagina, ottimo, ma come facciamo a usare Python per leggerne il contenuto? Funziona più o meno come farebbe un umano dal browser.

Per prima cosa, dobbiamo richiedere la pagina web usando la libreria "requests".

url = "https://old.reddit.com/r/datascience"
# Headers to mimic a browser visit
headers = {'User-Agent': 'Mozilla/5.0'}

# Returns a requests.models.Response object
page = requests.get(url, headers=headers)

Ora abbiamo un oggetto Response che contiene il testo grezzo della pagina. Al momento non possiamo farci nulla. Abbiamo solo una lunga stringa con l'intero sorgente HTML. Per dargli un senso, dobbiamo usare BeautifulSoup 4.

Gli header ci permettono di simulare una visita dal browser. Poiché la risposta a un bot è diversa da quella a un browser, e il nostro riferimento è il browser, è meglio ottenere la risposta del browser.

BeautifulSoup ci permette di trovare tag specifici cercando qualsiasi combinazione di classi, id o nomi di tag. Questo avviene creando un albero sintattico, ma i dettagli non sono rilevanti per il nostro obiettivo (e fuori dallo scopo di questo tutorial).

Quindi creiamo l'albero sintattico.

soup = BeautifulSoup(page.text, 'html.parser')

La "soup" è semplicemente un oggetto BeautifulSoup creato a partire da una stringa di sorgente grezzo. Tieni presente che dobbiamo specificare l'html parser, perché BeautifulSoup può creare soup anche da XML.

Trovare i tag

Sappiamo quali tag vogliamo (gli span con classe "domain") e abbiamo la soup. Il passo successivo è attraversare la soup e trovare tutte le istanze di questi tag. Potresti sorridere per quanto sia semplice con BeautifulSoup.

domains = soup.find_all("span", class_="domain")

Cosa abbiamo fatto? Abbiamo chiamato il metodo "find_all" sull'oggetto soup, che cerca al suo interno tutti i tag ancora con i parametri passati come secondo argomento. Abbiamo passato solo una condizione (classe uguale a "domain"), ma possiamo combinarla con molte altre cose, o usare anche solo un id.

Usiamo "class_=" perché "class" è una parola chiave riservata in Python per definire le classi e simili

Se volessi passare più di un parametro, ti basta rendere il secondo parametro un dizionario degli argomenti che vuoi includere, così:

soup.find_all("span", {"class": "domain", "height", "100px"})

Abbiamo tutti i tag span nella nostra lista "domains", ma quello che vogliamo sono i domini "(self.datascience)".

for domain in domains:
    if domain != "(self.datascience)":
        continue

    print(domain.text)

Ora dovresti vedere un elenco dei tipi di post nella pagina, escludendo quelli che non sono "(self.datascience)". Ma in sostanza abbiamo tutto quello che ci serve. Ora abbiamo riferimenti a tutti i post inbound.

Trovare le informazioni

Va bene stampare qualche riga di "(self.datascience)", ma e poi? Ripensa al nostro obiettivo iniziale: ottenere titolo del post, autore, like e numero di commenti.

Per questo dobbiamo tornare al browser. Se riapri l'ispettore sul nostro identificatore, vedrai che lo span è annidato dentro un div... Che a sua volta è annidato in un altro div, e così via. Salendo di livello, arrivi al div padre dell'intero post.

<div class=" thing id-t3_8qccuv even  link self" ...>
    ...
    <div class="entry unvoted">
        <div class="top-matter">
            <p class="title">
                ...
                <span class="domain">
                ...
            </p>
        </div>
    </div>
</div>

Come vedi, il padre comune è 4 livelli sopra nella struttura del DOM. Le ellissi (…) rappresentano rumore superfluo. Ora ci serve un riferimento al div del post per ogni post nella nostra lista domains. Possiamo trovare il padre di qualsiasi elemento nella soup usando i metodi di BeautifulSoup.

for domain in soup.find_all("span", class_="domain"):
    if domain != "(self.datascience)":
        continue

    parent_div = domain.parent.parent.parent.parent
    print(parent_div.text)

Eseguendo lo script stamperai tutto il testo di tutti i post inbound. Ma potresti pensare che questo codice non sia il massimo. Avresti ragione. Non è mai sicuro affidarsi solo all'integrità strutturale del DOM. È una soluzione "taglia e cuci" che richiede continui aggiornamenti ed è l'equivalente, in codice, di una bomba a orologeria.

Invece, diamo un'occhiata al div padre di ciascun post e vediamo se possiamo distinguere i link inbound e outbound dal padre stesso. Ogni div padre ha un attributo chiamato "data-domain", il cui valore è esattamente quello che vogliamo! Tutti i post inbound hanno data-domain impostato a "self.datascience".

Come abbiamo già detto, possiamo cercare tag con una combinazione di attributi con BeautifulSoup. Per fortuna, Reddit ha assegnato la classe "thing" al div padre di ciascun post.

attrs = {'class': 'thing', 'data-domain': 'self.datascience'}

for post in soup.find_all('div', attrs=attrs):
    print(post.attrs['data-domain'])

La variabile "attrs" è un dizionario che contiene gli attributi e i rispettivi valori per la ricerca. Questo è un approccio molto più sicuro per trovare i post, perché coinvolge meno parti in movimento. Poiché stiamo già fornendo "self.datascience" come argomento nella ricerca, non dobbiamo usare l'istruzione if per saltare iterazioni: siamo garantiti di ricevere solo post con l'attributo data-domain "self.datascience".

Ora che abbiamo tutte le informazioni che vogliamo, non resta che estrarle dai figli. Ed è semplice come immagini.

Estrarre le nostre informazioni da Reddit

Per ogni post ci servono 4 informazioni.

  • Titolo
  • Autore
  • Like
  • Commenti

Se diamo un'occhiata alla struttura del div del post, possiamo trovare tutte queste informazioni.

Il titolo

È la parte più semplice. In ogni div del post, c'è un tag paragrafo annidato sotto un paio di livelli di div. È particolarmente facile trovarlo perché ha la classe "title".

title = post.find('p', class_="title").text

L'oggetto post è all'interno del nostro ciclo for di prima. È anche un oggetto BeautifulSoup, ma contiene solo la struttura DOM all'interno del div del post. Il metodo "find" restituisce un solo oggetto, a differenza di "find_all", che restituisce un elenco di oggetti che soddisfano i criteri. Dopo aver trovato il tag, vogliamo solo la stringa contenente il titolo, quindi usiamo la sua proprietà text per leggerlo. Possiamo anche leggere altri attributi usando "object.attrs['attribute']".

L'autore

Anche questo è relativamente semplice: apri l'ispettore sul nome di un autore sotto il titolo del post. Vedrai che il nome dell'autore è in un tag ancora con classe "author".

author = post.find('a', class_='author').text

I commenti

Richiede un piccolo passaggio in più, ma resta semplice. Possiamo trovare i commenti nello stesso modo in cui abbiamo trovato titolo e autore.

comments = post.find('a', class_='comments').text

Eseguendolo, otterrai qualcosa come "49 comments". Va bene, ma sarebbe meglio ottenere solo il numero. Per farlo, serve un po' di Python in più.

Se usiamo la funzione di Python "str.split()", restituirà un array di tutti gli elementi della stringa separati dagli spazi. Nel nostro caso otterremo la lista "["49", "comments"]". Perfetto! Ora ci basta prendere il primo elemento e salvarlo. Dobbiamo solo concatenare le funzioni alla nostra riga.

comments = post.find('a', class_='comments').text.split()[0]

Ma non abbiamo ancora finito perché a volte non otteniamo un numero, ma "comment". Succede quando un post non ha commenti. Dal momento che lo sappiamo, ci basta controllare se il risultato è "comment" e sostituirlo con "0".

if comments == "comment":
    comments = 0

I like

Trovare il numero di like è semplicissimo e segue la stessa logica usata sopra.

likes = post.find("div", attrs={"class": "score likes"}).text

Qui noterai però che abbiamo usato una combinazione di due classi. Semplicemente perché esistono altri div con classe "score" o "likes", ma solo uno con la combinazione "score" e "likes".

Se il numero di like è 0, otteniamo 0. Ma succede qualcosa di strano se il post è così nuovo da non avere ancora like. Otteniamo "•". Sostituiamolo con "None" per non creare confusione nei risultati finali.

if likes == "•":
    likes = "None"

Scrivere i risultati in CSV

Finora abbiamo un ciclo che estrae titolo, autore, like e commenti per ogni post nella pagina. Python ha un ottimo modulo integrato per leggere e scrivere file CSV chiamato "csv", seguendo il principio pitonico: tienilo semplice. Tutto quello che dobbiamo fare è aggiungere una riga alla fine del blocco del ciclo che aggiunge i dettagli del post a un file CSV.

counter = 1
for post in posts:
    ...
    post_line = [counter, title, author, likes, comments]
    with open('output.csv', 'a') as f:
        writer = csv.writer(f)
        writer.writerow(post_line)

    counter += 1

Abbastanza semplice, no? La variabile post_line serve a creare un array che memorizza gli elementi da separare con una virgola. La variabile counter tiene traccia di quanti post abbiamo registrato.

Passare alla pagina successiva

Dal momento che contiamo il numero di post salvati, ci basta racchiudere tutta la nostra logica in un altro ciclo che richiede nuove pagine fino a registrare una certa quantità di post.

counter = 1

while (counter <= 100):
    for post in posts:
        # Getting the title, author, ...
        # Writing to CSV and incrementing counter...

    next_button = soup.find("span", class_="next-button")
    next_page_link = next_button.find("a").attrs['href']
    time.sleep(2)
    page = requests.get(next_page_link, headers=headers)
    soup = BeautifulSoup(page.text, 'html.parser')

Abbiamo fatto diverse cose, vero? Vediamole riga per riga. Per prima cosa, abbiamo spostato la variabile counter fuori, in modo che il while possa usarla.

Poi, sappiamo cosa fa il for, ma le altre? La variabile "next_button" serve a trovare e memorizzare il pulsante "next". Dopo, possiamo trovare il tag ancora al suo interno e ottenere l'attributo "href", che salviamo in "next_page_link".

Possiamo usare questo link per richiedere la pagina successiva, salvarla di nuovo in "page" e creare un'altra soup con BeautifulSoup.

Lo snippet sopra si ferma dopo 100, ma puoi farlo fermare dopo qualsiasi numero di post desideri.

Scraping in modo responsabile

La riga di cui non abbiamo parlato sopra è "time.sleep(2)". Questo perché merita una sezione a parte. Qualsiasi server web ha risorse finite, quindi sta a noi fare in modo di non usarle tutte. Non solo rischi il ban se richiedi centinaia di pagine in pochi secondi, ma non è nemmeno corretto.

È importante ricordare che è già gentile da parte del sito permetterti di fare scraping, perché se volessero, potrebbero rilevare i bot nelle prime 10 o 20 richieste, o persino beccarti in base all'oggetto request che manda Python. È già tanto che ti permettano di fare scraping senza dover ruotare l'IP, quindi ricambia rallentando il tuo bot.

Puoi trovare la crawl policy di un sito guardando il file robots.txt. Di solito si trova nella root del sito (es. http://www.reddit.com/robots.txt).

E adesso?

Quello che vuoi, davvero. Come abbiamo appena dimostrato, tutto ciò che vedi sul web può essere estratto e salvato in locale. Puoi usare le informazioni che abbiamo appena acquisito per una moltitudine di scopi. Con solo le quattro informazioni trovate, possiamo trarre molte conclusioni.

Analizzando più a fondo, possiamo capire quali tipi di post su Reddit ottengono più like, quali parole contengono, chi li pubblica. Oppure, al contrario, possiamo creare un "calcolatore di controversie" analizzando il rapporto tra like e commenti.

Considera un post che riceve molti commenti ma nessun like. Probabilmente significa che il post contiene qualcosa che colpisce le persone, ma non necessariamente qualcosa che piace.

Le possibilità sono molte, e sta a te decidere come usare le informazioni.

Se ti è piaciuto questo tutorial, dai un'occhiata alla nostra guida su come fare scraping di Amazon con Python, oltre alla nostra raccolta di corsi di Python.

Argomenti
Python

Scopri di più su Python

Corso

Introduzione a Python

4 ore
7.1M
Impara le basi dell’analisi dei dati con Python in sole 4 ore. Questo corso online ti introduce all’interfaccia di Python e ai pacchetti più usati.
Vedi i dettagliRight Arrow
Inizia Il Corso
Mostra altroRight Arrow
Correlato

blog

Tokenizzazione nel NLP: come funziona, sfide e casi d'uso

Guida al preprocessing NLP nel machine learning. Copriamo spaCy, i transformer di Hugging Face e come funziona la tokenizzazione in casi d'uso reali.
Abid Ali Awan's photo

Abid Ali Awan

10 min

blog

I 15 migliori server MCP remoti che ogni AI builder dovrebbe conoscere nel 2026

Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Abid Ali Awan's photo

Abid Ali Awan

15 min

blog

Che cos'è Snowflake? Guida per principianti alla piattaforma dati cloud

Esplora le basi di Snowflake, la piattaforma dati cloud. Scopri la sua architettura, le sue funzionalità e come integrarla nelle tue pipeline di dati.
Tim Lu's photo

Tim Lu

12 min

Mostra AltroMostra Altro