Come estrarre dati da Goodreads con Python (senza più risultati vuoti)

Ultimo aggiornamento il April 16, 2026

Ci sono poche cose più frustranti che scrivere 30 righe di Python, avviare il tuo scraper di Goodreads e vederlo restituire []. Una lista vuota. Niente. Solo tu e il cursore che lampeggia.

L’ho visto succedere decine di volte — nei nostri esperimenti interni in , nei forum per sviluppatori e nelle issue GitHub che si accumulano nei repository di scraper abbandonati. Le lamentele sono quasi sempre le stesse: "la sezione delle recensioni in alto è vuota, mi mostra solo []", "qualunque numero di pagina usi, estrae sempre la prima", "il mio codice funzionava l’anno scorso, adesso è rotto." E come se non bastasse, l’API di Goodreads è stata deprecata nel dicembre 2020, quindi il consiglio “usa l’API” che trovi nei tutorial più vecchi non porta da nessuna parte.

Se oggi vuoi ottenere dati strutturati dei libri da Goodreads — titoli, autori, valutazioni, recensioni, generi, ISBN — lo scraping è la strada principale. In questa guida ti mostrerò un metodo completo e funzionante per estrarre dati da goodreads con python, coprendo contenuti renderizzati in JavaScript, paginazione, anti-blocco ed esportazione. E se Python non fa per te, ti farò vedere anche un’alternativa no-code che fa il lavoro in un paio di clic.

Che cos’è lo scraping di Goodreads e perché farlo con Python?

Scrapare Goodreads significa estrarre automaticamente dati sui libri — titoli, autori, valutazioni, numero di recensioni, generi, ISBN, numero di pagine, data di pubblicazione e altro — dalle pagine web di Goodreads usando codice, invece di copiare e incollare a mano.

Goodreads è uno dei più grandi database di libri al mondo, con oltre e circa . Ogni mese più di 18 milioni di libri finiscono nelle liste “Want to Read”. Un patrimonio di dati così ampio, strutturato e aggiornato continuamente è esattamente il motivo per cui editori, data scientist, librai e ricercatori continuano a tornarci.

Python è il linguaggio di riferimento per questo tipo di lavoro — alimenta circa il di tutti i progetti di scraping. Le librerie sono mature (requests, BeautifulSoup, Selenium, Playwright, pandas), la sintassi è adatta anche a chi inizia, e la community è enorme.

Se non hai mai estratto dati da un sito web prima d’ora, Python è il punto di partenza ideale.

Perché estrarre dati da Goodreads con Python? Casi d’uso reali

Prima di entrare nel codice, vale la pena chiederselo: chi ha davvero bisogno di questi dati e cosa ci fa?

Caso d’usoChi ne beneficiaCosa estrai
Analisi di mercato per editoriEditori, agenti letterariGeneri in trend, titoli più apprezzati, autori emergenti, valutazioni dei concorrenti
Sistemi di raccomandazione libriData scientist, appassionati, sviluppatoriValutazioni, generi, scaffali utente, sentiment delle recensioni
Monitoraggio prezzi e inventarioLibrai ecommerceTitoli in trend, volume delle recensioni, conteggio “Want to Read”
Ricerca accademicaRicercatori, studentiTesto delle recensioni, distribuzione dei voti, classificazioni per genere
Analisi delle lettureBook blogger, progetti personaliDati degli scaffali personali, cronologia di lettura, statistiche di fine anno

Alcuni esempi concreti: il UCSD Book Graph — uno dei dataset accademici più citati nella ricerca sui sistemi di raccomandazione — contiene , tutti raccolti da scaffali Goodreads pubblicamente accessibili. Diversi dataset Kaggle (goodbooks-10k, Best Books Ever, ecc.) sono nati proprio dallo scraping di Goodreads. E uno studio del 2025 pubblicato su Big Data and Society ha curato computazionalmente per analizzare come le recensioni sponsorizzate influenzino la piattaforma.

Dal lato commerciale, Bright Data vende dataset Goodreads già estratti a partire da appena 0,50 $ per 1.000 record — un’ulteriore prova del valore reale di questi dati.

L’API di Goodreads non c’è più — ecco cosa l’ha sostituita

Se hai cercato “Goodreads API” di recente, probabilmente sei finito su un tutorial ormai obsoleto. L’8 dicembre 2020, Goodreads ha smesso silenziosamente di rilasciare nuove chiavi API per sviluppatori. Nessun post sul blog, nessuna email di massa — solo un piccolo banner nella pagina della documentazione e tanti sviluppatori confusi.

goodreads-data-access-tools.webp

L’impatto è stato immediato. Uno sviluppatore, Kyle K, aveva creato un bot Discord per condividere consigli di lettura — “all’improvviso PUF, ha smesso di funzionare.” Un altro, Matthew Jones, ha perso l’accesso all’API una settimana prima del voto per gli Stabby Awards di Reddit r/Fantasy, dovendo tornare a Google Forms. Una studentessa magistrale, Elena Neacsu, si è vista bloccare il progetto della tesi a metà sviluppo.

