Jak scrapovat Goodreads v Pythonu (už žádné prázdné výsledky)

Naposledy aktualizováno April 16, 2026

Pár věcí dokáže člověka zklamat víc než napsat 30 řádků v Pythonu, spustit Goodreads scraper a místo dat dostat []. Prázdný seznam. Nic. Jen ty a blikající kurzor.

Tohle jsem viděl už desítkykrát — v našich interních experimentech v , ve vývojářských fórech i v issuech na GitHubu u dávno opuštěných scraper repozitářů. Stížnosti jsou skoro vždycky stejné: "sekce s top recenzemi je prázdná, vrací jen []", "ať spustím jakékoliv číslo stránky, vždycky mi to stáhne jen první stránku", "kód fungoval loni, teď je rozbitý." A aby toho nebylo málo, Goodreads API bylo v prosinci 2020 zrušeno, takže rada typu „prostě použij API“, kterou najdeš ve starších návodech, je dnes slepá ulička.

Jestli dnes chceš z Goodreads získat strukturovaná data o knihách — názvy, autory, hodnocení, recenze, žánry, ISBN — scraping je hlavní cesta. V tomhle průvodci ti ukážu funkční a kompletní postup, jak scrapovat Goodreads v Pythonu, včetně obsahu renderovaného JavaScriptem, stránkování, ochrany proti blokování a exportu. A pokud Python není tvoje parketa, ukážu i no-code alternativu, která to zvládne zhruba na dvě kliknutí.

Co je scraping Goodreads a proč ho dělat v Pythonu?

Scraping Goodreads znamená automatizované získávání dat o knihách — názvy, autoři, hodnocení, počet recenzí, žánry, ISBN, počet stran, datum vydání a další — přímo z webových stránek Goodreads pomocí kódu místo ručního kopírování.

Goodreads je jedna z největších knižních databází na světě, s více než a přibližně . Každý měsíc se na „Want to Read“ police dostane přes 18 milionů knih. Právě taková průběžně aktualizovaná a strukturovaná data jsou důvodem, proč se k ní pořád vracejí vydavatelé, datoví analytici, knihkupci i výzkumníci.

Python je pro tenhle typ práce volba číslo jedna — pohání zhruba všech scrapingových projektů. Knihovny jsou vyspělé (requests, BeautifulSoup, Selenium, Playwright, pandas), syntaxe je přívětivá pro začátečníky a komunita obrovská.

Pokud jsi nikdy předtím web nescrapoval, Python je skvělé místo, kde začít.

Proč scrapovat Goodreads v Pythonu? Praktické případy použití

Než se pustíme do kódu, stojí za to položit si otázku: kdo vlastně ta data potřebuje a co s nimi dělá?

Případ použitíKdo z toho těžíCo scrapujete
Průzkum trhu pro vydavateleVydavatelé, literární agentiTrendující žánry, nejlépe hodnocené tituly, nové autory, hodnocení konkurence
Doporučovací systémy pro knihyDatoví analytici, nadšenci, vývojáři aplikacíHodnocení, žánry, uživatelské police, sentiment recenzí
Sledování cen a zásobOnline knihkupciTrendující tituly, objem recenzí, počet „Want to Read“
Akademický výzkumVýzkumníci, studentiTexty recenzí, rozložení hodnocení, kategorizace žánrů
Analýza čteníBook blogeři, osobní projektyData z osobních polic, historie čtení, statistiky za rok

Pár konkrétních příkladů: UCSD Book Graph — jeden z nejčastěji citovaných akademických datasetů v oblasti doporučovacích systémů — obsahuje , vše sesbírané z veřejně dostupných Goodreads polic. Několik Kaggle datasetů (goodbooks-10k, Best Books Ever atd.) původně vzniklo právě scrapingem Goodreads. A studie z roku 2025 v Big Data and Society strojově zpracovala , aby analyzovala, jak sponzorované recenze ovlivňují platformu.

