Slik scraper du Goodreads med Python (uten flere tomme resultater)

Sist oppdatert April 16, 2026

Få ting er mer demotiverende enn å skrive 30 linjer Python, kjøre Goodreads-scraperen din og se at den bare returnerer []. En tom liste. Ingenting. Bare deg og den blinkende markøren.

Jeg har sett dette skje gang på gang — i egne interne eksperimenter hos , i utviklerfora og i GitHub-issues som hoper seg opp i forlatte scraper-repositorier. Klagen er nesten alltid den samme: "top reviews-delen er tom, den viser bare []", "uansett hvilket sidenummer jeg prøver, scraper den alltid første side", "koden min fungerte i fjor, nå er den ødelagt." Og som om ikke det var nok: Goodreads API ble faset ut i desember 2020, så rådet om å "bare bruke API-et" du finner i eldre guider, er en blindvei.

Hvis du vil hente strukturert bokdata fra Goodreads i dag — titler, forfattere, vurderinger, anmeldelser, sjangre, ISBN-er — er scraping den praktiske løsningen. Denne guiden viser deg en fungerende, komplett metode for å scrape Goodreads med Python, med dekning av JS-rendret innhold, paginering, anti-blokkering og eksport. Og hvis Python ikke er din greie, viser jeg også et kodefritt alternativ som fikser jobben på omtrent to klikk.

Hva er Goodreads-scraping, og hvorfor bruke Python?

Goodreads-scraping betyr at du automatisk henter bokdata — titler, forfattere, vurderinger, antall anmeldelser, sjangre, ISBN-er, sidetall, utgivelsesdatoer og mer — fra Goodreads-sider ved hjelp av kode, i stedet for å kopiere for hånd.

Goodreads er en av verdens største bokdatabaser, med over og rundt . Over 18 millioner bøker legges til "Want to Read"-hyller hver måned. Den typen løpende oppdatert, strukturert data er nettopp grunnen til at forlag, data scientists, bokhandlere og forskere fortsetter å komme tilbake.

Python er standardspråket for denne typen arbeid — det driver rundt av alle scraping-prosjekter. Bibliotekene er modne (requests, BeautifulSoup, Selenium, Playwright, pandas), syntaksen er nybegynnervennlig, og miljøet rundt er enormt.

Hvis du aldri har scraped en nettside før, er Python et veldig godt sted å starte.

Hvorfor scrape Goodreads med Python? Bruksområder i praksis

Før vi går til koden, er det verdt å spørre: hvem trenger egentlig denne dataen, og hva brukes den til?

BruksområdeHvem har nytte av detHva du scraper
Markedsresearch for forlagForlag, litterære agenterTrendende sjangre, topprangerte titler, nye forfattere, konkurrenters vurderinger
Anbefalingssystemer for bøkerData scientists, hobbyister, apputviklereVurderinger, sjangre, brukerhyller, sentiment i anmeldelser
Pris- og lagerovervåkingBokhandlere på nettTrendende titler, antall anmeldelser, antall "Want to Read"
Akademisk forskningForskere, studenterAnmeldelsestekst, fordeling av vurderinger, sjangerklassifiseringer
LeseranalyseBokbloggere, personlige prosjekterPersonlige hyller, lesehistorikk, årssammendrag

Noen konkrete eksempler: UCSD Book Graph — et av de mest siterte akademiske datasettene innen anbefalingsforskning — inneholder , alt hentet fra offentlig tilgjengelige Goodreads-hyller. Flere Kaggle-datasett (goodbooks-10k, Best Books Ever, osv.) har sitt opphav i Goodreads-scraping. Og en studie fra 2025 i Big Data and Society samlet inn for å analysere hvordan sponsede anmeldelser påvirker plattformen.

På den kommersielle siden selger Bright Data forhåndsscrapede Goodreads-datasett for så lite som $0,50 per 1 000 poster — et tydelig tegn på at denne dataen har reell markedsverdi.

Goodreads API er borte — dette er det som tok over

Hvis du har søkt etter "Goodreads API" nylig, har du sannsynligvis landet på en utdatert guide. 8. desember 2020 sluttet Goodreads stille å utstede nye utviklernøkler til API-et. Ingen bloggpost, ingen e-postutsendelse — bare et lite varsel på dokumentasjonssiden og mange forvirrede utviklere.

goodreads-data-access-tools.webp

