Goodreads scrapen met Python: zo voorkom je lege resultaten

Laatst bijgewerkt op April 16, 2026

Er zijn maar weinig dingen zo frustrerend als 30 regels Python schrijven, je Goodreads-scraper starten en dan alleen [] terugzien. Een lege lijst. Niets. Alleen jij en je knipperende cursor.

Ik heb dit tientallen keren zien gebeuren — in onze eigen interne experimenten bij , op ontwikkelaarsfora en in de GitHub-issues van verlaten scraper-repositories. De klachten zijn bijna altijd hetzelfde: "de sectie met topreviews is leeg, ik zie alleen []", "welk paginanummer ik ook gebruik, hij scrapt steeds alleen de eerste pagina", "mijn code werkte vorig jaar nog, maar nu niet meer." En alsof dat nog niet genoeg is: de Goodreads API werd in december 2020 uitgefaseerd, dus het advies uit oudere tutorials om "gewoon de API te gebruiken" brengt je tegenwoordig nergens meer.

Als je nu gestructureerde boekdata van Goodreads wilt — titels, auteurs, beoordelingen, reviews, genres, ISBN’s — dan is scrapen de belangrijkste route. In deze gids laat ik je een werkende, complete aanpak zien om Goodreads met Python te scrapen, inclusief JS-gerenderde content, paginering, anti-blokkeermaatregelen en exports. En als Python niet jouw ding is, laat ik ook een no-code alternatief zien waarmee je dit in ongeveer twee klikken geregeld hebt.

Wat is Goodreads scrapen (en waarom zou je Python gebruiken)?

Goodreads scrapen betekent dat je automatisch boekdata — zoals titels, auteurs, beoordelingen, aantal reviews, genres, ISBN’s, paginacijfers en publicatiedata — uit Goodreads-webpagina’s haalt met code, in plaats van alles handmatig te kopiëren en plakken.

Goodreads is een van de grootste boekendatabases ter wereld, met meer dan en ongeveer . Elke maand belanden meer dan 18 miljoen boeken op een "Want to Read"-plank. Juist die continu bijgewerkte, gestructureerde data maakt Goodreads interessant voor uitgevers, data scientists, boekverkopers en onderzoekers.

Python is voor dit soort werk de standaardtaal — het drijft ongeveer van alle scrapingprojecten aan. De libraries zijn volwassen (requests, BeautifulSoup, Selenium, Playwright, pandas), de syntaxis is toegankelijk voor beginners en de community is enorm.

Als je nog nooit eerder een website hebt gescrapet, is Python een prima plek om te beginnen.

Waarom Goodreads scrapen met Python? Praktische toepassingen

Voordat we de code induiken, is het slim om even stil te staan bij de vraag: wie heeft deze data eigenlijk nodig, en wat doen ze ermee?

ToepassingWie heeft er baat bijWat je scrapt
Marktonderzoek voor uitgeversUitgevers, literaire agentenPopulaire genres, hoogst beoordeelde titels, opkomende auteurs, beoordelingen van concurrenten
BoekaanbevelingssystemenData scientists, hobbyisten, appbouwersRatings, genres, user shelves, sentiment in reviews
Prijs- en voorraadmonitoringOnline boekverkopersTrending titels, reviewvolume, aantallen "Want to Read"
Academisch onderzoekOnderzoekers, studentenReviewtekst, verdeling van ratings, genreclassificaties
LeesanalysesBoekbloggers, persoonlijke projectenEigen plankdata, leesgeschiedenis, jaaroverzichten

Een paar concrete voorbeelden: de UCSD Book Graph — een van de meest geciteerde academische datasets voor recommendation research — bevat , allemaal verzameld uit openbaar toegankelijke Goodreads-planken. Meerdere Kaggle-datasets (zoals goodbooks-10k, Best Books Ever, enz.) zijn ontstaan uit Goodreads-scraping. En een studie uit 2025 in Big Data and Society bracht computer-ondersteund in kaart om te analyseren hoe betaalde reviews het platform beïnvloeden.

