Det finns få saker som känns mer nedslående än att skriva 30 rader Python, köra din Goodreads-scraper och få tillbaka []. En tom lista. Ingenting. Bara du och den blinkande markören.
Jag har sett det här hända om och om igen — i våra egna interna tester på , i utvecklarforum och i GitHub-ärenden som samlas på övergivna scraper-repon. Klagen är nästan alltid desamma: "top reviews-sektionen är tom, den visar bara []", "vilket sidnummer jag än kör så hämtar den alltid första sidan", "min kod fungerade förra året, nu är den trasig." Och som om det inte räckte så avvecklades Goodreads API i december 2020, så rådet om att "bara använda API:t" som du hittar i äldre guider leder ingen vart.
Om du vill ha strukturerad bokdata från Goodreads i dag — titlar, författare, betyg, recensioner, genrer, ISBN och mer — är scraping det som gäller. Den här guiden visar ett fungerande, komplett sätt att extrahera Goodreads-data med Python, med stöd för JS-renderat innehåll, paginering, anti-blockering och export. Och om Python inte är din grej visar jag också ett no-code-alternativ som löser jobbet på ungefär två klick.
Vad är Goodreads-scraping, och varför göra det med Python?
Goodreads-scraping betyder att man automatiskt hämtar bokdata — titlar, författare, betyg, antal recensioner, genrer, ISBN, antal sidor, publiceringsdatum och mer — från Goodreads webbsidor med kod i stället för att kopiera och klistra in manuellt.
Goodreads är en av världens största bokdatabaser, med över och ungefär . Varje månad läggs över 18 miljoner böcker till på "Want to Read"-hyllor. Den typen av ständigt uppdaterad, strukturerad data är precis varför förlag, data scientists, bokhandlare och forskare återkommer gång på gång.
Python är det självklara språket för den här typen av arbete — det driver omkring av alla scraping-projekt. Biblioteken är mogna (requests, BeautifulSoup, Selenium, Playwright, pandas), syntaxen är nybörjarvänlig och communityn är enorm.
Om du aldrig har extraherat data från en webbplats tidigare är Python en bra plats att börja på.
Varför extrahera Goodreads-data med Python? Verkliga användningsområden
Innan vi går in på koden är det värt att fråga: vem behöver egentligen den här datan, och vad gör de med den?
| Användningsområde | Vem har nytta av det | Vad du extraherar |
|---|---|---|
| Marknadsundersökning för förlag | Förlag, litterära agenter | Trendande genrer, topprankade titlar, nya författare, konkurrenters betyg |
| Rekommendationssystem för böcker | Data scientists, hobbyutvecklare, appbyggare | Betyg, genrer, användarhyllor, känslan i recensioner |
| Pris- och lagerbevakning | Bokhandlare inom e-handel | Trendande titlar, antal recensioner, "Want to Read"-siffror |
| Akademisk forskning | Forskare, studenter | Recensionstext, fördelning av betyg, genreklassificering |
| Läsanalys | Bokbloggare, personliga projekt | Egen hylldata, läshistorik, årsstatistik |
Några konkreta exempel: UCSD Book Graph — en av de mest citerade akademiska dataseten inom rekommendationsforskning — innehåller , allt insamlat från offentligt tillgängliga Goodreads-hyllor. Flera Kaggle-dataseten (goodbooks-10k, Best Books Ever, med flera) har sitt ursprung i Goodreads-scraping. Och en studie från 2025 i Big Data and Society sammanställde för att analysera hur betalda recensioner påverkar plattformen.
På den kommersiella sidan säljer Bright Data förskrapade Goodreads-dataset för så lite som 0,50 dollar per 1 000 poster — ett tydligt bevis på att datan har ett verkligt marknadsvärde.
Goodreads API är borta — här är vad som ersatte det
Om du nyligen har sökt efter "Goodreads API" har du säkert hamnat i en guide som är föråldrad. Den 8 december 2020 slutade Goodreads tyst att utfärda nya API-nycklar för utvecklare. Inget blogginlägg, inget mejlutskick — bara en liten banner på dokumentationssidan och många förvirrade utvecklare.