Følgene kom raskt. En utvikler, Kyle K, bygde en Discord-bot for å dele bokanbefalinger — "plutselig bare POOF, så slutter den å fungere." En annen, Matthew Jones, mistet API-tilgang en uke før avstemningen til Reddit r/Fantasy Stabby Awards, og måtte gå tilbake til Google Forms. En masterstudent ved navn Elena Neacsu fikk prosjektet sitt til avhandlingen sin satt ut av spill midt i utviklingen.

Så hva gjenstår? Landskapet ser slik ut nå:

MetodeTilgjengelige dataBrukervennlighetRate limitsStatus
Goodreads APIFull metadata, anmeldelserEnkelt (var det)1 forespørsel/sekUtfaset (des 2020) — ingen nye nøkler
Open Library APITitler, forfattere, ISBN-er, omslag (~30M titler)Enkelt1–3 forespørsler/sekAktiv, gratis, uten autentisering
Google Books APIMetadata, forhåndsvisningerEnkelt1 000/dag gratisAktiv (mangler noe for ikke-engelske ISBN-er)
Python scraping (requests + BS4)Alt som ligger i den første HTML-enMiddelsDu styrer selvFungerer for statisk innhold
Python scraping (Selenium/Playwright)Også JS-rendret innholdVanskeligereDu styrer selvNødvendig for anmeldelser, enkelte lister
Thunderbit (kodefri Chrome-utvidelse)Alle synlige data på sidenVeldig enkelt (2 klikk)KreditbasertAktiv — ingen Python nødvendig

Open Library er et sterkt supplement, spesielt for ISBN-oppslag og grunnleggende metadata. Men hvis du trenger vurderinger, anmeldelser, sjanger-tags eller antall "Want to Read", scraper du Goodreads direkte — enten med Python eller med et verktøy som Thunderbit, som kan scrape Goodreads-sider (inkludert undersider for bokdetaljer) med AI-forslåtte felt og direkte eksport til Google Sheets, Notion eller Airtable.

Hvorfor returnerer Goodreads Python-scraperen din tomme resultater? Slik fikser du det

Dette er delen jeg skulle ønske fantes da jeg begynte å jobbe med Goodreads-data. Problemet med "tomme resultater" er den klart vanligste klagen i utviklerfora, og det har flere ulike årsaker — hver med sin egen løsning.

SymptomÅrsakLøsning
Anmeldelser/vurderinger returnerer []JS-rendret innhold (React/lazy-load)Bruk Selenium eller Playwright i stedet for requests
Scraper alltid bare side 1Paginering ignoreres eller styres av JSSend riktig ?page=N-parameter; bruk nettleserautomatisering for infinite scroll
Koden fungerte i fjor, men feiler nåGoodreads har endret HTML-klassenavnBruk robuste selektorer (JSON-LD, data-testid-attributter)
403/blokkert etter noen få forespørslerManglende headere / for raske forespørslerLegg til User-Agent, time.sleep(), roter proxies
Innloggingsmur på hyller/listsiderCookie/sesjon krevesBruk requests.Session() med cookies eller scraping via nettleser

JS-rendret innhold: anmeldelser og vurderinger vises tomme

Goodreads bruker en frontend basert på React. Når du kaller requests.get() på en bokside, får du den initielle HTML-en — men anmeldelser, fordeling av vurderinger og mange "mer info"-seksjoner lastes inn asynkront via JavaScript. Scraperen din kan bokstavelig talt ikke se dem.

Løsningen: for sider der du trenger JS-rendret innhold, bytt til Selenium eller Playwright. Playwright er min anbefaling for nye prosjekter — det er takket være WebSocket-basert protokoll, og har bedre innebygd stealth og async-støtte.

troubleshooting-empty-array-causes.webp

Paginering som bare returnerer side 1

Denne er lumsk. Du skriver en løkke, øker ?page=N, men får likevel de samme resultatene hver gang. På Goodreads returnerer hyllesider stille innholdet fra side 1 uansett ?page=-parameter dersom du ikke er autentisert. Ingen feilmelding, ingen redirect — bare samme første side om og om igjen.

Løsningen: inkluder en autentisert sesjons-cookie (spesifikt _session_id2). Mer om dette i pagineringsdelen lenger ned.

Kode som fungerte i fjor, men feiler nå