Aan de commerciële kant verkoopt Bright Data voor-gescrapete Goodreads-datasets vanaf slechts $0,50 per 1.000 records — een duidelijk signaal dat deze data echte marktwaarde heeft.

De Goodreads API is weg — dit is wat daarvoor in de plaats kwam

Als je recent hebt gezocht op "Goodreads API", ben je waarschijnlijk op een verouderde tutorial uitgekomen. Op 8 december 2020 stopte Goodreads stilletjes met het uitgeven van nieuwe developer API-keys. Geen blogpost, geen e-mailcampagne — alleen een kleine melding op de documentatiepagina en een hoop verwarring bij ontwikkelaars.

goodreads-data-access-tools.webp

De gevolgen waren meteen merkbaar. Een ontwikkelaar, Kyle K, had een Discord-bot gebouwd om boekaanbevelingen te delen — "ineens poef, het werkt niet meer." Matthew Jones verloor API-toegang een week vóór de stemming voor de Reddit r/Fantasy Stabby Awards, waardoor hij moest terugvallen op Google Forms. Een masterstudent, Elena Neacsu, zag haar thesisproject halverwege vastlopen.

Dus wat blijft er over? Het landschap ziet er nu zo uit:

AanpakBeschikbare dataGebruiksgemakRate limitsStatus
Goodreads APIVolledige metadata, reviewsEenvoudig (was het)1 verzoek/secUitgefaseerd (dec 2020) — geen nieuwe keys
Open Library APITitels, auteurs, ISBN’s, covers (~30 miljoen titels)Eenvoudig1-3 verzoeken/secActief, gratis, geen auth
Google Books APIMetadata, previewsEenvoudig1.000/dag gratisActief (maar hiaten in niet-Engelse ISBN’s)
Python-scraping (requests + BS4)Alles in de initiële HTMLGemiddeldZelf te beherenWerkt voor statische content
Python-scraping (Selenium/Playwright)Ook JS-gerenderde contentMoeilijkerZelf te beherenNodig voor reviews en sommige lijsten
Thunderbit (no-code Chrome-extensie)Alle zichtbare data op een paginaZeer eenvoudig (2 klikken)Op credits gebaseerdActief — geen Python nodig

Open Library is een sterke aanvulling, vooral voor ISBN-zoekopdrachten en basismetadata. Maar als je ratings, reviews, genretags of aantallen "Want to Read" nodig hebt, dan scrape je Goodreads rechtstreeks — met Python of met een tool als Thunderbit, dat Goodreads-pagina’s (inclusief subpagina’s met boekdetails) kan scrapen met AI-voorgestelde velden en direct exporteren naar Google Sheets, Notion of Airtable.

Waarom je Goodreads Python-scraper lege resultaten teruggeeft (en hoe je dat fixt)

Dit is de sectie die ik zelf had willen hebben toen ik voor het eerst met Goodreads-data werkte. Het probleem met de "lege resultaten" is de meest voorkomende klacht in ontwikkelaarsfora, en er zijn meerdere oorzaken — elk met een eigen oplossing.

SymptoomOorzaakOplossing
Reviews/ratings geven [] terugJS-gerenderde content (React/lazy-load)Gebruik Selenium of Playwright in plaats van requests
Hij scrapt altijd alleen pagina 1Paginatie-parameters worden genegeerd of zijn JS-gestuurdGeef ?page=N correct mee; gebruik browserautomatisering bij infinite scroll
De code werkte vorig jaar nog, maar nu niet meerGoodreads heeft HTML-classnamen gewijzigdGebruik robuustere selectors (JSON-LD, data-testid-attributen)
403/blocked na een paar verzoekenOntbrekende headers / te snelle requestsVoeg een User-Agent toe, gebruik time.sleep(), roteer proxies
Login-wall op shelf-/lijstpagina’sCookie/sessie vereistGebruik requests.Session() met cookies of browser scraping

JS-gerenderde content: reviews en ratings blijven leeg

Goodreads gebruikt een React-gebaseerde frontend. Als je requests.get() op een boekpagina doet, krijg je de initiële HTML terug — maar reviews, ratingverdelingen en veel secties met "meer info" laden later via JavaScript. Je scraper kan ze letterlijk niet zien.