Z obchodního hlediska Bright Data prodává předem nascrapované Goodreads datasety už od 0,50 USD za 1 000 záznamů — jasný důkaz, že tahle data mají reálnou hodnotu.

Goodreads API zmizelo — co ho nahradilo?

Pokud jsi v poslední době hledal „Goodreads API“, nejspíš jsi narazil na návod, který je dávno zastaralý. Dne 8. prosince 2020 Goodreads potichu přestal vydávat nové vývojářské API klíče. Žádný blogový post, žádný e-mail — jen malý banner v dokumentaci a spousta zmatených vývojářů.

goodreads-data-access-tools.webp

Dopady byly okamžité. Jeden vývojář, Kyle K, vytvořil Discord bota na sdílení knižních doporučení — „najednou to PUF prostě přestalo fungovat.“ Jiný, Matthew Jones, přišel o přístup k API týden před hlasováním v Redditu pro r/Fantasy Stabby Awards, takže se musel vrátit ke Google Forms. Studentka magistrského studia Elena Neacsu měla rozpracovaný projekt diplomky a vše se jí rozpadlo uprostřed vývoje.

Co tedy zbývá? Dnešní možnosti vypadají takto:

PřístupDostupná dataJednoduchost použitíRate limitStav
Goodreads APIKompletní metadata, recenzeSnadné (dříve)1 požadavek/sZrušeno (prosinec 2020) — nové klíče nejsou
Open Library APINázvy, autoři, ISBN, obaly (~30 mil. titulů)Snadné1–3 požadavky/sAktivní, zdarma, bez autentizace
Google Books APIMetadata, náhledySnadné1 000/den zdarmaAktivní (chybí některá ISBN mimo angličtinu)
Python scraping (requests + BS4)Cokoliv v počátečním HTMLStředně náročnéSpravujete samiFunguje pro statický obsah
Python scraping (Selenium/Playwright)I obsah renderovaný JSNáročnějšíSpravujete samiNutné pro recenze a některé seznamy
Thunderbit (no-code Chrome rozšíření)Jakákoli viditelná data na stránceVelmi snadné (2 kliknutí)Na kredityAktivní — Python není potřeba

Open Library je výborný doplněk, hlavně pro dohledávání ISBN a základních metadat. Pokud ale potřebuješ hodnocení, recenze, štítky žánrů nebo počty „Want to Read“, scrapuješ Goodreads přímo — buď v Pythonu, nebo pomocí nástroje jako Thunderbit, který umí Goodreads stránky (včetně podstránek s detaily knih) scrapovat s AI navrženými poli a rovnou exportovat do Google Sheets, Notion nebo Airtable.

Proč váš Goodreads Python scraper vrací prázdné výsledky (a jak to opravit)

Tohle je sekce, kterou bych si přál mít k dispozici, když jsem s Goodreads daty začínal. Problém „prázdných výsledků“ je ve vývojářských fórech úplně nejčastější stížnost a může mít několik různých příčin — každá má jiné řešení.

PříznakPříčinaŘešení
Recenze/hodnocení vrací []Obsah renderovaný JS (React / lazy-load)Použijte Selenium nebo Playwright místo requests
Vždy se scrapuje jen první stránkaParametr stránkování je ignorovaný nebo řízený JSPředávejte správně ?page=N; pro infinite scroll použijte automatizaci prohlížeče
Kód fungoval loni, teď selháváGoodreads změnil názvy HTML třídPoužijte odolnější selektory (JSON-LD, atributy data-testid)
403 / blokace po pár požadavcíchChybějící hlavičky / příliš rychlé požadavkyPřidejte User-Agent, time.sleep(), střídejte proxy
Login wall na stránkách polic / seznamůNutná cookie / sessionPoužijte requests.Session() s cookies nebo scraping přes prohlížeč

Obsah renderovaný JavaScriptem: recenze a hodnocení jsou prázdné

Goodreads používá frontend postavený na Reactu. Když zavoláš requests.get() na stránku knihy, dostaneš počáteční HTML — ale recenze, rozložení hodnocení a mnoho sekcí „více informací“ se načítá asynchronně přes JavaScript. Tvůj scraper je jednoduše nevidí.