Effekterna kom direkt. En utvecklare, Kyle K, byggde en Discord-bot för att dela bokrekommendationer — "plötsligt POFF så slutar allt bara fungera." En annan, Matthew Jones, förlorade API-åtkomst en vecka före omröstningen till Reddit r/Fantasy Stabby Awards och tvingades gå tillbaka till Google Forms. En doktorand vid namn Elena Neacsu fick sitt masterarbete stoppat mitt i utvecklingen.
Så vad återstår? Läget ser ut ungefär så här:
| Tillvägagångssätt | Tillgänglig data | Användarvänlighet | Rate limits | Status |
|---|---|---|---|---|
| Goodreads API | Full metadata, recensioner | Enkelt (var) | 1 begäran/sek | Avvecklat (dec 2020) — inga nya nycklar |
| Open Library API | Titlar, författare, ISBN, omslag (~30 miljoner titlar) | Enkelt | 1–3 begäran/sek | Aktivt, gratis, ingen autentisering |
| Google Books API | Metadata, förhandsvisningar | Enkelt | 1 000/dag gratis | Aktivt (luckor för icke-engelska ISBN) |
| Python scraping (requests + BS4) | Allt som finns i den initiala HTML-koden | Medel | Egenuppskött | Fungerar för statiskt innehåll |
| Python scraping (Selenium/Playwright) | Även JS-renderat innehåll | Svårare | Egenuppskött | Krävs för recensioner och vissa listor |
| Thunderbit (no-code Chrome-tillägg) | All synlig data på sidan | Mycket enkelt (2 klick) | Kreditbaserat | Aktivt — ingen Python behövs |
Open Library är ett starkt komplement, särskilt för ISBN-sökningar och grundläggande metadata. Men om du behöver betyg, recensioner, genre-taggar eller antalet "Want to Read"-markeringar, då extraherar du Goodreads direkt — antingen med Python eller med ett verktyg som Thunderbit, som kan extrahera Goodreads-sidor (inklusive undersidor för bokdetaljer) med AI-föreslagna fält och export direkt till Google Sheets, Notion eller Airtable.
Varför din Goodreads Python-scraper ger tomma resultat — och hur du fixar det
Det här är avsnittet jag önskar hade funnits när jag först började jobba med Goodreads-data. Problemet med "tomma resultat" är den absolut vanligaste klagan i utvecklarforum, och det finns flera olika orsaker — med varsin lösning.
| Symptom | Grundorsak | Lösning |
|---|---|---|
Recensioner/betyg returnerar [] | JS-renderat innehåll (React/lazy-load) | Använd Selenium eller Playwright i stället för requests |
| Hämtar alltid bara sida 1 | Paginering ignoreras eller styrs av JS | Skicka ?page=N korrekt; använd webbläsarautomation för oändlig scroll |
| Koden fungerade förra året, men inte nu | Goodreads ändrade HTML-klassnamn | Använd robusta selektorer (JSON-LD, data-testid-attribut) |
| 403/blockering efter några få förfrågningar | Saknade headers / för snabb takt | Lägg till User-Agent, time.sleep(), rotera proxies |
| Inloggningsvägg på hyll- och listsidor | Cookie/session krävs | Använd requests.Session() med cookies eller webbläsarscraping |
JS-renderat innehåll: recensioner och betyg visas som tomt
Goodreads kör ett React-baserat frontend. När du kör requests.get() på en bok-sida får du den inledande HTML-koden — men recensioner, betygsfördelningar och många avsnitt med "mer info" laddas asynkront via JavaScript. Din scraper kan bokstavligen inte se dem.
Lösningen: när du behöver JS-renderat innehåll, byt till Selenium eller Playwright. För nya projekt rekommenderar jag Playwright — det är tack vare sin WebSocket-baserade protokollmodell, och har bättre inbyggt stealth-stöd samt async-hantering.