De oplossing: voor elke pagina waar je JS-gerenderde content nodig hebt, stap je over op Selenium of Playwright. Mijn voorkeur voor nieuwe projecten gaat uit naar Playwright — het is dankzij het WebSocket-gebaseerde protocol, en het heeft betere ingebouwde stealth- en async-ondersteuning.

troubleshooting-empty-array-causes.webp

Paginering die steeds alleen pagina 1 oplevert

Dit is een stiekeme valkuil. Je schrijft een loop, verhoogt ?page=N, en krijgt toch steeds dezelfde resultaten. Op Goodreads leveren shelf-pagina’s zonder authenticatie soms stilletjes de inhoud van pagina 1 terug, ongeacht de ?page=-parameter. Geen foutmelding, geen redirect — gewoon steeds dezelfde eerste pagina.

De oplossing: voeg een geauthenticeerde sessiecookie toe, specifiek _session_id2. Meer daarover in de sectie over paginering hieronder.

Code die vorig jaar nog werkte, werkt nu niet meer

Goodreads wijzigt af en toe HTML-classnamen en paginastructuren. De populaire GitHub-repo maria-antoniak/goodreads-scraper heeft inmiddels permanent de waarschuwing: "This project is unmaintained and no longer functioning." De oplossing is om robuustere selectors te gebruiken — JSON-LD structured data (volgens schema.org en zelden veranderend) of data-testid-attributen in plaats van breekbare classnamen.

403-fouten of geblokkeerd worden

De requests-library van Python heeft een andere TLS-fingerprint dan Chrome. Zelfs met een Chrome User-Agent kunnen botdetectiesystemen zoals AWS WAF (dat Goodreads gebruikt als Amazon-dochter) dat verschil herkennen. De oplossing: voeg realistische browserheaders toe, gebruik time.sleep()-vertragingen van 3-8 seconden tussen requests, en overweeg curl_cffi als je op grotere schaal scrape’t en TLS-fingerprint-matching nodig hebt.

Login-walls op shelves en lijstpagina’s

Sommige Goodreads shelf- en lijstpagina’s vereisen authenticatie om volledige content te zien, vooral na pagina 5. Gebruik requests.Session() met cookies die je uit je browser exporteert, of gebruik Selenium/Playwright met een ingelogd profiel. Thunderbit regelt dit natuurlijk, omdat het draait in jouw eigen, ingelogde Chrome-browser.

Voor je begint

  • Moeilijkheidsgraad: Gemiddeld (basiskennis Python verondersteld)
  • Benodigde tijd: Ongeveer 20-30 minuten voor de volledige walkthrough
  • Wat je nodig hebt:
    • Python 3.8+
    • Chrome-browser (voor DevTools-inspectie en Selenium/Playwright)
    • Libraries: requests, beautifulsoup4, selenium of playwright, pandas
    • (Optioneel) gspread voor export naar Google Sheets
    • (Optioneel) voor het no-code alternatief

goodreads-scraping-flow.webp

Stap 1: Je Python-omgeving opzetten

Installeer de vereiste libraries. Open je terminal en voer uit:

1pip install requests beautifulsoup4 selenium pandas lxml

Als je liever Playwright gebruikt (aanrader voor nieuwe projecten):

1pip install playwright
2playwright install chromium

Voor export naar Google Sheets (optioneel):

1pip install gspread oauth2client

Zorg dat je Python 3.8 of hoger gebruikt. Controleer dit met python --version.

Na installatie moet je alle libraries zonder fouten kunnen importeren. Probeer python -c "import requests, bs4, pandas; print('Ready')" om te testen of alles werkt.

Stap 2: Je eerste request sturen met de juiste headers

Open in je browser een Goodreads-genrepagina of lijstpagina — bijvoorbeeld https://www.goodreads.com/list/show/1.Best_Books_Ever. Laten we die pagina nu met Python ophalen.

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}")