Řešení: u každé stránky, kde potřebuješ obsah renderovaný JavaScriptem, přejdi na Selenium nebo Playwright. Pro nové projekty doporučuji Playwright — díky protokolu založenému na WebSocketu je a má lepší vestavěné možnosti stealth a podporu async.

troubleshooting-empty-array-causes.webp

Stránkování vrací pořád jen první stránku

Tohle je záludné. Napíšeš smyčku, zvyšuješ ?page=N, ale výsledky jsou pokaždé stejné. Na Goodreads se u stránek polic potichu vrací obsah první stránky bez ohledu na parametr ?page=, pokud nejsi přihlášený. Žádná chyba, žádné přesměrování — jen pořád dokola ta samá první stránka.

Řešení: přidej autentizační cookie session, konkrétně _session_id2. Více o tom v sekci o stránkování níže.

Kód, který fungoval loni, už nefunguje

Goodreads občas mění názvy HTML tříd i strukturu stránky. Populární repozitář maria-antoniak/goodreads-scraper na GitHubu dnes nese trvalé upozornění: "This project is unmaintained and no longer functioning." Řešení je používat odolnější selektory — strukturovaná data JSON-LD (podle standardu schema.org, která se mění jen zřídka) nebo atributy data-testid místo křehkých názvů tříd.

403 chyby nebo blokace

Knihovna requests v Pythonu má jiný TLS fingerprint než Chrome. I když použiješ User-Agent string pro Chrome, systémy na detekci botů jako AWS WAF (který Goodreads používá, protože patří Amazonu) mohou nesoulad odhalit. Řešení: přidej realistické browser hlavičky, mezi požadavky vlož zpoždění time.sleep() 3–8 sekund a při větším scrapingovém objemu zvaž curl_cffi pro shodu TLS fingerprintu.

Login wall na stránkách polic a seznamů

Některé stránky polic a seznamů na Goodreads vyžadují přihlášení, aby se zobrazil plný obsah, zejména po páté stránce. Použij requests.Session() s cookies exportovanými z prohlížeče, nebo Selenium/Playwright s přihlášeným profilem. Thunderbit to řeší přirozeně, protože běží ve tvém vlastním přihlášeném Chromu.

Než začnete

  • Obtížnost: střední (předpokládá se základní znalost Pythonu)
  • Časová náročnost: asi 20–30 minut pro celý postup
  • Co budete potřebovat:
    • Python 3.8+
    • Prohlížeč Chrome (pro kontrolu přes DevTools a Selenium/Playwright)
    • Knihovny: requests, beautifulsoup4, selenium nebo playwright, pandas
    • (Volitelné) gspread pro export do Google Sheets
    • (Volitelné) jako no-code alternativa

goodreads-scraping-flow.webp

Krok 1: Nastavení Python prostředí

Nainstalujte potřebné knihovny. Otevřete terminál a spusťte:

1pip install requests beautifulsoup4 selenium pandas lxml

Pokud dáváte přednost Playwrightu (doporučeno pro nové projekty):

1pip install playwright
2playwright install chromium

Pro export do Google Sheets (volitelné):

1pip install gspread oauth2client

Ujistěte se, že používáte Python 3.8 nebo vyšší. Verzi zkontrolujete přes python --version.

Po instalaci by mělo jít importovat všechny knihovny bez chyb. Zkuste python -c "import requests, bs4, pandas; print('Ready')", abyste si to ověřili.

Krok 2: Pošlete první požadavek se správnými hlavičkami

V prohlížeči si otevřete nějakou Goodreads stránku žánrové police nebo seznamu — například https://www.goodreads.com/list/show/1.Best_Books_Ever. Teď ji načteme v Pythonu.

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

Měli byste vidět Status: 200. Pokud dostanete 403, zkontrolujte hlavičky — AWS WAF Goodreads kontroluje realistický User-Agent a jednoduché požadavky blokuje. Hlavičky výše napodobují běžnou relaci v Chrome.