Quindi cosa resta? Oggi il panorama è questo:

ApproccioDati disponibiliFacilità d’usoLimiti di velocitàStato
Goodreads APIMetadati completi, recensioniFacile (lo era)1 richiesta/secDeprecata (dicembre 2020) — nessuna nuova chiave
Open Library APITitoli, autori, ISBN, copertine (~30M titoli)Facile1-3 richieste/secAttiva, gratuita, senza autenticazione
Google Books APIMetadati, anteprimeFacile1.000/giorno gratisAttiva (con lacune sugli ISBN non inglesi)
Scraping Python (requests + BS4)Tutto ciò che è nell’HTML inizialeMedioGestito da teFunziona per contenuti statici
Scraping Python (Selenium/Playwright)Anche contenuti renderizzati in JSPiù difficileGestito da teNecessario per recensioni, alcune liste
Thunderbit (estensione Chrome no-code)Qualsiasi dato visibile nella paginaMolto facile (2 clic)Basato su creditiAttivo — nessun Python richiesto

Open Library è un ottimo complemento, soprattutto per ricerche ISBN e metadati di base. Ma se ti servono valutazioni, recensioni, tag di genere o conteggi “Want to Read”, allora stai estraendo direttamente da Goodreads — con Python oppure con uno strumento come Thunderbit, che può estrarre pagine Goodreads (incluse le sottopagine dei dettagli libro) con campi suggeriti dall’AI ed esportazione diretta su Google Sheets, Notion o Airtable.

Perché il tuo scraper Python di Goodreads restituisce risultati vuoti (e come risolvere)

Questa è la sezione che avrei voluto trovare quando ho iniziato a lavorare con i dati di Goodreads. Il problema dei “risultati vuoti” è il più comune nei forum per sviluppatori, e può avere cause diverse — ognuna con la sua soluzione.

SintomoCausa principaleSoluzione
Recensioni/valutazioni restituiscono []Contenuto renderizzato in JS (React/lazy-load)Usa Selenium o Playwright invece di requests
Estrae sempre e solo la pagina 1Parametri di paginazione ignorati o gestiti in JSPassa correttamente ?page=N; usa l’automazione del browser per lo scroll infinito
Il codice funzionava l’anno scorso, ora fallisceGoodreads ha cambiato i nomi delle classi HTMLUsa selettori più robusti (JSON-LD, attributi data-testid)
Errori 403/blocco dopo poche richiesteHeader mancanti / richieste troppo velociAggiungi User-Agent, time.sleep(), ruota i proxy
Login wall sulle pagine scaffale/listaNecessario cookie/sessioneUsa requests.Session() con cookie oppure scraping via browser

Contenuto renderizzato in JS: recensioni e valutazioni risultano vuote

Goodreads usa un frontend basato su React. Quando fai requests.get() su una pagina libro, ottieni l’HTML iniziale — ma recensioni, distribuzioni delle valutazioni e molte sezioni “more info” vengono caricate in modo asincrono tramite JavaScript. In pratica, il tuo scraper non può vederle.

La soluzione: per qualsiasi pagina in cui ti servano contenuti renderizzati in JavaScript, passa a Selenium o Playwright. Per i progetti nuovi, io consiglio Playwright — è grazie al protocollo basato su WebSocket, e offre migliori funzioni integrate di stealth e supporto async.

troubleshooting-empty-array-causes.webp

La paginazione restituisce sempre la pagina 1

Questo è subdolo. Scrivi un ciclo, incrementi ?page=N, ma ottieni sempre gli stessi risultati. Su Goodreads, le pagine degli scaffali restituiscono silenziosamente il contenuto della pagina 1 indipendentemente dal parametro ?page= se non sei autenticato. Nessun errore, nessun redirect — solo la stessa prima pagina all’infinito.

La soluzione: includi un cookie di sessione autenticato (in particolare _session_id2). Ne parlo meglio nella sezione sulla paginazione qui sotto.

Il codice che funzionava l’anno scorso ora non va più

Goodreads cambia periodicamente i nomi delle classi HTML e la struttura delle pagine. Il popolare repository GitHub maria-antoniak/goodreads-scraper mostra ormai un avviso permanente: "This project is unmaintained and no longer functioning." La soluzione è usare selettori più robusti — dati strutturati JSON-LD (che seguono gli standard schema.org e cambiano raramente) oppure attributi data-testid al posto di classi fragili.

Errori 403 o blocchi

La libreria requests di Python ha un fingerprint TLS diverso da quello di Chrome. Anche con una stringa User-Agent da Chrome, sistemi di rilevamento bot come AWS WAF (che Goodreads usa perché è un sussidiario di Amazon) possono accorgersi della discrepanza. La soluzione: aggiungi header realistici da browser, inserisci pause time.sleep() di 3-8 secondi tra le richieste e, per scraping su larga scala, considera curl_cffi per allineare il fingerprint TLS.