Je zou Status: 200 moeten zien. Krijg je 403, controleer dan je headers nog eens — Goodreads’ AWS WAF let op een realistische User-Agent en weigert kale requests. De headers hierboven bootsen een echte Chrome-sessie na.

Stap 3: Inspecteer de pagina en vind de juiste selectors

Open Chrome DevTools (F12) op de Goodreads-lijstpagina. Klik met rechts op een boektitel en kies "Inspecteren". Je ziet dan de DOM-structuur van elk boekitem.

Op lijstpagina’s zit elk boek meestal in een <tr>-element met itemtype="http://schema.org/Book". Daarin vind je:

  • Titel: a.bookTitle (de linktekst is de titel, href geeft de boek-URL)
  • Auteur: a.authorName
  • Rating: span.minirating (bevat de gemiddelde rating en het aantal ratings)
  • Omslagafbeelding: img binnen de boekrij

Voor individuele boekdetailpagina’s kun je CSS-selectors beter overslaan en direct naar JSON-LD gaan. Goodreads plaatst gestructureerde data in een <script type="application/ld+json">-tag die het schema.org Book-formaat volgt. Die is veel stabieler dan classnamen, die Goodreads naar believen verandert.

Stap 4: Boekdata uit één lijstpagina halen

Laten we de lijstpagina parsen en basisinformatie van elk boek extraheren:

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"Gevonden {len(books)} boeken op pagina 1")
28for b in books[:3]:
29    print(b)

Je zou ongeveer 100 boeken per lijstpagina moeten zien. Elk item heeft een titel, auteur, een ratingstring zoals "4.28 avg rating — 9,031,257 ratings", en een URL naar de detailpagina van het boek.

Stap 5: Subpagina’s scrapen voor gedetailleerde boekinformatie

De overzichtspagina geeft je de basis, maar de echte goudmijn — ISBN, volledige beschrijving, genretags, aantal pagina’s, publicatiedatum — staat op de individuele boekpagina. Hier blinkt JSON-LD uit.