Krok 3: Prozkoumejte stránku a najděte správné selektory

Otevřete v Chromu DevTools (F12) na stránce seznamu Goodreads. Klikněte pravým tlačítkem na název knihy a zvolte „Inspect“. Uvidíte DOM strukturu jednotlivých záznamů.

U seznamových stránek je každá kniha obvykle zabalená v elementu <tr> s itemtype="http://schema.org/Book". Uvnitř najdete:

  • Název: a.bookTitle (text odkazu je název, href je URL knihy)
  • Autor: a.authorName
  • Hodnocení: span.minirating (obsahuje průměrné hodnocení a počet hodnocení)
  • Obrázek obálky: img uvnitř řádku knihy

U detailních stránek knih raději přeskoč CSS selektory a jdi rovnou na JSON-LD. Goodreads vkládá strukturovaná data do tagu <script type="application/ld+json">, který odpovídá formátu schema.org Book. Je mnohem stabilnější než názvy tříd, které Goodreads mění podle potřeby.

Krok 4: Vytáhněte data o knihách z jedné stránkové výpisové stránky

Pojďme rozparsovat list stránku a vytáhnout základní informace o každé knize:

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"Na stránce 1 jsem našel {len(books)} knih")
28for b in books[:3]:
29    print(b)

Měli byste vidět zhruba 100 knih na jednu list stránku. Každý záznam bude obsahovat název, autora, řetězec s hodnocením jako „4,28 avg rating — 9 031 257 ratings“ a URL na detail knihy.

Krok 5: Scrape podstránek pro detailní informace o knize

Výpisová stránka ti dá základ, ale to nejzajímavější — ISBN, plný popis, žánrové štítky, počet stran, datum vydání — je až na detailní stránce knihy. Tady se ukáže síla JSON-LD.