Login wall su scaffali e pagine lista

Alcune pagine degli scaffali e delle liste di Goodreads richiedono autenticazione per accedere al contenuto completo, soprattutto oltre la pagina 5. Usa requests.Session() con cookie esportati dal browser, oppure Selenium/Playwright con un profilo già loggato. Thunderbit gestisce tutto in modo naturale, perché lavora nel tuo browser Chrome già autenticato.

Prima di iniziare

  • Difficoltà: Intermedio (si presume una conoscenza base di Python)
  • Tempo necessario: circa 20-30 minuti per l’intera guida
  • Cosa ti serve:
    • Python 3.8+
    • Browser Chrome (per ispezione con DevTools e per Selenium/Playwright)
    • Librerie: requests, beautifulsoup4, selenium o playwright, pandas
    • (Opzionale) gspread per esportare su Google Sheets
    • (Opzionale) per l’alternativa no-code

goodreads-scraping-flow.webp

Passo 1: configura il tuo ambiente Python

Installa le librerie necessarie. Apri il terminale ed esegui:

1pip install requests beautifulsoup4 selenium pandas lxml

Se preferisci Playwright (consigliato per i nuovi progetti):

1pip install playwright
2playwright install chromium

Per esportare su Google Sheets (opzionale):

1pip install gspread oauth2client

Assicurati di usare Python 3.8 o superiore. Puoi verificarlo con python --version.

Dopo l’installazione, dovresti riuscire a importare tutte le librerie senza errori. Prova python -c "import requests, bs4, pandas; print('Ready')" per confermare.

Passo 2: invia la tua prima richiesta con gli header corretti

Apri nel browser una pagina lista o scaffale di Goodreads — ad esempio https://www.goodreads.com/list/show/1.Best_Books_Ever. Ora recuperiamo quella pagina con Python.

1import requests
2from bs4 import BeautifulSoup
3headers = {
4    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
5                  "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
6    "Accept-Language": "en-US,en;q=0.9",
7    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
8}
9url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
10response = requests.get(url, headers=headers, timeout=15)
11print(f"Status: {response.status_code}")

Dovresti vedere Status: 200. Se ottieni 403, ricontrolla gli header — AWS WAF di Goodreads verifica che il User-Agent sia realistico e respinge richieste troppo essenziali. Gli header qui sopra imitano una vera sessione Chrome.

Passo 3: ispeziona la pagina e individua i selettori giusti

Apri Chrome DevTools (F12) sulla pagina lista di Goodreads. Fai clic destro su un titolo di libro e seleziona “Inspect”. Vedrai la struttura DOM di ogni elemento del libro.

Per le pagine lista, ogni libro è di solito racchiuso in un elemento <tr> con itemtype="http://schema.org/Book". Al suo interno troverai:

  • Titolo: a.bookTitle (il testo del link è il titolo, href contiene l’URL del libro)
  • Autore: a.authorName
  • Valutazione: span.minirating (contiene voto medio e numero di valutazioni)
  • Copertina: img all’interno della riga del libro

Per le singole pagine dettaglio libro, lascia perdere i selettori CSS e vai direttamente su JSON-LD. Goodreads incorpora dati strutturati in un tag <script type="application/ld+json"> che segue il formato Book di schema.org. È molto più stabile dei nomi di classe, che Goodreads cambia spesso.

Passo 4: estrai i dati dei libri da una singola pagina lista

Parsiamo la pagina lista ed estraiamo le informazioni di base per ogni libro:

1import requests
2from bs4 import BeautifulSoup
3headers = {
4    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
5                  "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
6    "Accept-Language": "en-US,en;q=0.9",
7}
8url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
9response = requests.get(url, headers=headers, timeout=15)
10soup = BeautifulSoup(response.text, "lxml")
11books = []
12rows = soup.select('tr[itemtype="http://schema.org/Book"]')
13for row in rows:
14    title_tag = row.select_one("a.bookTitle")
15    author_tag = row.select_one("a.authorName")
16    rating_tag = row.select_one("span.minirating")
17    title = title_tag.get_text(strip=True) if title_tag else ""
18    book_url = "https://www.goodreads.com" + title_tag["href"] if title_tag else ""
19    author = author_tag.get_text(strip=True) if author_tag else ""
20    rating_text = rating_tag.get_text(strip=True) if rating_tag else ""
21    books.append({
22        "title": title,
23        "author": author,
24        "rating_info": rating_text,
25        "book_url": book_url,
26    })
27print(f"Trovati {len(books)} libri nella pagina 1")
28for b in books[:3]:
29    print(b)