1import json
2import time
3def scrape_book_detail(book_url, headers):
4    """Bezoek een individuele boekpagina en haal metadata op 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    # Genretags zitten niet in JSON-LD; gebruik HTML als 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],  # inkorten voor preview
25        "genres": ", ".join(genres[:5]),
26    }
27# Voorbeeld: verrijk de eerste 3 boeken
28for book in books[:3]:
29    details = scrape_book_detail(book["book_url"], headers)
30    book.update(details)
31    print(f"Gescrapet: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
32    time.sleep(4)  # respecteer rate limits

Voeg tussen verzoeken een time.sleep() van 3-8 seconden toe. Goodreads begint rond 20-30 requests per minuut vanaf één IP rate limiting toe te passen, en bij hogere snelheid zie je al snel 403’s of CAPTCHAs.

Deze tweestappenaanpak — eerst alle boek-URL’s verzamelen uit lijstpagina’s en daarna elke detailpagina bezoeken — is betrouwbaarder en makkelijker te hervatten als iets halverwege misgaat. Dat is ook de strategie die de meest succesvolle Goodreads-scrapers gebruiken.

Terzijde: kan dit automatisch doen met subpage scraping. De AI bezoekt elke detailpagina van een boek en verrijkt je tabel met ISBN, beschrijving, genres en meer — geen code, geen loops, geen sleep-timers.

Stap 6: JavaScript-gerenderde content verwerken met Selenium

Voor pagina’s waarbij de content via JavaScript wordt geladen — reviews, rating breakdowns, secties met "meer details" — heb je een browserautomatiseringstool nodig. Hier is een Selenium-voorbeeld:

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# Wacht tot reviews geladen zijn
14try:
15    WebDriverWait(driver, 10).until(
16        EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
17    )
18except:
19    print("Reviews zijn niet geladen — mogelijk is login vereist of is de JS-timeout verlopen")
20# Parse nu de volledig gerenderde pagina
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"Rating: {stars.get_text(strip=True) if stars else 'N/A'}")
28    print(f"Review: {text.get_text(strip=True)[:150] if text else 'N/A'}...")
29    print()
30driver.quit()

Wanneer gebruik je Selenium vs. requests:

  • Gebruik requests + BeautifulSoup voor boekmetadata (JSON-LD), lijstpagina’s, shelf-pagina’s (pagina 1) en Choice Awards-data
  • Gebruik Selenium of Playwright voor reviews, ratingverdelingen en alles wat niet in de ruwe HTML verschijnt

Playwright is meestal de betere keuze voor nieuwe projecten — sneller, zuiniger met geheugen en met betere standaardinstellingen voor stealth. Maar Selenium heeft een grotere community en meer bestaande codevoorbeelden voor Goodreads specifiek.

Paginering die echt werkt: volledige Goodreads-lijsten scrapen

Paginering is de plek waar Goodreads-scrapers het vaakst stuklopen, en ik heb nog geen enkele concurrerende tutorial gezien die dit goed uitlegt. Zo doe je het wél goed.

Hoe Goodreads-paginatie-URL’s werken

Goodreads gebruikt voor de meeste gepagineerde pagina’s een eenvoudige ?page=N-parameter:

  • Lijsten: https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2
  • Shelves: https://www.goodreads.com/shelf/show/thriller?page=2
  • Zoeken: https://www.goodreads.com/search?q=fantasy&page=2

Elke lijstpagina toont doorgaans 100 boeken. Shelves tonen er 50 per pagina.

Een pagineloop schrijven die weet wanneer hij moet stoppen

1import time
2all_books = []
3base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
4for page_num in range(1, 50):  # veiligheidslimiet op 50 pagina's
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}, stoppen.")
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}: geen boeken gevonden, einde bereikt.")
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}: {len(rows)} boeken gescrapet (totaal: {len(all_books)})")
23    time.sleep(5)  # 5 seconden pauze tussen pagina's
24print(f"\nKlaar. Totaal aantal verzamelde boeken: {len(all_books)}")

Je herkent de laatste pagina aan het feit dat de resultatenlijst leeg is (geen tr[itemtype="http://schema.org/Book"]-elementen) of aan het ontbreken van een "next"-link (a.next_page).

Randgeval: login vereist na pagina 5

Dit is de valkuil waar bijna iedereen in trapt: sommige Goodreads shelf- en lijstpagina’s geven zonder authenticatie stilletjes gewoon opnieuw de content van pagina 1 terug zodra je pagina 6+ opvraagt. Geen foutmelding, geen redirect — alleen dezelfde data opnieuw.

Om dit op te lossen exporteer je de _session_id2-cookie uit je browser (gebruik een cookie-exportextensie of Chrome DevTools > Application > Cookies) en voeg je die toe aan je requests:

1session = requests.Session()
2session.headers.update(headers)
3session.cookies.set("_session_id2", "YOUR_SESSION_COOKIE_VALUE_HERE", domain=".goodreads.com")
4# Gebruik nu session.get() in plaats van requests.get()
5resp = session.get(f"{base_url}?page=6", timeout=15)

Thunderbit verwerkt click-based én infinite-scroll-paginering native, zonder code en zonder cookiebeheer. Als je paginatie steeds blijft breken, is het zeker het overwegen waard.

Het complete Python-script dat je direct kunt plakken

Hier is het volledige, samengevoegde script. Het regelt headers, paginering, subpage scraping via JSON-LD, rate limiting en CSV-export. Ik heb dit getest tegen live Goodreads-pagina’s medio 2025.

1"""
2goodreads_scraper.py — Scrape een Goodreads-lijst met paginering en verrijking via detailpagina's.
3Gebruik: 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          # pas aan indien nodig
16DELAY_LISTING = 5      # seconden tussen lijstpagina's
17DELAY_DETAIL = 4       # seconden tussen detailpagina's
18OUTPUT_FILE = "goodreads_books.csv"
19def scrape_listing_page(url):
20    """Geef een lijst van dicts terug met title, author en book_url van één lijstpagina."""
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    """Bezoek een boekpagina en haal metadata op via JSON-LD + HTML fallback."""
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    # --- Pass 1: verzamel boek-URL's uit lijstpagina's ---
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}: leeg — stoppen met pagineren.")
70            break
71        all_books.extend(page_books)
72        print(f"Pagina {page}: {len(page_books)} boeken (totaal: {len(all_books)})")
73        time.sleep(DELAY_LISTING)
74    # --- Pass 2: verrijk elk boek met data van de detailpagina ---
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    # --- Export naar 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"\n{len(all_books)} boeken opgeslagen in {OUTPUT_FILE}")
88    else:
89        print("Geen boeken gescrapet.")
90if __name__ == "__main__":
91    main()