Paginering som bara returnerar sida 1
Den här är lurig. Du skriver en loop, ökar ?page=N, men får ändå samma resultat varje gång. På Goodreads returnerar hyllsidor tyst innehållet från sida 1 oavsett ?page=-parametern om du inte är autentiserad. Inget felmeddelande, ingen omdirigering — bara samma första sida om och om igen.
Lösningen: inkludera en autentiserad sessionscookie (specifikt _session_id2). Mer om detta i avsnittet om paginering längre ned.
Kod som fungerade förra året slutar fungera
Goodreads ändrar regelbundet HTML-klassnamn och sidstruktur. Det populära GitHub-repot maria-antoniak/goodreads-scraper har nu en permanent notis: "This project is unmaintained and no longer functioning." Lösningen är att använda mer robusta selektorer — JSON-LD-strukturerad data (som följer schema.org och sällan ändras) eller data-testid-attribut i stället för sköra klassnamn.
403-fel eller blockering
Python-biblioteket requests har en annan TLS-fingerprint än Chrome. Även med en Chrome User-Agent-sträng kan bot-detekteringssystem som AWS WAF (som Goodreads använder eftersom tjänsten ägs av Amazon) upptäcka skillnaden. Lösningen: lägg till realistiska webbläsarheaders, införa time.sleep()-pauser på 3–8 sekunder mellan förfrågningar och överväg curl_cffi för att matcha TLS-fingerprint om du kör större scrapingjobb.
Inloggningsvägg på hyllor och listsidor
Vissa Goodreads-hyllor och listsidor kräver inloggning för att ge full åtkomst, särskilt efter sida 5. Använd requests.Session() med cookies exporterade från webbläsaren, eller använd Selenium/Playwright med en inloggad profil. Thunderbit hanterar detta naturligt eftersom verktyget körs i din egen inloggade Chrome-webbläsare.
Innan du börjar
- Svårighetsgrad: Medel (grundläggande Python-kunskap förutsätts)
- Tidsåtgång: Cirka 20–30 minuter för hela genomgången
- Det här behöver du:
- Python 3.8+
- Chrome-webbläsare (för DevTools-inspektion och Selenium/Playwright)
- Bibliotek:
requests,beautifulsoup4,seleniumellerplaywright,pandas - (Valfritt)
gspreadför export till Google Sheets - (Valfritt) som no-code-alternativ