Dovresti vedere circa 100 libri per ogni pagina lista. Ogni elemento avrà titolo, autore, una stringa di valutazione come “4.28 avg rating — 9,031,257 ratings”, e l’URL alla pagina dettaglio del libro.

Passo 5: estrai le sottopagine per ottenere informazioni dettagliate sui libri

La pagina elenco ti dà le basi, ma il vero valore — ISBN, descrizione completa, tag di genere, numero di pagine, data di pubblicazione — si trova nella pagina individuale di ciascun libro. Qui JSON-LD fa davvero la differenza.

1import json
2import time
3def scrape_book_detail(book_url, headers):
4    """Visita una singola pagina libro ed estrae i metadati dettagliati via JSON-LD."""
5    resp = requests.get(book_url, headers=headers, timeout=15)
6    if resp.status_code != 200:
7        return {}
8    soup = BeautifulSoup(resp.text, "lxml")
9    script = soup.find("script", {"type": "application/ld+json"})
10    if not script:
11        return {}
12    data = json.loads(script.string)
13    agg = data.get("aggregateRating", {})
14    # I tag di genere non sono in JSON-LD; usiamo l'HTML come fallback
15    genres = [g.get_text(strip=True) for g in soup.select('span.BookPageMetadataSection__genreButton a span')]
16    return {
17        "isbn": data.get("isbn", ""),
18        "pages": data.get("numberOfPages", ""),
19        "language": data.get("inLanguage", ""),
20        "format": data.get("bookFormat", ""),
21        "avg_rating": agg.get("ratingValue", ""),
22        "rating_count": agg.get("ratingCount", ""),
23        "review_count": agg.get("reviewCount", ""),
24        "description": data.get("description", "")[:200],  # taglio per l’anteprima
25        "genres": ", ".join(genres[:5]),
26    }
27# Esempio: arricchiamo i primi 3 libri
28for book in books[:3]:
29    details = scrape_book_detail(book["book_url"], headers)
30    book.update(details)
31    print(f"Estratto: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
32    time.sleep(4)  # rispetta i limiti di richiesta

Aggiungi time.sleep() di 3-8 secondi tra una richiesta e l’altra. Goodreads inizia a limitare il traffico intorno alle 20-30 richieste al minuto da un singolo IP, e se vai troppo veloce inizierai a vedere 403 o CAPTCHA.

Questo approccio in due passaggi — prima raccogli tutti gli URL dei libri dalle pagine lista, poi visiti ogni pagina dettaglio — è più affidabile e più facile da riprendere se qualcosa si interrompe. È la strategia usata dalla maggior parte degli scraper Goodreads che funzionano davvero.

Nota a margine: può farlo automaticamente con lo scraping delle sottopagine. L’AI visita ogni pagina dettaglio del libro e arricchisce la tua tabella con ISBN, descrizione, generi e altro — senza codice, senza cicli, senza timer di attesa.

Passo 6: gestisci i contenuti renderizzati in JavaScript con Selenium

Per le pagine in cui i contenuti necessari vengono caricati via JavaScript — recensioni, distribuzione dei voti, sezioni “more details” — serve uno strumento di automazione del browser. Ecco un esempio con Selenium:

1from selenium import webdriver
2from selenium.webdriver.chrome.options import Options
3from selenium.webdriver.common.by import By
4from selenium.webdriver.support.ui import WebDriverWait
5from selenium.webdriver.support import expected_conditions as EC
6options = Options()
7options.add_argument("--headless=new")
8options.add_argument("--disable-blink-features=AutomationControlled")
9options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
10                     "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
11driver = webdriver.Chrome(options=options)
12driver.get("https://www.goodreads.com/book/show/5907.The_Hobbit")
13# Attendi il caricamento delle recensioni
14try:
15    WebDriverWait(driver, 10).until(
16        EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
17    )
18except:
19    print("Le recensioni non si sono caricate — la pagina potrebbe richiedere il login oppure il JS è andato in timeout")
20# Ora analizziamo la pagina completamente renderizzata
21page_source = driver.page_source
22soup = BeautifulSoup(page_source, "lxml")
23reviews = soup.select("article.ReviewCard")
24for rev in reviews[:3]:
25    text = rev.select_one("span.Formatted")
26    stars = rev.select_one("span.RatingStars")
27    print(f"Valutazione: {stars.get_text(strip=True) if stars else 'N/A'}")
28    print(f"Recensione: {text.get_text(strip=True)[:150] if text else 'N/A'}...")
29    print()
30driver.quit()

Quando usare Selenium rispetto a requests:

  • Usa requests + BeautifulSoup per metadati dei libri (JSON-LD), pagine lista, pagine scaffale (pagina 1) e dati Choice Awards
  • Usa Selenium o Playwright per recensioni, distribuzione delle valutazioni e qualsiasi contenuto che non appare nell’HTML grezzo

Playwright è in genere la scelta migliore per i progetti nuovi — più veloce, meno memoria, default di stealth migliori. Ma Selenium ha una community più ampia e più esempi di codice già pronti specifici per Goodreads.

Paginazione che funziona davvero: estrarre liste complete di Goodreads

La paginazione è il punto in cui gli scraper per Goodreads si rompono più spesso, e non ho ancora trovato un tutorial concorrente che la spieghi bene. Ecco come farla nel modo corretto.

Come funzionano gli URL di paginazione di Goodreads

Goodreads usa un semplice parametro ?page=N per la maggior parte delle pagine paginate:

  • Liste: https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2
  • Scaffali: https://www.goodreads.com/shelf/show/thriller?page=2
  • Ricerca: https://www.goodreads.com/search?q=fantasy&page=2

Ogni pagina lista mostra in genere 100 libri. Gli scaffali ne mostrano 50 per pagina.

Scrivere un ciclo di paginazione che sappia quando fermarsi

1import time
2all_books = []
3base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
4for page_num in range(1, 50):  # limite di sicurezza a 50 pagine
5    url = f"{base_url}?page={page_num}"
6    resp = requests.get(url, headers=headers, timeout=15)
7    if resp.status_code != 200:
8        print(f"Pagina {page_num}: status {resp.status_code}, mi fermo.")
9        break
10    soup = BeautifulSoup(resp.text, "lxml")
11    rows = soup.select('tr[itemtype="http://schema.org/Book"]')
12    if not rows:
13        print(f"Pagina {page_num}: nessun libro trovato, fine delle pagine.")
14        break
15    for row in rows:
16        title_tag = row.select_one("a.bookTitle")
17        author_tag = row.select_one("a.authorName")
18        title = title_tag.get_text(strip=True) if title_tag else ""
19        book_url = "https://www.goodreads.com" + title_tag["href"] if title_tag else ""
20        author = author_tag.get_text(strip=True) if author_tag else ""
21        all_books.append({"title": title, "author": author, "book_url": book_url})
22    print(f"Pagina {page_num}: estratti {len(rows)} libri (totale: {len(all_books)})")
23    time.sleep(5)  # pausa di 5 secondi tra le pagine
24print(f"\nFatto. Totale libri raccolti: {len(all_books)}")

Puoi capire di aver raggiunto l’ultima pagina controllando se la lista dei risultati è vuota (nessun elemento tr[itemtype="http://schema.org/Book"]) oppure verificando l’assenza del link “next” (a.next_page).

Caso limite: serve il login oltre la pagina 5

Ecco la trappola che coglie quasi tutti: alcune pagine degli scaffali e delle liste di Goodreads restituiscono silenziosamente il contenuto della pagina 1 quando richiedi la pagina 6+ senza autenticazione. Nessun errore, nessun redirect — solo gli stessi dati ripetuti.

Per risolvere, esporta il cookie _session_id2 dal browser (usa un’estensione per esportare i cookie oppure Chrome DevTools > Application > Cookies) e inseriscilo nelle richieste:

1session = requests.Session()
2session.headers.update(headers)
3session.cookies.set("_session_id2", "IL_TUO_VALORE_COOKIE_DI_SESSIONE", domain=".goodreads.com")
4# Ora usa session.get() al posto di requests.get()
5resp = session.get(f"{base_url}?page=6", timeout=15)

Thunderbit gestisce nativamente sia la paginazione a clic sia quella con scroll infinito, senza codice e senza dover gestire cookie. Se la tua logica di paginazione continua a rompersi, vale davvero la pena prenderlo in considerazione.

Lo script Python completo pronto da copiare e incollare

Ecco lo script completo e consolidato. Gestisce header, paginazione, scraping delle sottopagine tramite JSON-LD, rate limiting ed esportazione CSV. L’ho testato su pagine Goodreads live a metà 2025.

1"""
2goodreads_scraper.py — Estrae una lista Goodreads con paginazione e arricchimento delle pagine dettaglio libro.
3Uso: python goodreads_scraper.py
4Output: goodreads_books.csv
5"""
6import csv, json, time, requests
7from bs4 import BeautifulSoup
8HEADERS = {
9    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
10                  "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
11    "Accept-Language": "en-US,en;q=0.9",
12    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
13}
14BASE_URL = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
15MAX_PAGES = 3          # modifica se necessario
16DELAY_LISTING = 5      # secondi tra le pagine lista
17DELAY_DETAIL = 4       # secondi tra le pagine dettaglio
18OUTPUT_FILE = "goodreads_books.csv"
19def scrape_listing_page(url):
20    """Restituisce una lista di dict con title, author, book_url da una pagina lista."""
21    resp = requests.get(url, headers=HEADERS, timeout=15)
22    if resp.status_code != 200:
23        return []
24    soup = BeautifulSoup(resp.text, "lxml")
25    rows = soup.select('tr[itemtype="http://schema.org/Book"]')
26    books = []
27    for row in rows:
28        t = row.select_one("a.bookTitle")
29        a = row.select_one("a.authorName")
30        if t:
31            books.append({
32                "title": t.get_text(strip=True),
33                "author": a.get_text(strip=True) if a else "",
34                "book_url": "https://www.goodreads.com" + t["href"],
35            })
36    return books
37def scrape_book_detail(book_url):
38    """Visita una pagina libro ed estrae metadati via JSON-LD + fallback HTML."""
39    resp = requests.get(book_url, headers=HEADERS, timeout=15)
40    if resp.status_code != 200:
41        return {}
42    soup = BeautifulSoup(resp.text, "lxml")
43    script = soup.find("script", {"type": "application/ld+json"})
44    if not script:
45        return {}
46    data = json.loads(script.string)
47    agg = data.get("aggregateRating", {})
48    genres = [g.get_text(strip=True)
49              for g in soup.select("span.BookPageMetadataSection__genreButton a span")]
50    return {
51        "isbn": data.get("isbn", ""),
52        "pages": data.get("numberOfPages", ""),
53        "avg_rating": agg.get("ratingValue", ""),
54        "rating_count": agg.get("ratingCount", ""),
55        "review_count": agg.get("reviewCount", ""),
56        "description": (data.get("description", "") or "")[:300],
57        "genres": ", ".join(genres[:5]),
58        "language": data.get("inLanguage", ""),
59        "format": data.get("bookFormat", ""),
60        "published": data.get("datePublished", ""),
61    }
62def main():
63    all_books = []
64    # --- Passaggio 1: raccogli gli URL dei libri dalle pagine lista ---
65    for page in range(1, MAX_PAGES + 1):
66        url = f"{BASE_URL}?page={page}"
67        page_books = scrape_listing_page(url)
68        if not page_books:
69            print(f"Pagina {page}: vuota — interrompo la paginazione.")
70            break
71        all_books.extend(page_books)
72        print(f"Pagina {page}: {len(page_books)} libri (totale: {len(all_books)})")
73        time.sleep(DELAY_LISTING)
74    # --- Passaggio 2: arricchisci ogni libro con i dati della pagina dettaglio ---
75    for i, book in enumerate(all_books):
76        details = scrape_book_detail(book["book_url"])
77        book.update(details)
78        print(f"[{i+1}/{len(all_books)}] {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
79        time.sleep(DELAY_DETAIL)
80    # --- Esporta in CSV ---
81    if all_books:
82        fieldnames = list(all_books[0].keys())
83        with open(OUTPUT_FILE, "w", newline="", encoding="utf-8") as f:
84            writer = csv.DictWriter(f, fieldnames=fieldnames)
85            writer.writeheader()
86            writer.writerows(all_books)
87        print(f"\nSalvati {len(all_books)} libri in {OUTPUT_FILE}")
88    else:
89        print("Nessun libro estratto.")
90if __name__ == "__main__":
91    main()

Con MAX_PAGES = 3, questo script raccoglie circa 300 libri dalla lista “Best Books Ever”, visita la pagina dettaglio di ogni libro e scrive tutto in un CSV. Sulla mia macchina richiede circa 25 minuti (soprattutto per via delle pause di 4 secondi tra le richieste alle pagine dettaglio). Il CSV di output avrà colonne come title, author, book_url, isbn, pages, avg_rating, rating_count, review_count, description, genres, language, format e published.

Esportare oltre il CSV: Google Sheets con gspread

Se vuoi i dati in Google Sheets invece che, o oltre, un CSV, aggiungi questo dopo l’esportazione CSV:

1import gspread
2from oauth2client.service_account import ServiceAccountCredentials
3scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
4creds = ServiceAccountCredentials.from_json_keyfile_name("credentials.json", scope)
5client = gspread.authorize(creds)
6sheet = client.open("Goodreads Scrape").sheet1
7header = list(all_books[0].keys())
8sheet.append_row(header)
9for book in all_books:
10    sheet.append_row([str(book.get(k, "")) for k in header])
11print("Dati inviati a Google Sheets.")

Ti servirà un service account Google Cloud con le API Sheets e Drive abilitate. La spiega la configurazione in circa 5 minuti. Usa operazioni batch (append_rows() con una lista di liste) se stai inserendo più di qualche centinaio di righe — il limite di Google è 300 richieste ogni 60 secondi per progetto.

Naturalmente, se tutta questa configurazione ti sembra eccessiva, Thunderbit esporta in Google Sheets, Airtable, Notion, Excel, CSV e JSON con — niente librerie da installare, nessun file credenziali, nessuna quota API.

L’alternativa no-code: estrarre Goodreads con Thunderbit

Non tutti vogliono mantenere uno script Python. Magari sei un editore che deve fare un’analisi spot del mercato, oppure un book blogger che vuole solo un foglio con i bestseller dell’anno. È proprio per questo scenario che abbiamo creato Thunderbit.

Come estrarre Goodreads con Thunderbit

  1. Installa l’estensione Chrome di Thunderbit dal e vai su una pagina lista, scaffale o risultati di ricerca di Goodreads.
  2. Clicca “AI Suggest Fields” nella sidebar di Thunderbit. L’AI legge la pagina e propone le colonne — di solito titolo, autore, URL della copertina e link al libro.
  3. Clicca “Scrape” — i dati vengono estratti in una tabella strutturata in pochi secondi.
  4. Esporta su Google Sheets, Excel, Airtable, Notion, CSV o JSON.

Per i dati dettagliati dei libri (ISBN, descrizione, generi, numero di pagine), la funzione di scraping delle sottopagine di Thunderbit visita automaticamente la pagina dettaglio di ciascun libro e arricchisce la tabella — senza cicli, senza timer, senza debug.

Thunderbit gestisce anche le liste paginate in modo nativo. Gli dici di cliccare “Next” o di scorrere, e raccoglie i dati da tutte le pagine senza scrivere una riga di codice.

Il compromesso è semplice: lo script Python ti dà pieno controllo ed è gratuito (a parte il tuo tempo), mentre Thunderbit sacrifica un po’ di flessibilità in cambio di un grande risparmio di tempo e zero manutenzione. Per una lista da 300 libri, lo script Python richiede circa 25 minuti di esecuzione più il tempo speso per scriverlo e correggerlo. Thunderbit ottiene gli stessi dati in circa 3 minuti, con due clic.

Estrarre Goodreads in modo responsabile: robots.txt, Termini di servizio ed etica

Qui serve una risposta chiara, non un disclaimer buttato lì.

Cosa dice davvero il robots.txt di Goodreads

Il robots.txt pubblico di Goodreads è sorprendentemente specifico. Le pagine dettaglio libro (/book/show/), le liste pubbliche (/list/show/), gli scaffali pubblici (/shelf/show/) e le pagine autore (/author/show/) non sono bloccate. Sono invece bloccati: /api, /book/reviews/, /review/list, /review/show, /search e altri percorsi. GPTBot e CCBot (Common Crawl) sono completamente bloccati con Disallow: /. C’è anche una direttiva Crawl-delay: 5 per bingbot, ma non un ritardo globale.

I Termini di servizio di Goodreads in parole semplici

I ToS (aggiornati l’ultima volta il 28 aprile 2021) vietano “qualsiasi uso di data mining, robot o strumenti simili di raccolta ed estrazione dati”. È un linguaggio molto ampio, quindi va preso sul serio — ma i tribunali hanno costantemente stabilito che la violazione dei ToS, da sola, non equivale a “accesso non autorizzato” in senso penale. La sentenza afferma che “criminalizzare le violazioni dei termini di servizio rischia di trasformare ogni sito web in una giurisdizione penale autonoma”.

Best practice

  • Inserisci pause tra le richieste: 3-8 secondi tra una richiesta e l’altra (il robots.txt di Goodreads suggerisce 5 secondi per i bot)
  • Resta sotto le 5.000 richieste al giorno da un singolo IP
  • Estrai solo pagine pubblicamente accessibili — evita scraping massivo di dati disponibili solo dopo login
  • Non ridistribuire commercialmente il testo grezzo delle recensioni — le recensioni sono opere creative protette da copyright
  • Conserva solo ciò che ti serve e definisci una policy di retention dei dati
  • Uso personale/ricerca vs commerciale: estrarre dati pubblici per analisi personale o ricerca accademica è generalmente accettato. Il rischio legale aumenta quando inizi a ridistribuire i dati commercialmente.

Usare uno strumento come Thunderbit (che estrae tramite la tua sessione browser) rende l’interazione visivamente identica alla navigazione normale, ma gli stessi principi etici valgono comunque a prescindere dallo strumento scelto. Se vuoi approfondire le , ne abbiamo parlato in un articolo dedicato.

Consigli e errori comuni

Consiglio: parti sempre da JSON-LD. Prima di scrivere selettori CSS complessi, controlla se i dati ti servono nel tag <script type="application/ld+json">. È più stabile, più facile da parsare e meno soggetto a rotture quando Goodreads aggiorna il frontend.

Consiglio: usa la strategia in due passaggi. Prima raccogli tutti gli URL dei libri dalle pagine lista, poi visita ogni pagina dettaglio. Così è più facile riprendere lo scraping se si interrompe a metà, e puoi salvare l’elenco degli URL su disco come checkpoint.

Errore: dimenticare di gestire campi mancanti. Non tutte le pagine libro hanno ISBN, tag di genere o descrizione. Usa sempre .get() con un valore di default, oppure proteggi i selettori con controlli if. Un solo errore NoneType può mandare in crash un’esecuzione di scraping durata 3 ore.

Errore: andare troppo veloci. Lo so, viene voglia di mettere time.sleep(0.5) e correre. Ma Goodreads comincia a rispondere con 403 dopo circa 20-30 richieste rapide, e una volta segnalato potresti dover aspettare ore o cambiare IP. Una pausa di 4-5 secondi è il punto giusto.

Errore: fidarsi dei tutorial vecchi. Se una guida cita l’API di Goodreads, o usa classi come .field.value o #bookTitle, probabilmente è superata. Verifica sempre i selettori sulla pagina live prima di costruire il tuo scraper.

Per approfondire la scelta degli strumenti e dei framework giusti, consulta le nostre guide su e .

Conclusione e punti chiave

Estrarre dati da Goodreads con Python è assolutamente possibile — basta sapere dove sono le insidie. In sintesi:

  • L’API di Goodreads non esiste più (da dicembre 2020). Lo scraping è oggi il modo principale per ottenere dati strutturati sui libri dalla piattaforma.
  • I risultati vuoti sono quasi sempre causati da contenuti renderizzati in JS, selettori obsoleti, header mancanti o problemi di autenticazione nella paginazione — non dal fatto che il tuo codice sia “sbagliato”.
  • JSON-LD è il tuo migliore alleato per i metadati dei libri. È stabile, strutturato e cambia di rado.
  • La paginazione richiede autenticazione per molte pagine lista e scaffale oltre la pagina 5. Includi il cookie _session_id2.
  • Il rate limiting è reale. Usa pause di 3-8 secondi e resta sotto le 5.000 richieste al giorno.
  • La strategia in due passaggi (prima gli URL, poi le pagine dettaglio) è più affidabile e più facile da riprendere.
  • Per chi non programma (o per chi ha poco tempo), gestisce tutto — rendering JS, paginazione, arricchimento delle sottopagine ed esportazione — in circa due clic.

Estrai dati con responsabilità, rispetta il robots.txt e speriamo che i tuoi dati da Goodreads tornino sempre con qualcosa di più di [].

FAQ

Si può ancora usare l’API di Goodreads?

No. Goodreads ha deprecato la sua API pubblica nel dicembre 2020 e non rilascia più nuove chiavi per sviluppatori. Le chiavi esistenti rimaste inattive per 30 giorni sono state disattivate automaticamente. Oggi le opzioni per accedere ai dati dei libri in modo programmatico sono lo scraping web o API alternative (come Open Library o Google Books).

Perché il mio scraper Goodreads restituisce risultati vuoti?

La causa più comune è il contenuto renderizzato in JavaScript. Goodreads carica recensioni, distribuzione delle valutazioni e molte sezioni di dettaglio tramite React/JavaScript, che una semplice chiamata requests.get() non può vedere. Per queste pagine passa a Selenium o Playwright. Altre cause possono essere selettori CSS obsoleti (Goodreads ha cambiato l’HTML), header User-Agent mancanti (che attivano blocchi 403) o richieste non autenticate sulle pagine scaffale paginate.

È legale fare scraping su Goodreads?

Estrarre dati pubblicamente disponibili per uso personale o di ricerca è generalmente accettato secondo i precedenti legali attuali (hiQ v. LinkedIn, Meta v. Bright Data). Tuttavia, i Termini di servizio di Goodreads vietano la raccolta automatizzata dei dati, e dovresti sempre controllare anche il loro robots.txt. Evita la ridistribuzione commerciale del testo delle recensioni protetto da copyright e limita il volume delle richieste per non sovraccaricare il sito.

Come faccio a estrarre più pagine su Goodreads?

Aggiungi ?page=N all’URL dello scaffale o della lista e fai un ciclo sui numeri di pagina. Controlla i risultati vuoti o l’assenza del link “next” per capire quando finisce la lista. Importante: alcune pagine scaffale richiedono autenticazione (cookie _session_id2) per mostrare risultati oltre la pagina 5 — senza di esso, riceverai in silenzio gli stessi dati della pagina 1 ripetuti.

Posso estrarre Goodreads senza scrivere codice?

Sì. è un’estensione Chrome che ti permette di estrarre dati da Goodreads in due clic — l’AI suggerisce i campi, tu clicchi “Scrape” e poi esporti direttamente su Google Sheets, Excel, Airtable o Notion. Gestisce automaticamente contenuti renderizzati in JavaScript, paginazione e arricchimento delle sottopagine, senza bisogno di Python o programmazione.

Scopri di più

Shuai Guan
Shuai Guan
Co-founder/CEO @ Thunderbit. Passionate about cross section of AI and Automation. He's a big advocate of automation and loves making it more accessible to everyone. Beyond tech, he channels his creativity through a passion for photography, capturing stories one picture at a time.
Indice

Prova Thunderbit

Estrai lead e altri dati in soli 2 clic. Potenziato dall’AI.

Scarica Thunderbit È gratis
Estrai dati con l’AI
Trasferisci facilmente i dati a Google Sheets, Airtable o Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week