Met MAX_PAGES = 3 haalt dit script ongeveer 300 boeken op uit de lijst "Best Books Ever", bezoekt elk boek op de detailpagina en schrijft alles weg naar een CSV. Op mijn machine duurt dit ongeveer 25 minuten (vooral door de 4-secondenvertraging tussen detailpagina’s). Je output-CSV bevat kolommen zoals title, author, book_url, isbn, pages, avg_rating, rating_count, review_count, description, genres, language, format en published.

Verder dan CSV exporteren: Google Sheets met gspread

Als je de data liever in Google Sheets hebt in plaats van — of naast — een CSV, voeg dan dit toe na de CSV-export:

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("Data naar Google Sheets gepusht.")

Je hebt hiervoor een Google Cloud service account nodig met de Sheets- en Drive-API’s ingeschakeld. De legt de setup in ongeveer 5 minuten uit. Gebruik batch-operaties (append_rows() met een lijst van lijsten) als je meer dan een paar honderd rijen verstuurt — de Google-rate limit is 300 verzoeken per 60 seconden per project.

Als dit allemaal als overkill voelt, exporteert Thunderbit met naar Google Sheets, Airtable, Notion, Excel, CSV en JSON — zonder library-installaties, zonder credentials-bestand en zonder API-quota’s.

Het no-code alternatief: Goodreads scrapen met Thunderbit

Niet iedereen wil een Python-script onderhouden. Misschien ben je een uitgever die één keer marktonderzoek wil doen, of een boekblogger die gewoon een spreadsheet van de bestsellers van dit jaar nodig heeft. Precies voor dat scenario hebben we Thunderbit gebouwd.

Hoe je Goodreads scrapt met Thunderbit

  1. Installeer de Thunderbit Chrome-extensie via de en open een Goodreads-lijst-, shelf- of zoekresultatenpagina.
  2. Klik op "AI Suggest Fields" in de Thunderbit-sidebar. De AI leest de pagina en stelt kolommen voor — meestal titel, auteur, rating, URL van de omslagafbeelding en boeklink.
  3. Klik op "Scrape" — de data wordt binnen enkele seconden uitgelezen naar een gestructureerde tabel.
  4. Exporteer naar Google Sheets, Excel, Airtable, Notion, CSV of JSON.

Voor gedetailleerde boekdata (ISBN, beschrijving, genres, aantal pagina’s) bezoekt Thunderbit met zijn subpage-scrapingfunctie automatisch elke detailpagina en verrijkt de tabel — zonder loops, zonder sleep-timers, zonder debuggen.

Thunderbit verwerkt ook gepagineerde lijsten vanzelf. Jij geeft aan dat hij op "Next" moet klikken of moet scrollen, en hij verzamelt data over alle pagina’s heen zonder code.

De afweging is simpel: met het Python-script heb je volledige controle en geen directe kosten (behalve je tijd), terwijl Thunderbit wat flexibiliteit inlevert voor enorme tijdswinst en geen onderhoud. Voor een lijst van 300 boeken kost het Python-script ongeveer 25 minuten draaitijd plus de tijd die je kwijt bent aan schrijven en debuggen. Thunderbit haalt dezelfde data in ongeveer 3 minuten op, met twee klikken.

Goodreads verantwoord scrapen: robots.txt, gebruiksvoorwaarden en ethiek

Dit verdient een eerlijk antwoord, niet een standaard disclaimerzin.

Wat Goodreads’ robots.txt daadwerkelijk zegt