Steg 1: Sätt upp din Python-miljö
Installera de bibliotek du behöver. Öppna terminalen och kör:
1pip install requests beautifulsoup4 selenium pandas lxml
Om du föredrar Playwright (rekommenderas för nya projekt):
1pip install playwright
2playwright install chromium
För export till Google Sheets (valfritt):
1pip install gspread oauth2client
Se till att du kör Python 3.8 eller högre. Det kan du kontrollera med python --version.
Efter installationen ska du kunna importera alla bibliotek utan fel. Testa python -c "import requests, bs4, pandas; print('Ready')" för att bekräfta.
Steg 2: Skicka din första begäran med rätt headers
Öppna en Goodreads genre-sida eller listsida i webbläsaren — till exempel https://www.goodreads.com/list/show/1.Best_Books_Ever. Nu ska vi hämta sidan 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. Om du får 403, kontrollera headers igen — Goodreads AWS WAF letar efter en realistisk User-Agent och blockerar enkla begäranden. Headers ovan efterliknar en riktig Chrome-session.
Steg 3: Inspektera sidan och hitta rätt selektorer
Öppna Chrome DevTools (F12) på Goodreads-listan. Högerklicka på en boktitel och välj "Inspect". Då ser du DOM-strukturen för varje bokpost.
För listsidor är varje bok vanligtvis innesluten i ett <tr>-element med itemtype="http://schema.org/Book". Inuti hittar du:
- Titel:
a.bookTitle(länktexten är titeln,hrefger bokens URL) - Författare:
a.authorName - Betyg:
span.minirating(innehåller genomsnittsbetyg och antal betyg) - Omslagsbild:
imgi bokraden
För enskilda boksidor ska du hoppa över CSS-selektorerna och gå direkt till JSON-LD. Goodreads bäddar in strukturerad data i en <script type="application/ld+json">-tagg som följer schema.org Book-formatet. Det är mycket stabilare än klassnamn, som Goodreads ändrar när de känner för det.
Steg 4: Extrahera bokdata från en enda listsida
Låt oss parsa listsidan och plocka ut grundläggande information för varje 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"Hittade {len(books)} böcker på sida 1")
28for b in books[:3]:
29 print(b)
Du bör se ungefär 100 böcker per listsida. Varje post får en titel, författare, en betygssträng som "4.28 avg rating — 9,031,257 ratings" och en URL till bokens detaljsida.
Steg 5: Extrahera undersidor för mer detaljerad bokinformation
Listan ger dig grunderna, men det riktiga guldet — ISBN, full beskrivning, genre-taggar, antal sidor och publiceringsdatum — finns på varje enskild boksida. Här är JSON-LD perfekt.
1import json
2import time
3def scrape_book_detail(book_url, headers):
4 """Besök en enskild boksida och extrahera detaljerad 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 # Genre-taggar finns inte i JSON-LD; använd HTML som 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], # trunkerat för förhandsvisning
25 "genres": ", ".join(genres[:5]),
26 }
27# Exempel: berika de första 3 böckerna
28for book in books[:3]:
29 details = scrape_book_detail(book["book_url"], headers)
30 book.update(details)
31 print(f"Extraherad: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
32 time.sleep(4) # respektera rate limits
Lägg in en time.sleep() på 3–8 sekunder mellan förfrågningarna. Goodreads börjar begränsa runt 20–30 förfrågningar per minut från samma IP, och du kommer att börja se 403-fel eller CAPTCHA om du går snabbare.
Det här tvåstegsflödet — först samla alla bok-URL:er från listsidor, sedan besöka varje detaljsida — är mer robust och lättare att återuppta om något avbryts. Det är också den strategi som de mest lyckade Goodreads-scraperna använder.
Sidonotis: kan göra detta automatiskt med subpage scraping. AI:n besöker varje boks detaljsida och fyller på din tabell med ISBN, beskrivning, genrer och mer — ingen kod, inga loopar, inga sleep-timers.
Steg 6: Hantera JS-renderat innehåll med Selenium
För sidor där innehållet laddas via JavaScript — recensioner, betygsfördelningar, avsnittet med "mer detaljer" — behöver du ett verktyg för webbläsarautomation. Här är ett Selenium-exempel:
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# Vänta på att recensionerna laddas
14try:
15 WebDriverWait(driver, 10).until(
16 EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
17 )
18except:
19 print("Recensioner laddades inte — sidan kan kräva inloggning eller så tog JS för lång tid")
20# Parsea nu den fullt renderade sidan
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"Betyg: {stars.get_text(strip=True) if stars else 'N/A'}")
28 print(f"Recension: {text.get_text(strip=True)[:150] if text else 'N/A'}...")
29 print()
30driver.quit()
När ska du använda Selenium i stället för requests:
- Använd
requests+ BeautifulSoup för bokmetadata (JSON-LD), listsidor, hyllsidor (sida 1) och Choice Awards-data - Använd Selenium eller Playwright för recensioner, betygsfördelningar och allt innehåll som inte syns i rå HTML
Playwright är oftast det bättre valet för nya projekt — snabbare, mindre minneskrävande och med bättre standard för stealth. Men Selenium har ett större community och fler befintliga kodexempel för just Goodreads.
Paginering som faktiskt fungerar: extrahera hela Goodreads-listor
Paginering är den vanligaste felkällan för Goodreads-scrapers, och jag har inte sett en enda konkurrerande guide som förklarar det ordentligt. Så här gör du rätt.
Hur Goodreads-pagineringens URL:er fungerar
Goodreads använder en enkel ?page=N-parameter för de flesta paginerade sidor:
- Listor:
https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2 - Hyllor:
https://www.goodreads.com/shelf/show/thriller?page=2 - Sök:
https://www.goodreads.com/search?q=fantasy&page=2
Varje listsida visar vanligtvis 100 böcker. Hyllor visar 50 per sida.
Skriva en pagineringsloop som vet när den ska sluta
1import time
2all_books = []
3base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
4for page_num in range(1, 50): # säkerhetsgräns på 50 sidor
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"Sida {page_num}: fick status {resp.status_code}, avbryter.")
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"Sida {page_num}: inga böcker hittades, slutet är nått.")
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"Sida {page_num}: extraherade {len(rows)} böcker (totalt: {len(all_books)})")
23 time.sleep(5) # 5 sekunders paus mellan sidor
24print(f"\nKlart. Totalt antal insamlade böcker: {len(all_books)}")
Du upptäcker sista sidan genom att kontrollera om resultatlistan är tom (inga tr[itemtype="http://schema.org/Book"]-element hittas) eller genom att titta efter avsaknaden av en "next"-länk (a.next_page).
Specialfall: inloggning krävs efter sida 5
Det här är fällan som nästan alla går i: vissa Goodreads-hyllor och listsidor returnerar tyst innehållet från sida 1 när du begär sida 6+ utan autentisering. Ingen felkod, ingen omdirigering — bara samma data igen.
För att fixa det kan du exportera cookien _session_id2 från din webbläsare (använd ett cookie-export-tillägg eller Chrome DevTools > Application > Cookies) och lägga in den i dina anrop:
1session = requests.Session()
2session.headers.update(headers)
3session.cookies.set("_session_id2", "YOUR_SESSION_COOKIE_VALUE_HERE", domain=".goodreads.com")
4# Använd nu session.get() istället för requests.get()
5resp = session.get(f"{base_url}?page=6", timeout=15)
Thunderbit hanterar både klickbaserad paginering och infinite scroll nativt, utan kod och utan cookiehantering. Om din pagineringslogik hela tiden går sönder är det värt att överväga.
Det kompletta Python-skriptet, redo att klistra in
Här är hela det samlade skriptet. Det hanterar headers, paginering, extrahering av undersidor via JSON-LD, rate limiting och CSV-export. Jag har testat detta mot live Goodreads-sidor så sent som i mitten av 2025.
1"""
2goodreads_scraper.py — Extrahera en Goodreads-lista med paginering och berikning av bokdetaljer.
3Användning: python goodreads_scraper.py
4Utdata: 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 # justera vid behov
16DELAY_LISTING = 5 # sekunder mellan listsidor
17DELAY_DETAIL = 4 # sekunder mellan detaljsidor
18OUTPUT_FILE = "goodreads_books.csv"
19def scrape_listing_page(url):
20 """Returnera en lista med dicts för titel, författare och book_url från en listsida."""
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 """Besök en boksida och extrahera 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: samla bok-URL:er från listsidor ---
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"Sida {page}: tom — avbryter pagineringen.")
70 break
71 all_books.extend(page_books)
72 print(f"Sida {page}: {len(page_books)} böcker (totalt: {len(all_books)})")
73 time.sleep(DELAY_LISTING)
74 # --- Pass 2: berika varje bok med data från detaljsidan ---
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 # --- Exportera till 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"\nSparade {len(all_books)} böcker till {OUTPUT_FILE}")
88 else:
89 print("Inga böcker extraherades.")
90if __name__ == "__main__":
91 main()
Med MAX_PAGES = 3 samlar det här skriptet omkring 300 böcker från listan "Best Books Ever", besöker varje boks detaljsida och skriver allt till en CSV. På min dator tar det ungefär 25 minuter (mest på grund av 4-sekunderspauserna mellan detaljsidorna). Din utdata-CSV kommer att ha kolumner som title, author, book_url, isbn, pages, avg_rating, rating_count, review_count, description, genres, language, format och published.
Exportera bortom CSV: Google Sheets med gspread
Om du vill ha datan i Google Sheets i stället för, eller utöver, en CSV kan du lägga till detta efter CSV-exporten:
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 skickad till Google Sheets.")
Du behöver ett Google Cloud-servicekonto med Sheets- och Drive-API:erna aktiverade. går igenom uppsättningen på ungefär 5 minuter. Använd batch-operationer (append_rows() med en lista av listor) om du skickar fler än några hundra rader — Googles rate limit är 300 förfrågningar per 60 sekunder per projekt.
Om allt detta känns som överkurs exporterar Thunderbit till Google Sheets, Airtable, Notion, Excel, CSV och JSON med — ingen biblioteksinstallation, ingen credentials-fil, inga API-kvoter.
No-code-alternativet: extrahera Goodreads med Thunderbit
Alla vill inte underhålla ett Python-script. Kanske är du ett förlag som gör en engångsanalys av marknaden, eller en bokbloggare som bara vill ha ett kalkylblad med årets bästsäljare. Det är exakt sådana fall Thunderbit är byggt för.
Så extraherar du Goodreads med Thunderbit
- Installera Thunderbit Chrome-tillägget från och gå till en Goodreads-lista, hylla eller sökresultatsida.
- Klicka på "AI Suggest Fields" i Thunderbits sidopanel. AI:n läser sidan och föreslår kolumner — vanligtvis titel, författare, betyg, URL till omslagsbild och boklänk.
- Klicka på "Scrape" — datan extraheras till en strukturerad tabell på några sekunder.
- Exportera till Google Sheets, Excel, Airtable, Notion, CSV eller JSON.
För detaljerad bokdata (ISBN, beskrivning, genrer, antal sidor) besöker Thunderbits subpage scraping-funktion varje boks detaljsida och berikar tabellen automatiskt — inga loopar, inga sleep-timers, ingen felsökning.
Thunderbit hanterar också paginerade listor nativt. Du talar om för det att klicka på "Next" eller scrolla, så samlar det in data över alla sidor utan någon kod alls.
Avvägningen är enkel: Python-skriptet ger dig full kontroll och är gratis, bortsett från din tid, medan Thunderbit byter lite flexibilitet mot enorm tidsbesparing och noll underhåll. För en lista med 300 böcker tar Python-skriptet cirka 25 minuter att köra, plus tiden du lade på att skriva och felsöka det. Thunderbit hämtar samma data på ungefär 3 minuter, med två klick.
Scraping av Goodreads på ett ansvarsfullt sätt: robots.txt, användarvillkor och etik
Det här förtjänar ett rakt svar, inte ett slentrianmässigt ansvarsfriskrivningsstycke.
Vad Goodreads robots.txt faktiskt säger
Goodreads live-robots.txt är förvånansvärt specifik. Bokdetaljsidor (/book/show/), offentliga listor (/list/show/), offentliga hyllor (/shelf/show/) och författarsidor (/author/show/) är inte blockerade. Det som är blockerat är /api, /book/reviews/, /review/list, /review/show, /search och flera andra sökvägar. GPTBot och CCBot (Common Crawl) är helt blockerade med Disallow: /. Det finns en Crawl-delay: 5 för bingbot, men ingen global fördröjning.
Goodreads användarvillkor på vanlig svenska
Villkoren (senast reviderade 28 april 2021) förbjuder "any use of data mining, robots, or similar data gathering and extraction tools." Det är breda formuleringar, och det är värt att ta på allvar — men domstolar har konsekvent slagit fast att enbart brott mot användarvillkor inte i sig utgör brottet "unauthorized access". Beslutet i slog fast att "att kriminalisera brott mot användarvillkor riskerar att göra varje webbplats till sin egen straffrättsliga jurisdiktion."
Bästa praxis
- Lägg in fördröjning mellan förfrågningar: 3–8 sekunder mellan anrop (Goodreads egna robots.txt föreslår 5 sekunder för bots)
- Håll dig under 5 000 förfrågningar per dag från en enda IP-adress
- Extrahera bara offentligt tillgängliga sidor — undvik mass-scraping av data som bara finns bakom inloggning
- Återpublicera inte rå recensionstext kommersiellt — recensioner är upphovsrättsskyddade kreativa verk
- Spara bara det du behöver och ha en tydlig policy för datalagring
- Personlig forskning vs kommersiell användning: Att extrahera offentlig data för personlig analys eller akademisk forskning är generellt accepterat. Kommersiell vidareförsäljning är där den juridiska risken ökar.
Att använda ett verktyg som Thunderbit (som extraherar via din egen webbläsarsession) gör interaktionen visuellt identisk med vanlig surfning, men samma etiska principer gäller oavsett verktyg. Om du vill fördjupa dig i har vi skrivit mer om det separat.
Tips och vanliga fallgropar
Tips: Börja alltid med JSON-LD. Innan du skriver komplicerade CSS-selektorer, kolla om datan du behöver finns i <script type="application/ld+json">-taggen. Den är stabilare, enklare att parsa och mindre benägen att gå sönder när Goodreads uppdaterar frontend.
Tips: Använd tvåstegsstrategin. Samla först alla bok-URL:er från listsidor, och besök sedan varje detaljsida. Det gör scrapen lättare att återuppta om något kraschar halvvägs, och du kan spara URL-listan på disk som en checkpoint.
Fallgrop: Glömmer att hantera saknade fält. Alla boksidor har inte ISBN, genre-taggar eller en beskrivning. Använd alltid .get() med standardvärde, eller skydda selektorer med if-kontroller. Ett enda NoneType-fel kan krascha en 3 timmar lång scrapingkörning.
Fallgrop: Kör för snabbt. Jag vet att det är frestande att sätta time.sleep(0.5) och rusa igenom allt. Men Goodreads börjar ge 403-fel efter ungefär 20–30 snabba förfrågningar, och när du väl har flaggats kan du behöva vänta i timmar eller byta IP. En fördröjning på 4–5 sekunder är oftast bästa balansen.
Fallgrop: Lita på gamla guider. Om en guide hänvisar till Goodreads API, eller använder klassnamn som .field.value eller #bookTitle, är den förmodligen föråldrad. Verifiera alltid selektorerna mot den live-sidan innan du bygger din scraper.
För mer om hur du väljer rätt scrapingverktyg och ramverk, kolla våra guider om och .
Slutsats och viktigaste lärdomar
Att extrahera Goodreads-data med Python är absolut möjligt — du behöver bara veta var fallgroparna finns. Kortversionen:
- Goodreads API är borta (sedan december 2020). Scraping är det främsta sättet att få strukturerad bokdata från plattformen.
- Tomma resultat orsakas nästan alltid av JS-renderat innehåll, gamla selektorer, saknade headers eller autentiseringsproblem vid paginering — inte av att din kod är "fel".
- JSON-LD är din bästa vän för bokmetadata. Den är stabil, strukturerad och ändras sällan.
- Paginering kräver autentisering för många hyll- och listsidor efter sida 5. Lägg in cookien
_session_id2. - Rate limiting är på riktigt. Använd 3–8 sekunders fördröjningar och håll dig under 5 000 förfrågningar per dag.
- Tvåstegsstrategin (samla URL:er först, extrahera detaljsidor sedan) är mer pålitlig och lättare att återuppta.
- För icke-kodare (eller alla som värdesätter sin eftermiddag) hanterar allt detta — JS-rendering, paginering, berikning av undersidor och export — på ungefär två klick.
Extrahera ansvarsfullt, respektera robots.txt, och må din bokdata alltid komma tillbaka med mer än [].
Vanliga frågor
Går det fortfarande att använda Goodreads API?
Nej. Goodreads avvecklade sitt publika API i december 2020 och utfärdar inte längre nya utvecklarnycklar. Befintliga nycklar som varit inaktiva i 30 dagar deaktiverades automatiskt. Web scraping eller alternativa API:er (som Open Library eller Google Books) är de aktuella alternativen för programmatisk åtkomst till bokdata.
Varför ger min Goodreads-scraper tomma resultat?
Den vanligaste orsaken är JavaScript-renderat innehåll. Goodreads laddar recensioner, betygsfördelningar och många detaljavsnitt via React/JavaScript, vilket en enkel requests.get()-anrop inte kan se. Byt till Selenium eller Playwright för de sidorna. Andra orsaker är gamla CSS-selektorer (Goodreads har ändrat HTML), saknade User-Agent-headers (som utlöser 403-blockering), eller oautentiserade anrop till paginerade hyllsidor.
Är det lagligt att extrahera data från Goodreads?
Att extrahera offentligt tillgänglig data för personligt bruk eller forskning är generellt accepterat enligt nuvarande rättspraxis (hiQ v. LinkedIn, Meta v. Bright Data). Däremot förbjuder Goodreads användarvillkor automatiserad datainsamling, och du bör alltid granska deras robots.txt. Undvik kommersiell vidarepublicering av upphovsrättsskyddad recensionstext och begränsa mängden förfrågningar för att inte belasta webbplatsen i onödan.
Hur extraherar jag flera sidor på Goodreads?
Lägg till ?page=N i hyll- eller list-URL:en och loopa genom sidnumren. Kontrollera om resultatet är tomt eller om en "next"-länk saknas för att hitta sista sidan. Viktigt: vissa hyllsidor kräver autentisering (cookien _session_id2) för att ge resultat efter sida 5 — utan den får du tyst samma data från sida 1 om och om igen.
Kan jag extrahera Goodreads utan att skriva kod?
Ja. är ett Chrome-tillägg som låter dig extrahera Goodreads på två klick — AI föreslår fälten, du klickar på "Scrape" och exporterar direkt till Google Sheets, Excel, Airtable eller Notion. Det hanterar automatiskt JS-renderat innehåll, paginering och berikning av undersidor, utan att du behöver Python eller kod alls.
Läs mer