Goodreads endrer med jevne mellomrom HTML-klassenavn og sidens struktur. Det populære GitHub-repositoriet maria-antoniak/goodreads-scraper har nå en permanent melding: "This project is unmaintained and no longer functioning." Løsningen er å bruke mer robuste selektorer — JSON-LD-strukturdata (som følger schema.org-standarder og sjelden endrer seg) eller data-testid-attributter i stedet for skjøre klassenavn.

403-feil eller blokkering

Python-biblioteket requests har et annet TLS-fingeravtrykk enn Chrome. Selv med en Chrome User-Agent-streng kan bot-deteksjonssystemer som AWS WAF (som Goodreads bruker, siden de er et Amazon-datterselskap) oppdage avviket. Løsningen: legg til realistiske nettleser-headere, bruk time.sleep()-pauser på 3–8 sekunder mellom forespørsler, og vurder curl_cffi for TLS-fingeravtrykksmatching hvis du skal scrape i stor skala.

Innloggingsmurer på hyller og listsider

Noen Goodreads-hylle- og listsider krever autentisering for å vise fullt innhold, særlig etter side 5. Bruk requests.Session() med cookies eksportert fra nettleseren din, eller bruk Selenium/Playwright med en innlogget profil. Thunderbit håndterer dette naturlig siden den kjører i din egen innloggede Chrome-nettleser.

Før du starter

  • Vanskelighetsgrad: Middels (grunnleggende Python-kunnskap forutsettes)
  • Tidsbruk: Ca. 20–30 minutter for hele gjennomgangen
  • Du trenger:
    • Python 3.8+
    • Chrome-nettleser (for DevTools-inspeksjon og Selenium/Playwright)
    • Biblioteker: requests, beautifulsoup4, selenium eller playwright, pandas
    • (Valgfritt) gspread for eksport til Google Sheets
    • (Valgfritt) for det kodefrie alternativet

goodreads-scraping-flow.webp

Steg 1: Sett opp Python-miljøet ditt

Installer nødvendige biblioteker. Åpne terminalen og kjør:

1pip install requests beautifulsoup4 selenium pandas lxml

Hvis du foretrekker Playwright (anbefalt for nye prosjekter):

1pip install playwright
2playwright install chromium

For eksport til Google Sheets (valgfritt):

1pip install gspread oauth2client

Sørg for at du bruker Python 3.8 eller nyere. Du kan sjekke med python --version.

Etter installasjonen skal du kunne importere alle bibliotekene uten feil. Prøv python -c "import requests, bs4, pandas; print('Ready')" for å bekrefte.

Steg 2: Send din første forespørsel med riktige headere

Åpne en Goodreads-genrehylle eller en listsiden i nettleseren — for eksempel https://www.goodreads.com/list/show/1.Best_Books_Ever. Nå henter vi den siden med 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}")

Du bør se Status: 200. Hvis du får 403, sjekk headere på nytt — Goodreads' AWS WAF ser etter en realistisk User-Agent og vil avvise veldig enkle forespørsler. Headerne over etterligner en ekte Chrome-økt.

Steg 3: Inspiser siden og finn riktige selektorer

Åpne Chrome DevTools (F12) på Goodreads-listesiden. Høyreklikk på en boktittel og velg "Inspect". Da ser du DOM-strukturen for hver bokoppføring.

På listsider er hver bok vanligvis pakket inn i et <tr>-element med itemtype="http://schema.org/Book". Der finner du:

  • Tittel: a.bookTitle (lenketeksten er tittelen, href gir deg bok-URL-en)
  • Forfatter: a.authorName
  • Vurdering: span.minirating (inneholder gjennomsnittlig vurdering og antall vurderinger)
  • Omslagsbilde: img inne i bokraden

For individuelle boksider bør du hoppe over CSS-selektorer og gå rett til JSON-LD. Goodreads legger inn strukturert data i en <script type="application/ld+json">-tagg som følger schema.org Book-formatet. Det er mye mer stabilt enn klassenavn, som Goodreads endrer når de vil.

Steg 4: Hent bokdata fra én listsiden

La oss parse listen og hente grunnleggende info for hver bok:

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"Fant {len(books)} bøker på side 1")
28for b in books[:3]:
29    print(b)

Du bør se omtrent 100 bøker per listsiden. Hver oppføring vil ha en tittel, en forfatter, en vurderingstekst som for eksempel "4.28 avg rating — 9,031,257 ratings," og en URL til bokens detaljside.

Steg 5: Scrape undersider for detaljert bokinformasjon