De live robots.txt van Goodreads is verrassend specifiek. Boekdetailpagina’s (/book/show/), openbare lijsten (/list/show/), openbare shelves (/shelf/show/) en auteurspagina’s (/author/show/) zijn niet geblokkeerd. Wel geblokkeerd zijn: /api, /book/reviews/, /review/list, /review/show, /search en diverse andere paden. GPTBot en CCBot (Common Crawl) zijn volledig geblokkeerd met Disallow: /. Voor bingbot is er een Crawl-delay: 5, maar geen globale vertraging.

Goodreads’ gebruiksvoorwaarden in gewone taal

De ToS (voor het laatst herzien op 28 april 2021) verbieden "elk gebruik van data mining, robots of vergelijkbare tools voor het verzamelen en extraheren van data." Dat is brede taal, en je moet het serieus nemen — maar rechtbanken hebben consistent geoordeeld dat schending van een ToS op zichzelf geen strafbare "unauthorized access" vormt. De uitspraak in stelde dat "het strafbaar maken van schendingen van gebruiksvoorwaarden het risico meebrengt dat elke website zijn eigen strafrechtelijke jurisdictie wordt."

Best practices

  • Wacht tussen requests: 3-8 seconden tussen verzoeken (Goodreads’ eigen robots.txt suggereert 5 seconden voor bots)
  • Blijf onder 5.000 requests per dag vanaf één IP
  • Scrape alleen openbaar toegankelijke pagina’s — vermijd grootschalig scrapen van data die alleen voor ingelogde gebruikers zichtbaar is
  • Herpubliceer reviewtekst niet zomaar commercieel — reviews zijn auteursrechtelijk beschermde creatieve werken
  • Bewaar alleen wat je nodig hebt en hanteer een bewaartermijn
  • Persoonlijk onderzoek versus commercieel gebruik: openbaar toegankelijke data scrapen voor persoonlijke analyse of academisch onderzoek is meestal aanvaardbaar. Bij commerciĂ«le herdistributie neemt het juridische risico toe.

Een tool als Thunderbit (die via je eigen browsersessie scrapt) ziet er visueel hetzelfde uit als normaal browsen, maar dezelfde ethische principes blijven gelden, ongeacht je toolkeuze. Als je meer wilt weten over de , hebben we daar apart over geschreven.

Tips en veelvoorkomende valkuilen

Tip: Begin altijd met JSON-LD. Controleer vóór je ingewikkelde CSS-selectors schrijft eerst of de data die je nodig hebt in de <script type="application/ld+json">-tag zit. Dat is stabieler, makkelijker te parsen en breekt minder snel wanneer Goodreads hun frontend bijwerkt.

Tip: Gebruik de tweestappenstrategie. Verzamel eerst alle boek-URL’s uit lijstpagina’s en bezoek daarna elke detailpagina. Daardoor is je scraper makkelijker te hervatten als hij halverwege crasht, en je kunt de URL-lijst als checkpoint opslaan.

Valkuil: Vergeten om missende velden op te vangen. Niet elke boekpagina heeft een ISBN, genretags of een beschrijving. Gebruik altijd .get() met een standaardwaarde of controleer selectors met if. Eén NoneType-fout kan een scrapingrun van 3 uur laten crashen.

Valkuil: Te snel draaien. Ik snap dat het verleidelijk is om time.sleep(0.5) te zetten en erdoorheen te razen. Maar Goodreads begint na ongeveer 20-30 snelle requests 403’s terug te geven, en als je eenmaal geflagd bent, moet je soms uren wachten of van IP wisselen. Een vertraging van 4-5 seconden is meestal de sweet spot.

Valkuil: Oude tutorials vertrouwen. Als een gids verwijst naar de Goodreads API, of classnamen gebruikt zoals .field.value of #bookTitle, is hij waarschijnlijk verouderd. Controleer selectors altijd eerst op de live pagina voordat je je scraper bouwt.

Voor meer over het kiezen van de juiste scrapingtools en frameworks, bekijk onze gidsen over en .

Conclusie en belangrijkste inzichten