1import json
2import time
3def scrape_book_detail(book_url, headers):
4    """Navštíví jednu stránku knihy a vytáhne detailní metadata přes 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    # Žánrové štítky nejsou v JSON-LD; použijeme HTML jako 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],  # zkráceno pro náhled
25        "genres": ", ".join(genres[:5]),
26    }
27# Příklad: obohaťte první 3 knihy
28for book in books[:3]:
29    details = scrape_book_detail(book["book_url"], headers)
30    book.update(details)
31    print(f"Scraped: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
32    time.sleep(4)  # respektujte rate limit

Mezi požadavky přidej time.sleep() 3–8 sekund. Goodreads obvykle začne rate limiting kolem 20–30 požadavků za minutu z jedné IP a při rychlejším tempu se začnou objevovat 403 nebo CAPTCHA.

Tenhle dvoufázový postup — nejdřív nasbírat URL knih z list stránek a pak navštívit detailní stránky — je spolehlivější a snadno se obnovuje po přerušení. Používá ho většina úspěšných Goodreads scraperů.

Poznámka: to zvládne automaticky i se scrapingem podstránek. AI navštíví detail každé knihy a obohatí tabulku o ISBN, popis, žánry a další — bez kódu, smyček a čekacích timerů.

Krok 6: Obsah renderovaný JavaScriptem vyřešte pomocí Selenium

U stránek, kde se potřebný obsah načítá přes JavaScript — recenze, rozpad hodnocení, sekce „more details“ — budeš potřebovat nástroj pro automatizaci prohlížeče. Tady je příklad se Selenium:

1from selenium import webdriver
2from selenium.webdriver.chrome.options import Options
3from selenium.webdriver.common.by import By
4from selenium.webdriver.support.ui import WebDriverWait
5from selenium.webdriver.support import expected_conditions as EC
6options = Options()
7options.add_argument("--headless=new")
8options.add_argument("--disable-blink-features=AutomationControlled")
9options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
10                     "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
11driver = webdriver.Chrome(options=options)
12driver.get("https://www.goodreads.com/book/show/5907.The_Hobbit")
13# Počkejte, až se načtou recenze
14try:
15    WebDriverWait(driver, 10).until(
16        EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
17    )
18except:
19    print("Recenze se nenačetly — stránka možná vyžaduje přihlášení nebo vypršel čas pro JS")
20# Teď rozparsujte plně vyrenderovanou stránku
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"Hodnocení: {stars.get_text(strip=True) if stars else 'N/A'}")
28    print(f"Recenze: {text.get_text(strip=True)[:150] if text else 'N/A'}...")
29    print()
30driver.quit()

Kdy použít Selenium vs. requests:

  • requests + BeautifulSoup použij pro metadata knih (JSON-LD), list stránku, shelf stránky (strana 1) a data z Choice Awards
  • Selenium nebo Playwright použij pro recenze, rozpad hodnocení a jakýkoli obsah, který se v raw HTML vůbec neobjeví

Playwright je obecně lepší volba pro nové projekty — je rychlejší, méně paměťově náročný a má lepší stealth defaulty. Selenium má ale větší komunitu a víc existujících příkladů pro Goodreads.

Stránkování, které opravdu funguje: jak scrapovat celé seznamy Goodreads

Stránkování je nejčastější místo, kde Goodreads scrapery selhávají, a nenašel jsem jediný konkurenční návod, který by to vysvětloval pořádně. Takhle to uděláš správně.

Jak fungují URL stránkování na Goodreads

Goodreads používá u většiny stránkovaných stránek jednoduchý parametr ?page=N:

  • Seznamy: https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2
  • Police: https://www.goodreads.com/shelf/show/thriller?page=2
  • Vyhledávání: https://www.goodreads.com/search?q=fantasy&page=2

Každá list stránka obvykle zobrazuje 100 knih. Police zobrazují 50 na stránku.

Jak napsat smyčku pro stránkování, která pozná, kdy skončit

1import time
2all_books = []
3base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
4for page_num in range(1, 50):  # bezpečnostní limit na 50 stránek
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"Stránka {page_num}: status {resp.status_code}, končím.")
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"Stránka {page_num}: žádné knihy nenalezeny, konec seznamu.")
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"Stránka {page_num}: nascrapováno {len(rows)} knih (celkem: {len(all_books)})")
23    time.sleep(5)  # pětisekundová pauza mezi stránkami
24print(f"\nHotovo. Celkem získáno knih: {len(all_books)}")

Poslední stránku poznáš podle toho, že je výsledkový seznam prázdný (nenajdou se žádné elementy tr[itemtype="http://schema.org/Book"]) nebo že chybí odkaz na další stránku (a.next_page).

Záludný případ: po páté stránce je potřeba přihlášení

Tohle je past, do které spadne skoro každý: některé stránky polic a seznamů na Goodreads po zadání stránky 6+ bez přihlášení potichu vrátí obsah první stránky. Žádná chyba, žádné přesměrování — jen opakovaná stejná data.

Řešení: exportuj cookie _session_id2 z prohlížeče (pomocí rozšíření na export cookies nebo přes Chrome DevTools > Application > Cookies) a přidej ji do požadavků:

1session = requests.Session()
2session.headers.update(headers)
3session.cookies.set("_session_id2", "HODNOTA_VAŠÍ_SESSION_COOKIE", domain=".goodreads.com")
4# Místo requests.get() používejte session.get()
5resp = session.get(f"{base_url}?page=6", timeout=15)

Thunderbit zvládá stránkování přes kliknutí i nekonečný scroll nativně, bez kódu a bez správy cookies. Pokud se ti logika stránkování pořád rozpadá, stojí za to ho zvážit.

Kompletní Python skript připravený ke zkopírování

Tady je kompletní sjednocený skript. Ošetřuje hlavičky, stránkování, scraping podstránek přes JSON-LD, rate limiting i export do CSV. Testoval jsem ho proti živým Goodreads stránkám k polovině roku 2025.

1"""
2goodreads_scraper.py — Scrape Goodreads list s pagingem a obohacením detailů knih.
3Použití: python goodreads_scraper.py
4Výstup: 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          # upravte podle potřeby
16DELAY_LISTING = 5      # sekundy mezi stránkami seznamu
17DELAY_DETAIL = 4       # sekundy mezi detailními stránkami
18OUTPUT_FILE = "goodreads_books.csv"
19def scrape_listing_page(url):
20    """Vrátí seznam dictů s title, author, book_url z jedné list stránky."""
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    """Navštíví stránku knihy a vytáhne metadata přes 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    # --- 1. průchod: seberte URL knih z list stránek ---
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"Stránka {page}: prázdná — končím stránkování.")
70            break
71        all_books.extend(page_books)
72        print(f"Stránka {page}: {len(page_books)} knih (celkem: {len(all_books)})")
73        time.sleep(DELAY_LISTING)
74    # --- 2. průchod: obohaťte každou knihu o detailní data ---
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 do 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"\nUloženo {len(all_books)} knih do {OUTPUT_FILE}")
88    else:
89        print("Nepodařilo se nascrapovat žádné knihy.")
90if __name__ == "__main__":
91    main()

S MAX_PAGES = 3 tenhle skript nasbírá z listu „Best Books Ever“ asi 300 knih, navštíví detail každé z nich a všechno zapíše do CSV. Na mém počítači to trvá zhruba 25 minut (hlavně kvůli 4sekundovým pauzám mezi detailními požadavky). Výsledné CSV bude mít sloupce jako title, author, book_url, isbn, pages, avg_rating, rating_count, review_count, description, genres, language, format a published.

Export i jinam než do CSV: Google Sheets přes gspread

Pokud chceš data do Google Sheets místo CSV, nebo navíc k němu, přidej po exportu do CSV tohle:

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 odeslána do Google Sheets.")

Budeš potřebovat služební účet Google Cloud s aktivovanými Sheets a Drive API. tě provede nastavením asi za 5 minut. Pokud posíláš víc než několik stovek řádků, používej dávkové operace (append_rows() se seznamem seznamů) — limit Googlu je 300 požadavků za 60 sekund na projekt.

Samozřejmě, pokud ti celé tohle nastavování připadá zbytečně komplikované, Thunderbit exportuje do Google Sheets, Airtable, Notion, Excelu, CSV i JSON — bez instalace knihoven, bez souboru s credentials a bez API kvót.

No-code alternativa: scrapování Goodreads s Thunderbit

Ne každý chce udržovat Python skript. Možná jsi vydavatel a děláš jednorázovou analýzu trhu, nebo book blogger, který chce jen tabulku nejprodávanějších titulů. Přesně pro takové případy jsme Thunderbit vytvořili.

Jak scrapovat Goodreads s Thunderbit

  1. Nainstalujte rozšíření Thunderbit pro Chrome z a otevři Goodreads list, shelf nebo výsledky vyhledávání.
  2. Klikni na „AI Suggest Fields“ v postranním panelu Thunderbit. AI si stránku přečte a navrhne sloupce — obvykle název, autora, URL obrázku obálky a odkaz na knihu.
  3. Klikni na „Scrape“ — data se během pár sekund vytáhnou do strukturované tabulky.
  4. Exportuj do Google Sheets, Excelu, Airtable, Notion, CSV nebo JSON.

Pro detailní data o knihách (ISBN, popis, žánry, počet stran) Thunderbit použije scraping podstránek: navštíví detail každé knihy a tabulku automaticky obohatí — bez smyček, bez čekání, bez debugování.

Thunderbit zvládá i stránkované seznamy nativně. Řekneš mu, aby klikal na „Next“ nebo scrolloval, a on nasbírá data přes všechny stránky bez jediného řádku kódu.

Výhoda i kompromis jsou jednoduché: Python skript ti dává plnou kontrolu a je zdarma, pokud nepočítáš svůj čas. Thunderbit obětuje trochu flexibility výměnou za obrovskou úsporu času a nulovou údržbu. U seznamu o 300 knihách trvá Python skript asi 25 minut běhu plus čas, který jsi strávil psaním a laděním. Thunderbit stejná data získá zhruba za 3 minuty a ve dvou kliknutích.

Scraping Goodreads zodpovědně: robots.txt, podmínky použití a etika

Tohle si zaslouží přímou odpověď, ne laciný odstavcový disclaimer.

Co vlastně říká Goodreads robots.txt

Aktuální robots.txt Goodreads je překvapivě konkrétní. Stránky s detaily knih (/book/show/), veřejné seznamy (/list/show/), veřejné police (/shelf/show/) i stránky autorů (/author/show/) nejsou blokované. Co blokované je: /api, /book/reviews/, /review/list, /review/show, /search a několik dalších cest. GPTBot a CCBot (Common Crawl) jsou zcela zablokované přes Disallow: /. U bingbotu je nastavené Crawl-delay: 5, ale žádné globální zdržení neplatí.

Podmínky použití Goodreads lidsky

Podmínky použití (naposledy upravené 28. dubna 2021) zakazují „jakékoli použití dataminingu, robotů nebo podobných nástrojů pro sběr a extrakci dat“. To je široká formulace a stojí za to ji brát vážně — zároveň ale soudy opakovaně rozhodly, že samotné porušení ToS ještě neznamená trestněprávní „neoprávněný přístup“. Rozhodnutí uvedlo, že „kriminalizace porušení podmínek použití riskuje, že se každý web stane vlastním trestním jurisdikčním územím“.

Doporučené postupy

  • Zpomalte požadavky: 3–8 sekund mezi requesty (samotné Goodreads v robots.txt doporučuje pro boty 5 sekund)
  • Nepřekračujte 5 000 požadavků denně z jedné IP
  • Scrapujte jen veřejně dostupné stránky — vyhněte se hromadnému sběru dat dostupných jen po přihlášení
  • Nešířte komerčně raw text recenzí — recenze jsou chráněné autorské dílo
  • Ukládejte jen to, co opravdu potřebujete a nastavte si retenční politiku dat
  • Osobní výzkum vs. komerční použití: Scrapování veřejných dat pro osobní analýzu nebo akademický výzkum je obecně přijímané. Komerční redistribuce s sebou nese vyšší právní riziko.

Použití nástroje jako Thunderbit (který scrapuje přes tvoji vlastní browser session) vizuálně odpovídá běžnému prohlížení, ale stejné etické zásady platí bez ohledu na nástroj. Pokud chceš jít víc do hloubky a podívat se na , máme k tomu samostatný článek.

Tipy a časté chyby

Tip: Začněte vždy s JSON-LD. Než budeš psát složité CSS selektory, zkontroluj, jestli nejsou potřebná data v tagu <script type="application/ld+json">. Je stabilnější, snadněji se parsuje a méně často se rozbije, když Goodreads upraví frontend.

Tip: Používejte dvoufázový postup. Nejprve nasbírej všechny URL knih z list stránek a pak navštiv každou detailní stránku. Scraper se pak snáz obnoví, když spadne uprostřed, a seznam URL si můžeš uložit na disk jako checkpoint.

Chyba: Neošetření chybějících polí. Ne každá stránka knihy má ISBN, žánrové štítky nebo popis. Vždy používej .get() s výchozí hodnotou nebo podmínky if kolem selektorů. Jedna chyba NoneType ti může zničit tříhodinový scraping.

Chyba: Příliš rychlý běh. Vím, že je lákavé nastavit time.sleep(0.5) a projet to co nejrychleji. Goodreads ale začne po zhruba 20–30 rychlých požadavcích vracet 403 a jakmile tě označí, můžeš čekat hodiny nebo měnit IP. Zpoždění 4–5 sekund je ideální kompromis.

Chyba: Věřit starým návodům. Pokud průvodce odkazuje na Goodreads API nebo používá názvy tříd jako .field.value nebo #bookTitle, je nejspíš zastaralý. Před stavbou scraperu si vždy ověř selektory na živé stránce.

Další tipy k výběru správných scrapingových nástrojů a frameworků najdeš v našich průvodcích na a .

Závěr a hlavní poznatky

Scraping Goodreads v Pythonu je úplně reálný — jen musíš vědět, kde číhají nástrahy. Stručně:

  • Goodreads API je pryč (od prosince 2020). Scraping je dnes hlavní cesta, jak z platformy získat strukturovaná data o knihách.
  • Prázdné výsledky jsou skoro vždy způsobené obsahem renderovaným JS, zastaralými selektory, chybějícími hlavičkami nebo problémy s autentizací u stránkování — ne tím, že by byl tvůj kód špatně.
  • JSON-LD je tvůj nejlepší přítel pro metadata knih. Je stabilní, strukturované a mění se zřídka.
  • Stránkování u mnoha stránek polic a seznamů po páté stránce vyžaduje autentizaci. Přidej cookie _session_id2.
  • Rate limiting je skutečný. Používej pauzy 3–8 sekund a drž se pod 5 000 požadavků denně.
  • Dvoufázový postup (nejprve URL, potom detailní stránky) je spolehlivější a lze ho snadno obnovit.
  • Pro neprogramátory (nebo každého, kdo si váží odpoledne) zvládne všechno — JS rendering, stránkování, obohacení podstránek i export — zhruba na dvě kliknutí.

Scrapuj zodpovědně, respektuj robots.txt a ať ti knižní data nikdy nevrací jen [].

Často kladené otázky

Dá se ještě použít Goodreads API?

Ne. Goodreads v prosinci 2020 veřejné API zrušilo a nové vývojářské klíče už nevydává. Existující klíče, které byly 30 dní neaktivní, byly automaticky deaktivovány. Pro programový přístup k datům o knihách dnes zbývá web scraping nebo alternativní API (např. Open Library nebo Google Books).

Proč můj Goodreads scraper vrací prázdné výsledky?

Nejčastější příčinou je obsah renderovaný JavaScriptem. Goodreads načítá recenze, rozdělení hodnocení a mnoho detailů přes React/JavaScript, které jednoduchý requests.get() nevidí. U těchto stránek přejdi na Selenium nebo Playwright. Další příčiny jsou zastaralé CSS selektory (Goodreads změnil HTML), chybějící User-Agent hlavičky (spustí 403 blokaci) nebo neautentizované požadavky na stránkované shelf stránky.

Je scrapování Goodreads legální?

Scrapování veřejně dostupných dat pro osobní nebo výzkumné účely je podle současné právní praxe obecně přijímané (hiQ v. LinkedIn, Meta v. Bright Data). Goodreads ale ve svých podmínkách použití automatizovaný sběr dat zakazuje a vždy by sis měl zkontrolovat i robots.txt. Vyhni se komerční redistribuci chráněných textů recenzí a omezuj objem požadavků, abys web zbytečně nezatěžoval.

Jak scrapovat více stránek na Goodreads?

K URL police nebo seznamu přidej ?page=N a projdi čísla stránek ve smyčce. Poslední stránku poznáš podle prázdných výsledků nebo chybějícího odkazu „next“. Důležité: některé shelf stránky vyžadují autentizaci (cookie _session_id2), aby vrátily data i po páté stránce — bez ní se ti tiše budou opakovat data z první stránky.

Lze Goodreads scrapovat i bez programování?

Ano. je Chrome rozšíření, které ti umožní scrapovat Goodreads na dvě kliknutí — AI navrhne datová pole, klikneš na „Scrape“ a výsledek exportuješ přímo do Google Sheets, Excelu, Airtable nebo Notion. Automaticky zvládá obsah renderovaný JavaScriptem, stránkování i obohacení podstránek, bez nutnosti Pythonu nebo programování.

Zjisti více

Obsah

Vyzkoušej Thunderbit

Sbírej leady i další data jen na 2 kliknutí. Poháněno AI.

Získat Thunderbit Je to zdarma
Extrahuj data pomocí AI
Snadno přenes data do Google Sheets, Airtable nebo Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week