Listesiden gir deg basisdata, men den virkelige gullgruven — ISBN, full beskrivelse, sjanger-tags, sidetall, publiseringsdato — ligger på hver enkelt boks side. Det er her JSON-LD virkelig skinner.

1import json
2import time
3def scrape_book_detail(book_url, headers):
4    """Gå til en enkelt bokside og hent detaljert metadata 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    # Sjanger-tags ligger ikke i JSON-LD; fall tilbake til HTML
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],  # forkortet for forhåndsvisning
25        "genres": ", ".join(genres[:5]),
26    }
27# Eksempel: berik de første 3 bøkene
28for book in books[:3]:
29    details = scrape_book_detail(book["book_url"], headers)
30    book.update(details)
31    print(f"Scrapet: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
32    time.sleep(4)  # respekter rate limits

Legg inn time.sleep() på 3–8 sekunder mellom forespørsler. Goodreads begynner å rate-limite rundt 20–30 forespørsler i minuttet fra én enkelt IP, og du vil fort begynne å se 403-er eller CAPTCHA-er hvis du går raskere.

Denne to-pass-metoden — først samle alle bok-URL-er fra listsider, deretter besøke hver detaljside — er mer stabil og lettere å gjenoppta hvis noe avbrytes. Det er strategien de mest vellykkede Goodreads-scraperne bruker.

Merk: kan gjøre dette automatisk med subpage-scraping. AI-en besøker hver bokdetaljside og beriker tabellen din med ISBN, beskrivelse, sjangre og mer — ingen kode, ingen løkker, ingen ventetider.

Steg 6: Håndter JavaScript-rendret innhold med Selenium

For sider der innholdet du trenger lastes via JavaScript — anmeldelser, fordeling av vurderinger, "more details"-seksjoner — trenger du et verktøy for nettleserautomatisering. Her er et Selenium-eksempel:

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# Vent på at anmeldelser skal lastes
14try:
15    WebDriverWait(driver, 10).until(
16        EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
17    )
18except:
19    print("Anmeldelsene ble ikke lastet — siden kan kreve innlogging eller JS-timeout")
20# Parse den fullt rendrerte siden
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"Vurdering: {stars.get_text(strip=True) if stars else 'N/A'}")
28    print(f"Anmeldelse: {text.get_text(strip=True)[:150] if text else 'N/A'}...")
29    print()
30driver.quit()

Når bør du bruke Selenium vs. requests:

  • Bruk requests + BeautifulSoup for bokmetadata (JSON-LD), listsider, hyllesider (side 1) og Choice Awards-data
  • Bruk Selenium eller Playwright for anmeldelser, vurderingsfordelinger og innhold som ikke vises i rå HTML

Playwright er som regel det beste valget for nye prosjekter — raskere, mindre minnebruk og bedre standardinnstillinger for stealth. Men Selenium har et større community og flere eksisterende kodeeksempler for Goodreads spesielt.

Paginering som faktisk fungerer: slik scraper du hele Goodreads-lister

Paginering er den vanligste feilkilden for Goodreads-scrapere, og jeg har ikke funnet en eneste konkurrerende guide som forklarer det ordentlig. Slik gjør du det riktig.

Slik fungerer paginerings-URL-ene på Goodreads

Goodreads bruker en enkel ?page=N-parameter for de fleste paginerte sider:

  • Lister: https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2
  • Hyller: https://www.goodreads.com/shelf/show/thriller?page=2
  • Søk: https://www.goodreads.com/search?q=fantasy&page=2

Hver listsiden viser vanligvis 100 bøker. Hyller viser 50 per side.

Slik skriver du en pagineringsløkke som vet når den skal stoppe

1import time
2all_books = []
3base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
4for page_num in range(1, 50):  # sikkerhetsgrense på 50 sider
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"Side {page_num}: fikk status {resp.status_code}, stopper.")
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"Side {page_num}: ingen bøker funnet, nådde slutten.")
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"Side {page_num}: scrapet {len(rows)} bøker (totalt: {len(all_books)})")
23    time.sleep(5)  # 5 sekunders pause mellom sider
24print(f"\nFerdig. Totalt antall bøker samlet: {len(all_books)}")

Du finner siste side ved å sjekke om resultatlisten er tom (ingen tr[itemtype="http://schema.org/Book"]-elementer) eller ved å se etter mangelen på en "next"-lenke (a.next_page).

Edge case: innlogging kreves etter side 5

Dette er fellen som fanger nesten alle: enkelte Goodreads-hylle- og listsider returnerer stille innholdet fra side 1 når du ber om side 6+ uten autentisering. Ingen feilmelding, ingen redirect — bare de samme dataene om og om igjen.

For å fikse dette, eksporter _session_id2-cookien fra nettleseren din (bruk en cookie-eksportutvidelse eller Chrome DevTools > Application > Cookies) og legg den inn i forespørslene dine:

1session = requests.Session()
2session.headers.update(headers)
3session.cookies.set("_session_id2", "DIN_SESSION_COOKIE_VERDI_HER", domain=".goodreads.com")
4# Bruk nå session.get() i stedet for requests.get()
5resp = session.get(f"{base_url}?page=6", timeout=15)

Thunderbit håndterer både klikkbasert paginering og infinite scroll nativt, uten kode og uten cookie-administrasjon. Hvis pagineringslogikken din stadig sprekker, er det verdt å vurdere.

Fullt Python-skript du kan kopiere og lime inn

Her er hele, samlede skriptet. Det håndterer headere, paginering, scraping av undersider via JSON-LD, rate limiting og CSV-eksport. Jeg har testet dette mot live Goodreads-sider per midten av 2025.

1"""
2goodreads_scraper.py — Scrape en Goodreads-liste med paginering og berikelse av bokdetaljer.
3Bruk: 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          # juster ved behov
16DELAY_LISTING = 5      # sekunder mellom listsider
17DELAY_DETAIL = 4       # sekunder mellom detaljsider
18OUTPUT_FILE = "goodreads_books.csv"
19def scrape_listing_page(url):
20    """Returner en liste med dict-er for tittel, forfatter og book_url fra én listsiden."""
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    """Gå til en bokside og hent metadata 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: samle bok-URL-er fra listsider ---
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"Side {page}: tom — stopper paginering.")
70            break
71        all_books.extend(page_books)
72        print(f"Side {page}: {len(page_books)} bøker (totalt: {len(all_books)})")
73        time.sleep(DELAY_LISTING)
74    # --- Pass 2: berik hver bok med data fra detaljsiden ---
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    # --- Eksporter til 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"\nLagret {len(all_books)} bøker til {OUTPUT_FILE}")
88    else:
89        print("Ingen bøker ble scrapet.")
90if __name__ == "__main__":
91    main()

Med MAX_PAGES = 3 samler dette skriptet rundt 300 bøker fra "Best Books Ever"-listen, besøker hver boks detaljside og skriver alt til en CSV. På min maskin tar det omtrent 25 minutter (mest på grunn av 4-sekunders pausene mellom detaljside-forespørslene). CSV-en din vil ha kolonner som title, author, book_url, isbn, pages, avg_rating, rating_count, review_count, description, genres, language, format og published.

Eksport utover CSV: Google Sheets med gspread

Hvis du vil ha dataen i Google Sheets i stedet for, eller i tillegg til, en CSV, kan du legge til dette etter CSV-eksporten:

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 sendt til Google Sheets.")

Du trenger en Google Cloud service account med Sheets- og Drive-API-ene aktivert. går gjennom oppsettet på rundt 5 minutter. Bruk batch-operasjoner (append_rows() med en liste av lister) hvis du sender mer enn noen få hundre rader — Googles rate limit er 300 forespørsler per 60 sekunder per prosjekt.

Hvis alt dette oppsettet føles som overkill, eksporterer Thunderbit til Google Sheets, Airtable, Notion, Excel, CSV og JSON med — ingen bibliotekoppsett, ingen credentials-fil, ingen API-kvoter.

Det kodefrie alternativet: scrape Goodreads med Thunderbit

Ikke alle vil vedlikeholde et Python-skript. Kanskje du er et forlag som gjør en engangs markedsanalyse, eller en bokblogger som bare vil ha et regneark med årets bestselgere. Det er akkurat for slike situasjoner vi bygde Thunderbit.

Slik scraper du Goodreads med Thunderbit

  1. Installer Thunderbit Chrome-utvidelsen fra og åpne en Goodreads-liste, hylle eller søkeside.
  2. Klikk "AI Suggest Fields" i Thunderbit-sidepanelet. AI-en leser siden og foreslår kolonner — typisk tittel, forfatter, vurdering, URL til omslagsbilde og lenke til boken.
  3. Klikk "Scrape" — dataene hentes inn i en strukturert tabell på få sekunder.
  4. Eksporter til Google Sheets, Excel, Airtable, Notion, CSV eller JSON.

For detaljert bokdata (ISBN, beskrivelse, sjangre, sidetall) bruker Thunderbit subpage scraping og besøker hver bokdetaljside automatisk for å berike tabellen — ingen løkker, ingen ventetider, ingen feilsøking.

Thunderbit håndterer også paginerte lister nativt. Du ber den klikke "Next" eller scrolle, og den samler data fra alle sider uten kode.

Avveiningen er enkel: Python-skriptet gir deg full kontroll og er gratis (bortsett fra tiden din), mens Thunderbit bytter litt fleksibilitet mot massiv tidsbesparelse og null vedlikehold. For en liste med 300 bøker tar Python-skriptet omtrent 25 minutter kjøretid, pluss tiden du brukte på å skrive og feilsøke det. Thunderbit henter samme data på omtrent 3 minutter, med to klikk.

Scrape Goodreads ansvarlig: robots.txt, bruksvilkår og etikk

Dette fortjener et tydelig svar, ikke en standardisert ansvarsfraskrivelse.

Hva Goodreads' robots.txt faktisk sier

Goodreads' live robots.txt er overraskende spesifikk. Bokdetaljsider (/book/show/), offentlige lister (/list/show/), offentlige hyller (/shelf/show/) og forfattersider (/author/show/) er ikke blokkert. Det som er blokkert: /api, /book/reviews/, /review/list, /review/show, /search og flere andre stier. GPTBot og CCBot (Common Crawl) er helt blokkert med Disallow: /. Det finnes en Crawl-delay: 5-direktiv for bingbot, men ingen global forsinkelse.

Goodreads bruksvilkår i klartekst

Bruksvilkårene (sist revidert 28. april 2021) forbyr "any use of data mining, robots, or similar data gathering and extraction tools." Det er bred formulering, og den er verdt å ta seriøst — men domstoler har konsekvent slått fast at brudd på vilkår alene ikke er det samme som kriminell "uautorisert tilgang." Avgjørelsen i sa at "criminalizing terms-of-service violations risks turning each website into its own criminal jurisdiction."

Beste praksis

  • Legg inn pauser mellom forespørsler: 3–8 sekunder mellom requests (Goodreads' egen robots.txt antyder 5 sekunder for roboter)
  • Hold deg under 5 000 forespørsler per dag fra én enkelt IP
  • Scrape bare offentlig tilgjengelige sider — unngå massehenting av data som bare er tilgjengelig etter innlogging
  • Ikke videredistribuer rå anmeldelsestekst kommersielt — anmeldelser er opphavsrettslige kreative verk
  • Lagre bare det du trenger og ha en plan for datalagring
  • Personlig forskning vs. kommersiell bruk: Scraping av offentlig data til personlig analyse eller akademisk forskning er vanligvis akseptert. Kommersiell videredistribusjon er der den juridiske risikoen øker.

Å bruke et verktøy som Thunderbit (som scraper via din egen nettlesersesjon) gjør samhandlingen visuelt identisk med vanlig nettlesing, men de samme etiske prinsippene gjelder uansett hvilket verktøy du velger. Hvis du vil gå dypere inn i , har vi dekket det separat.

Tips og vanlige fallgruver

Tips: Start alltid med JSON-LD. Før du skriver kompliserte CSS-selektorer, sjekk om dataene du trenger ligger i <script type="application/ld+json">-taggen. Den er mer stabil, enklere å parse og mindre sannsynlig å ryke når Goodreads oppdaterer frontend-en sin.

Tips: Bruk to-pass-strategien. Samle først alle bok-URL-er fra listsider, og besøk deretter hver detaljside. Det gjør scraperen lettere å gjenoppta hvis den krasjer halvveis, og du kan lagre URL-listen på disk som et sjekkpunkt.

Fallgruve: Glemme å håndtere manglende felt. Ikke alle boksider har ISBN, sjanger-tags eller beskrivelse. Bruk alltid .get() med standardverdi, eller pakk selektorer inn i if-sjekker. Én NoneType-feil kan stoppe et tre timer langt scraping-løp.

Fallgruve: Kjøre for fort. Jeg vet det er fristende å sette time.sleep(0.5) og bare blåse gjennom. Men Goodreads begynner å returnere 403-er etter omtrent 20–30 raske forespørsler, og når du først er flagget, kan du måtte vente i timer eller bytte IP. En pause på 4–5 sekunder er ofte det beste kompromisset.

Fallgruve: Stol på gamle guider. Hvis en guide refererer til Goodreads API, eller bruker klassenavn som .field.value eller #bookTitle, er den sannsynligvis utdatert. Verifiser alltid selektorer mot den levende siden før du bygger scraperen.

For mer om hvordan du velger riktige scraping-verktøy og rammeverk, se guidene våre om og .

Konklusjon og hovedpunkter

Det er fullt mulig å scrape Goodreads med Python — du må bare vite hvor fellene ligger. Kortversjonen:

  • Goodreads API er borte (siden desember 2020). Scraping er den primære måten å hente strukturert bokdata fra plattformen på.
  • Tomme resultater skyldes nesten alltid JS-rendret innhold, utdaterte selektorer, manglende headere eller autentiseringsproblemer ved paginering — ikke at koden din er feil.
  • JSON-LD er din beste venn for bokmetadata. Det er stabilt, strukturert og endrer seg sjelden.
  • Paginering krever autentisering for mange hylle- og listsider etter side 5. Inkluder _session_id2-cookien.
  • Rate limiting er reell. Bruk 3–8 sekunders pauser og hold deg under 5 000 forespørsler per dag.
  • To-pass-strategien (samle URL-er først, scrape detaljsider etterpå) er mer robust og kan gjenopptas.
  • For ikke-kodere (eller alle som verdsetter ettermiddagen sin), håndterer alt dette — JS-rendering, paginering, berikelse av undersider og eksport — på omtrent to klikk.

Scrape ansvarlig, respekter robots.txt, og måtte bokdataene dine alltid komme tilbake med mer enn [].

Vanlige spørsmål

Kan man fortsatt bruke Goodreads API?

Nei. Goodreads faset ut det offentlige API-et i desember 2020 og utsteder ikke lenger nye utviklernøkler. Eksisterende nøkler som var inaktive i 30 dager, ble automatisk deaktivert. Web scraping eller alternative API-er (som Open Library eller Google Books) er de aktuelle alternativene for å hente bokdata programmessig.

Hvorfor returnerer Goodreads-scraperen min tomme resultater?

Den vanligste årsaken er JavaScript-rendret innhold. Goodreads laster anmeldelser, fordelinger av vurderinger og mange detaljseksjoner via React/JavaScript, som en enkel requests.get()-forespørsel ikke kan se. Bytt til Selenium eller Playwright for disse sidene. Andre årsaker er utdaterte CSS-selektorer (Goodreads har endret HTML-en), manglende User-Agent-headere (som utløser 403-blokkeringer), eller ikke-autentiserte forespørsler på paginerte hyllesider.

Er det lov å scrape Goodreads?

Scraping av offentlig tilgjengelige data til personlig bruk eller forskning er generelt akseptert i henhold til gjeldende rettspraksis (hiQ v. LinkedIn, Meta v. Bright Data). Likevel forbyr Goodreads’ bruksvilkår automatisert datainnsamling, og du bør alltid gjennomgå robots.txt. Unngå kommersiell videredistribusjon av opphavsrettslig beskyttet anmeldelsestekst, og begrens forespørselsmengden din for ikke å belaste nettstedet unødvendig.

Hvordan scraper jeg flere sider på Goodreads?

Legg ?page=N til hylle- eller liste-URL-en og loop gjennom sidenumrene. Se etter tomme resultater eller fravær av en "next"-lenke for å finne siste side. Viktig: noen hyllesider krever autentisering (cookie-en _session_id2) for å gi resultater etter side 5 — uten den får du stille gjentatt data fra side 1.

Kan jeg scrape Goodreads uten å skrive kode?

Ja. er en Chrome-utvidelse som lar deg scrape Goodreads med to klikk — AI foreslår datafeltene, du klikker "Scrape", og eksporterer direkte til Google Sheets, Excel, Airtable eller Notion. Den håndterer JS-rendret innhold, paginering og berikelse av undersider automatisk, uten at du trenger Python eller koding.

Les mer

Innholdsfortegnelse

Prøv Thunderbit

Hent leads og andre data med bare 2 klikk. Drevet av AI.

Få Thunderbit Det er gratis
Hent data med AI
Overfør enkelt data til Google Sheets, Airtable eller Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week