Goodreads scrapen met Python is prima te doen — je moet alleen weten waar de valkuilen zitten. De korte versie:

  • De Goodreads API is weg (sinds december 2020). Scraping is nu de belangrijkste manier om gestructureerde boekdata van het platform te halen.
  • Lege resultaten worden bijna altijd veroorzaakt door JS-gerenderde content, verouderde selectors, ontbrekende headers of authenticatieproblemen bij paginering — niet doordat je code per se fout is.
  • JSON-LD is je beste vriend voor boekmetadata. Het is stabiel, gestructureerd en verandert zelden.
  • Paginering vereist authenticatie voor veel shelf- en lijstpagina’s vanaf pagina 6. Voeg de _session_id2-cookie toe.
  • Rate limiting is echt. Gebruik vertragingen van 3-8 seconden en blijf onder 5.000 requests per dag.
  • De tweestappenstrategie (eerst URL’s verzamelen, dan detailpagina’s scrapen) is betrouwbaarder en makkelijker te hervatten.
  • Voor niet-programmeurs (of iedereen die zijn middag waardeert) regelt dit allemaal — JS-rendering, paginering, verrijking van subpagina’s en export — in ongeveer twee klikken.

Scrape verantwoord, respecteer robots.txt en moge je boekdata altijd meer teruggeven dan [].

Veelgestelde vragen

Kun je de Goodreads API nog gebruiken?

Nee. Goodreads heeft zijn publieke API in december 2020 uitgefaseerd en geeft geen nieuwe developer keys meer uit. Bestaande keys die 30 dagen inactief waren, werden automatisch gedeactiveerd. Webscraping of alternatieve API’s (zoals Open Library of Google Books) zijn momenteel de opties om programmatisch boekdata op te halen.

Waarom geeft mijn Goodreads-scraper lege resultaten terug?

De meest voorkomende oorzaak is JavaScript-gerenderde content. Goodreads laadt reviews, ratingverdelingen en veel detailsecties via React/JavaScript, en die ziet een simpele requests.get()-call niet. Gebruik Selenium of Playwright voor die pagina’s. Andere oorzaken zijn verouderde CSS-selectors (Goodreads heeft de HTML aangepast), ontbrekende User-Agent-headers (waardoor 403-blokkades ontstaan) of niet-geauthenticeerde requests op gepagineerde shelf-pagina’s.

Is het legaal om Goodreads te scrapen?

Openbaar toegankelijke data scrapen voor persoonlijk of onderzoeksgebruik wordt over het algemeen geaccepteerd onder de huidige juridische precedenten (hiQ v. LinkedIn, Meta v. Bright Data). Goodreads’ gebruiksvoorwaarden verbieden echter geautomatiseerd gegevens verzamelen, en je moet altijd hun robots.txt bekijken. Vermijd commerciële herdistributie van auteursrechtelijk beschermde reviewtekst en beperk je requestvolume om de site niet te belasten.

Hoe scrape ik meerdere pagina’s op Goodreads?

Voeg ?page=N toe aan de shelf- of lijst-URL en loop door de paginanummers. Controleer op lege resultaten of het ontbreken van een "next"-link om de laatste pagina te herkennen. Belangrijk: sommige shelf-pagina’s vereisen authenticatie (de _session_id2-cookie) om resultaten voorbij pagina 5 terug te geven — zonder die cookie krijg je stilletjes steeds opnieuw pagina 1.

Kan ik Goodreads scrapen zonder code te schrijven?

Ja. is een Chrome-extensie waarmee je Goodreads in twee klikken kunt scrapen — AI stelt de velden voor, jij klikt op "Scrape" en exporteert direct naar Google Sheets, Excel, Airtable of Notion. Het verwerkt JS-gerenderde content, paginering en verrijking van subpagina’s automatisch, zonder Python of programmeerkennis.

Meer lezen

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.
Inhoudsopgave

Probeer Thunderbit

Scrape leads en andere data in slechts 2 klikken. Aangedreven door AI.

Thunderbit ophalen Het is gratis
Data extraheren met AI
Gegevens eenvoudig overzetten naar Google Sheets, Airtable of Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week