Jak zeskrobać Goodreads w Pythonie (koniec z pustymi wynikami)

Ostatnia aktualizacja: April 16, 2026

Niewiele rzeczy potrafi bardziej zniechęcić niż napisanie 30 linii kodu w Pythonie, uruchomienie scrapera Goodreads i zobaczenie wyniku []. Pusta lista. Nic. Tylko Ty i migający kursor.

Widziałem to już dziesiątki razy — w naszych wewnętrznych testach w , na forach dla programistów i w zgłoszeniach na GitHubie, które piętrzą się w porzuconych repozytoriach scraperów. Skargi są prawie zawsze takie same: „sekcja z top recenzjami jest pusta, pokazuje tylko []”, „bez względu na numer strony zawsze pobiera pierwszą”, „kod działał w zeszłym roku, teraz jest zepsuty”. A sytuacji nie poprawia fakt, że API Goodreads zostało wycofane w grudniu 2020 roku, więc rada „po prostu użyj API”, którą znajdziesz w starszych poradnikach, prowadzi donikąd.

Jeśli dziś potrzebujesz uporządkowanych danych książek z Goodreads — tytułów, autorów, ocen, recenzji, gatunków, ISBN — scraping jest główną drogą. Ten poradnik pokaże Ci działające, kompletne podejście do scrapowania Goodreads w Pythonie: od treści renderowanych przez JS, przez paginację i zabezpieczenia przed blokadą, po eksport danych. A jeśli Python to nie Twoja bajka, pokażę też alternatywę bez kodowania, która załatwia sprawę w dosłownie dwa kliknięcia.

Czym jest scrapowanie Goodreads i dlaczego robić to w Pythonie?

Scrapowanie Goodreads polega na automatycznym pobieraniu danych o książkach — tytułów, autorów, ocen, liczby recenzji, gatunków, ISBN, liczby stron, dat publikacji i wielu innych informacji — bezpośrednio ze stron Goodreads, zamiast kopiować je ręcznie.

Goodreads to jedna z największych baz książek na świecie, z ponad i około . Co miesiąc na półki „Want to Read” trafia ponad 18 milionów książek. Takie stale aktualizowane, uporządkowane dane są właśnie tym, po co wydawcy, analitycy danych, księgarze i badacze wracają do Goodreads.

Python to najczęściej wybierany język do takich zadań — odpowiada za około wszystkich projektów scrapujących. Biblioteki są dojrzałe (requests, BeautifulSoup, Selenium, Playwright, pandas), składnia przyjazna dla początkujących, a społeczność ogromna.

Jeśli jeszcze nigdy nie scrapowałeś strony internetowej, Python to świetny punkt startowy.

Dlaczego warto scrapować Goodreads w Pythonie? Praktyczne zastosowania

Zanim przejdziemy do kodu, warto zadać pytanie: komu właściwie są potrzebne te dane i co z nimi robi?

Przypadek użyciaKto korzystaCo pobierasz
Badania rynku wydawniczegoWydawcy, agenci literaccyPopularne gatunki, wysoko oceniane tytuły, nowi autorzy, oceny konkurencji
Systemy rekomendacji książekAnalitycy danych, hobbyści, twórcy aplikacjiOceny, gatunki, półki użytkowników, sentyment recenzji
Monitorowanie cen i stanów magazynowychSprzedawcy książek onlinePopularne tytuły, liczba recenzji, liczba zapisów „Want to Read”
Badania akademickieNaukowcy, studenciTreść recenzji, rozkład ocen, klasyfikacje gatunków
Analiza czytelniczaBlogerzy książkowi, projekty osobisteDane z własnych półek, historia czytania, statystyki roczne

Kilka konkretnych przykładów: UCSD Book Graph — jeden z najczęściej cytowanych akademickich zbiorów danych w badaniach nad rekomendacjami — zawiera , zebranych z publicznie dostępnych półek Goodreads. Kilka zbiorów na Kaggle (goodbooks-10k, Best Books Ever itd.) również powstało na bazie scrapowania Goodreads. A badanie z 2025 roku w Big Data and Society skomputeryzowało i uporządkowało , aby przeanalizować wpływ recenzji sponsorowanych na platformę.

Po stronie komercyjnej Bright Data sprzedaje gotowe, wcześniej zeskrobane zbiory danych Goodreads już od 0,50 USD za 1000 rekordów — to najlepszy dowód, że te dane mają realną wartość rynkową.

API Goodreads zniknęło — co jest teraz zamiast niego?

Jeśli ostatnio szukałeś „Goodreads API”, zapewne trafiłeś na poradnik, który jest już nieaktualny. 8 grudnia 2020 roku Goodreads po cichu przestało wydawać nowe klucze API dla deweloperów. Bez wpisu na blogu, bez wiadomości e-mail — tylko mały baner na stronie dokumentacji i sporo zdezorientowanych programistów.

goodreads-data-access-tools.webp

Skutki były natychmiastowe. Jeden z deweloperów, Kyle K, zbudował bota na Discorda do dzielenia się rekomendacjami książek — „nagle PUF i przestaje działać”. Inny, Matthew Jones, stracił dostęp do API tydzień przed głosowaniem do Reddit r/Fantasy Stabby Awards, co zmusiło go do powrotu do Google Forms. Elena Neacsu, doktorantka, miała po drodze rozjechany projekt pracy magisterskiej.

Co więc zostało? Dzisiejszy krajobraz wygląda tak:

PodejścieDostępne daneŁatwość użyciaLimityStatus
Goodreads APIPełne metadane, recenzjeŁatwe (kiedyś)1 req/sWycofane (grudzień 2020) — brak nowych kluczy
Open Library APITytuły, autorzy, ISBN, okładki (~30 mln tytułów)Łatwe1–3 req/sAktywne, darmowe, bez autoryzacji
Google Books APIMetadane, podglądyŁatwe1000/dzień za darmoAktywne (braki w ISBN dla nieangielskich)
Scrapowanie w Pythonie (requests + BS4)Wszystko w początkowym HTMLŚrednieZarządzane samodzielnieDziała dla treści statycznych
Scrapowanie w Pythonie (Selenium/Playwright)Także treści renderowane przez JSTrudniejszeZarządzane samodzielnieWymagane dla recenzji i części list
Thunderbit (rozszerzenie Chrome bez kodu)Dowolne widoczne dane ze stronyBardzo łatwe (2 kliknięcia)System punktowyAktywne — bez Pythona

Open Library to dobre uzupełnienie, szczególnie przy wyszukiwaniu ISBN i podstawowych metadanych. Ale jeśli potrzebujesz ocen, recenzji, tagów gatunków albo liczby zapisów „Want to Read”, pobierasz dane bezpośrednio z Goodreads — albo w Pythonie, albo narzędziem takim jak Thunderbit, które potrafi scrapować strony Goodreads (w tym podstrony z detalami książek) z polami podpowiadanymi przez AI i eksportem prosto do Google Sheets, Notion lub Airtable.

Dlaczego Twój scraper Goodreads w Pythonie zwraca puste wyniki i jak to naprawić?

To jest sekcja, którą chciałbym mieć przed sobą, kiedy pierwszy raz pracowałem z danymi Goodreads. Problem „pustych wyników” to najczęstsza skarga na forach deweloperskich i ma kilka różnych przyczyn — każda z własnym rozwiązaniem.

ObjawPrzyczynaRozwiązanie
Recenzje/oceny zwracają []Treść renderowana przez JS (React/lazy-load)Użyj Selenium albo Playwright zamiast requests
Zawsze pobiera tylko stronę 1Parametry paginacji ignorowane lub sterowane przez JSPrawidłowo przekazuj ?page=N; przy infinite scroll użyj automatyzacji przeglądarki
Kod działał w zeszłym roku, teraz nie działaGoodreads zmieniło nazwy klas HTMLUżywaj odpornych selektorów (JSON-LD, atrybutów data-testid)
403 / blokada po kilku żądaniachBrak nagłówków / zbyt szybkie żądaniaDodaj User-Agent, time.sleep(), rotuj proxy
Logowanie wymagane na stronach półek/listPotrzebny cookie/sesjaUżyj requests.Session() z ciasteczkami albo scrapingu w przeglądarce

Treść renderowana przez JS: recenzje i oceny są puste

Frontend Goodreads działa w oparciu o React. Gdy wywołasz requests.get() na stronie książki, dostajesz początkowy HTML — ale recenzje, rozkład ocen i wiele sekcji „więcej informacji” ładuje się asynchronicznie przez JavaScript. Twój scraper po prostu nie jest w stanie tego zobaczyć.

Rozwiązanie: dla każdej strony, na której potrzebujesz treści renderowanej przez JS, przejdź na Selenium albo Playwright. Playwright polecam do nowych projektów — jest dzięki protokołowi opartemu na WebSocketach i ma lepsze wbudowane mechanizmy ukrywania automatyzacji oraz obsługi async.

troubleshooting-empty-array-causes.webp

Paginacja, która zwraca tylko stronę 1

To podstępny problem. Piszesz pętlę, zwiększasz ?page=N, a mimo to za każdym razem dostajesz te same wyniki. Na Goodreads strony półek potrafią po cichu zwracać zawartość strony 1 niezależnie od parametru ?page=, jeśli nie jesteś zalogowany. Bez błędu, bez przekierowania — po prostu ta sama pierwsza strona w kółko.

Rozwiązanie: dodaj uwierzytelnione ciasteczko sesyjne (konkretnie _session_id2). Więcej o tym w sekcji o paginacji niżej.

Kod działał rok temu, a teraz nie działa

Goodreads okresowo zmienia nazwy klas HTML i strukturę stron. Popularne repozytorium maria-antoniak/goodreads-scraper na GitHubie ma dziś stałą informację: „This project is unmaintained and no longer functioning.” Rozwiązanie to korzystanie z bardziej odpornych selektorów — struktur JSON-LD (zgodnych ze schema.org i rzadko zmienianych) albo atrybutów data-testid zamiast kruchych nazw klas.

Błędy 403 lub blokada

Biblioteka requests w Pythonie ma inny odcisk TLS niż Chrome. Nawet przy nagłówku User-Agent udającym Chrome systemy wykrywania botów, takie jak AWS WAF (które Goodreads wykorzystuje jako spółka zależna Amazona), mogą wychwycić różnicę. Rozwiązanie: dodaj realistyczne nagłówki przeglądarki, wprowadź opóźnienia time.sleep() rzędu 3–8 sekund między żądaniami i przy większej skali rozważ curl_cffi, jeśli zależy Ci na zgodności odcisku TLS.

Ściany logowania na półkach i listach

Niektóre strony półek i list Goodreads wymagają uwierzytelnienia, by zobaczyć pełną zawartość, zwłaszcza po stronie 5. Użyj requests.Session() z ciasteczkami wyeksportowanymi z przeglądarki albo Selenium/Playwright z zalogowanym profilem. Thunderbit radzi sobie z tym naturalnie, ponieważ działa w Twojej własnej, zalogowanej przeglądarce Chrome.

Zanim zaczniesz

  • Poziom trudności: średnio zaawansowany (zakładamy podstawy Pythona)
  • Czas potrzebny: ok. 20–30 minut na pełny walkthrough
  • Czego potrzebujesz:
    • Python 3.8+
    • Przeglądarka Chrome (do inspekcji DevTools i Selenium/Playwright)
    • Biblioteki: requests, beautifulsoup4, selenium lub playwright, pandas
    • (Opcjonalnie) gspread do eksportu do Google Sheets
    • (Opcjonalnie) jako alternatywa bez kodu

goodreads-scraping-flow.webp

Krok 1: Skonfiguruj środowisko Pythona

Zainstaluj potrzebne biblioteki. Otwórz terminal i uruchom:

1pip install requests beautifulsoup4 selenium pandas lxml

Jeśli wolisz Playwright (polecany w nowych projektach):

1pip install playwright
2playwright install chromium

Do eksportu do Google Sheets (opcjonalnie):

1pip install gspread oauth2client

Upewnij się, że masz Python 3.8 lub nowszy. Sprawdzisz to poleceniem python --version.

Po instalacji powinieneś móc zaimportować wszystkie biblioteki bez błędów. Spróbuj python -c "import requests, bs4, pandas; print('Ready')", żeby to potwierdzić.

Krok 2: Wyślij pierwsze żądanie z poprawnymi nagłówkami

Wejdź w przeglądarce na stronę listy lub półki Goodreads — na przykład https://www.goodreads.com/list/show/1.Best_Books_Ever. Teraz pobierzmy tę stronę w Pythonie.

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

Powinieneś zobaczyć Status: 200. Jeśli pojawi się 403, sprawdź nagłówki — AWS WAF Goodreads oczekuje realistycznego User-Agent i odrzuci zbyt „gołe” żądania. Powyższe nagłówki imitują prawdziwą sesję Chrome.

Krok 3: Zbadaj stronę i znajdź właściwe selektory

Otwórz DevTools w Chrome (F12) na stronie listy Goodreads. Kliknij prawym na tytuł książki i wybierz „Inspect”. Zobaczysz strukturę DOM dla każdego wpisu.

Na stronach list pojedyncza książka jest zwykle otoczona elementem <tr> z itemtype="http://schema.org/Book". W środku znajdziesz:

  • Tytuł: a.bookTitle (tekst linku to tytuł, a href daje URL książki)
  • Autor: a.authorName
  • Ocena: span.minirating (zawiera średnią ocenę i liczbę ocen)
  • Okładka: img wewnątrz wiersza książki

Na stronach pojedynczych książek lepiej pominąć selektory CSS i przejść od razu do JSON-LD. Goodreads osadza ustrukturyzowane dane w tagu <script type="application/ld+json">, zgodnym z formatem Book ze schema.org. Jest znacznie stabilniejszy niż nazwy klas, które Goodreads zmienia wedle uznania.

Krok 4: Pobierz dane o książkach z jednej strony listy

Przeanalizujmy stronę listy i wyciągnijmy podstawowe informacje o każdej książce:

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"Znaleziono {len(books)} książek na stronie 1")
28for b in books[:3]:
29    print(b)

Powinieneś zobaczyć około 100 książek na stronę listy. Każdy wpis będzie miał tytuł, autora, ciąg z oceną typu „4.28 avg rating — 9,031,257 ratings” oraz URL do strony szczegółów książki.

Krok 5: Zeskrob podstrony z pełnymi informacjami o książce

Strona listy daje podstawy, ale prawdziwe „złoto” — ISBN, pełny opis, tagi gatunków, liczba stron, data publikacji — znajduje się na indywidualnej stronie każdej książki. Tu właśnie błyszczy JSON-LD.

1import json
2import time
3def scrape_book_detail(book_url, headers):
4    """Wejdź na stronę pojedynczej książki i pobierz szczegółowe metadane przez 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    # Tagi gatunków nie są w JSON-LD; używamy zapasowo 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],  # skrót podglądowy
25        "genres": ", ".join(genres[:5]),
26    }
27# Przykład: wzbogacenie pierwszych 3 książek
28for book in books[:3]:
29    details = scrape_book_detail(book["book_url"], headers)
30    book.update(details)
31    print(f"Zeskrobano: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
32    time.sleep(4)  # respektuj limity

Dodaj time.sleep() od 3 do 8 sekund między żądaniami. Goodreads zaczyna ograniczać ruch mniej więcej przy 20–30 żądaniach na minutę z jednego IP i przy szybszym tempie zaczniesz widzieć 403 albo CAPTCHA.

To dwuetapowe podejście — najpierw zbierasz wszystkie URL-e książek ze stron list, a dopiero potem wchodzisz na strony szczegółów — jest bardziej niezawodne i łatwiejsze do wznowienia, jeśli coś przerwie proces. To strategia, z której korzysta większość skutecznych scraperów Goodreads.

Uwaga: potrafi zrobić to automatycznie dzięki scrapowaniu podstron. AI odwiedza każdą stronę książki i wzbogaca tabelę o ISBN, opis, gatunki i wiele więcej — bez kodu, bez pętli, bez timerów.

Krok 6: Obsłuż treści renderowane przez JavaScript za pomocą Selenium

W przypadku stron, na których potrzebne dane ładowane są przez JavaScript — recenzji, rozkładu ocen, sekcji „więcej szczegółów” — potrzebujesz narzędzia do automatyzacji przeglądarki. Oto przykład w 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# Poczekaj, aż recenzje się załadują
14try:
15    WebDriverWait(driver, 10).until(
16        EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
17    )
18except:
19    print("Recenzje się nie załadowały — strona może wymagać logowania albo JS przekroczył limit czasu")
20# Teraz parsujemy w pełni wyrenderowaną stronę
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"Ocena: {stars.get_text(strip=True) if stars else 'N/A'}")
28    print(f"Recenzja: {text.get_text(strip=True)[:150] if text else 'N/A'}...")
29    print()
30driver.quit()

Kiedy używać Selenium, a kiedy requests:

  • Użyj requests + BeautifulSoup dla metadanych książek (JSON-LD), stron list, półek (strona 1) i danych z Choice Awards
  • Użyj Selenium albo Playwright dla recenzji, rozkładu ocen i wszelkiej treści, której nie ma w surowym HTML

Playwright to zwykle lepszy wybór do nowych projektów — szybszy, mniej pamięciożerny, lepsze domyślne mechanizmy ukrywania automatyzacji. Ale Selenium ma większą społeczność i więcej gotowych przykładów dotyczących Goodreads.

Paginacja, która naprawdę działa: jak zeskrobać całe listy Goodreads

Paginacja to najczęstszy punkt awarii scraperów Goodreads i nie widziałem jeszcze jednego konkurencyjnego poradnika, który tłumaczyłby ją poprawnie. Oto jak zrobić to dobrze.

Jak działają adresy paginacji w Goodreads

Goodreads używa prostego parametru ?page=N dla większości stronicowanych stron:

  • Listy: https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2
  • Półki: https://www.goodreads.com/shelf/show/thriller?page=2
  • Wyszukiwanie: https://www.goodreads.com/search?q=fantasy&page=2

Każda strona listy pokazuje zwykle 100 książek. Półki pokazują 50 na stronę.

Jak napisać pętlę paginacji, która wie, kiedy się zatrzymać

1import time
2all_books = []
3base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
4for page_num in range(1, 50):  # bezpieczny limit do 50 stron
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"Strona {page_num}: status {resp.status_code}, kończę.")
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"Strona {page_num}: brak książek, to koniec.")
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"Strona {page_num}: zeskrobano {len(rows)} książek (łącznie: {len(all_books)})")
23    time.sleep(5)  # 5 sekund przerwy między stronami
24print(f"\nGotowe. Łącznie pobrano {len(all_books)} książek")

Ostatnią stronę wykrywasz, sprawdzając, czy lista wyników jest pusta (brak elementów tr[itemtype="http://schema.org/Book"]) albo czy nie ma linku „next” (a.next_page).

Trudny przypadek: wymagane logowanie po stronie 5

To pułapka, na którą wpada prawie każdy: niektóre strony półek i list Goodreads po cichu zwracają treść strony 1, jeśli poprosisz o stronę 6+ bez autoryzacji. Bez błędu, bez przekierowania — tylko te same dane w kółko.

Żeby to naprawić, wyeksportuj ciasteczko _session_id2 z przeglądarki (użyj rozszerzenia do eksportu cookies albo Chrome DevTools > Application > Cookies) i dodaj je do żądań:

1session = requests.Session()
2session.headers.update(headers)
3session.cookies.set("_session_id2", "TWOJA_WARTOŚĆ_CIACHETKA_SESJI", domain=".goodreads.com")
4# Teraz używaj session.get() zamiast requests.get()
5resp = session.get(f"{base_url}?page=6", timeout=15)

Thunderbit obsługuje zarówno paginację klikaną, jak i infinite scroll natywnie, bez kodu i bez zarządzania ciasteczkami. Jeśli logika paginacji ciągle Ci się psuje, warto to rozważyć.

Kompletny skrypt w Pythonie gotowy do skopiowania

Poniżej znajdziesz pełny, scalony skrypt. Obsługuje nagłówki, paginację, scrapowanie podstron przez JSON-LD, limitowanie tempa i eksport do CSV. Testowałem go na działających stronach Goodreads w połowie 2025 roku.

1"""
2goodreads_scraper.py — zeskrob listę Goodreads z paginacją i wzbogacaniem danych z podstron.
3Użycie: python goodreads_scraper.py
4Wyjście: 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          # dostosuj według potrzeb
16DELAY_LISTING = 5      # sekundy między stronami listy
17DELAY_DETAIL = 4       # sekundy między stronami szczegółów
18OUTPUT_FILE = "goodreads_books.csv"
19def scrape_listing_page(url):
20    """Zwraca listę słowników z tytułem, autorem i book_url z jednej strony listy."""
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    """Odwiedza stronę książki i pobiera metadane przez JSON-LD + zapasowy HTML."""
39    resp = requests.get(book_url, headers=HEADERS, timeout=15)
40    if resp.status_code != 200:
41        return {}
42    soup = BeautifulSoup(resp.text, "lxml")
43    script = soup.find("script", {"type": "application/ld+json"})
44    if not script:
45        return {}
46    data = json.loads(script.string)
47    agg = data.get("aggregateRating", {})
48    genres = [g.get_text(strip=True)
49              for g in soup.select("span.BookPageMetadataSection__genreButton a span")]
50    return {
51        "isbn": data.get("isbn", ""),
52        "pages": data.get("numberOfPages", ""),
53        "avg_rating": agg.get("ratingValue", ""),
54        "rating_count": agg.get("ratingCount", ""),
55        "review_count": agg.get("reviewCount", ""),
56        "description": (data.get("description", "") or "")[:300],
57        "genres": ", ".join(genres[:5]),
58        "language": data.get("inLanguage", ""),
59        "format": data.get("bookFormat", ""),
60        "published": data.get("datePublished", ""),
61    }
62def main():
63    all_books = []
64    # --- Etap 1: zbierz URL-e książek ze stron listy ---
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"Strona {page}: pusta — kończę paginację.")
70            break
71        all_books.extend(page_books)
72        print(f"Strona {page}: {len(page_books)} książek (łącznie: {len(all_books)})")
73        time.sleep(DELAY_LISTING)
74    # --- Etap 2: wzbogac każdy rekord danymi ze strony szczegółów ---
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    # --- Eksport 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"\nZapisano {len(all_books)} książek do {OUTPUT_FILE}")
88    else:
89        print("Nie udało się zeskrobać żadnych książek.")
90if __name__ == "__main__":
91    main()

Przy MAX_PAGES = 3 ten skrypt zbiera około 300 książek z listy „Best Books Ever”, odwiedza stronę szczegółów każdej z nich i zapisuje wszystko do CSV. Na moim komputerze zajmuje to około 25 minut (głównie przez 4-sekundowe przerwy między żądaniami stron szczegółów). Wynikowy plik CSV będzie miał kolumny takie jak title, author, book_url, isbn, pages, avg_rating, rating_count, review_count, description, genres, language, format i published.

Eksport poza CSV: Google Sheets z gspread

Jeśli chcesz mieć dane w Google Sheets zamiast CSV albo dodatkowo obok niego, dodaj to po eksporcie CSV:

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

Potrzebujesz konta usługi Google Cloud z włączonymi API Sheets i Drive. Dokumentacja prowadzi przez konfigurację w około 5 minut. Przy większej liczbie niż kilkaset wierszy używaj operacji zbiorczych (append_rows() z listą list) — limit Google to 300 żądań na 60 sekund na projekt.

Oczywiście, jeśli cała ta konfiguracja brzmi jak przesada, Thunderbit eksportuje do Google Sheets, Airtable, Notion, Excela, CSV i JSON — bez instalowania bibliotek, pliku z credentialami i limitów API.

Alternatywa bez kodu: scrapowanie Goodreads w Thunderbit

Nie każdy chce utrzymywać skrypt w Pythonie. Może jesteś wydawcą robiącym jednorazową analizę rynku albo blogerem książkowym, który po prostu chce arkusz z bestsellerami tego roku. Właśnie na takie sytuacje powstał Thunderbit.

Jak zeskrobać Goodreads w Thunderbit

  1. Zainstaluj rozszerzenie Thunderbit Chrome z i przejdź na stronę listy, półki lub wyników wyszukiwania Goodreads.
  2. Kliknij „AI Suggest Fields” w bocznym panelu Thunderbit. AI analizuje stronę i proponuje kolumny — zwykle tytuł, autor, URL okładki i link do książki.
  3. Kliknij „Scrape” — dane trafiają do uporządkowanej tabeli w kilka sekund.
  4. Wyeksportuj do Google Sheets, Excela, Airtable, Notion, CSV lub JSON.

Jeśli potrzebujesz szczegółowych danych książki (ISBN, opis, gatunki, liczba stron), funkcja subpage scraping w Thunderbit odwiedzi każdą stronę szczegółów i automatycznie wzbogaci tabelę — bez pętli, bez opóźnień, bez debugowania.

Thunderbit obsługuje też listy stronicowane natywnie. Wskazujesz „Next” albo przewijanie, a narzędzie zbiera dane ze wszystkich stron bez kodu.

Kompromis jest prosty: skrypt w Pythonie daje pełną kontrolę i jest darmowy (poza Twoim czasem), a Thunderbit wymienia trochę elastyczności na ogromną oszczędność czasu i brak konieczności utrzymania. Przy liście 300 książek skrypt w Pythonie to około 25 minut działania plus czas napisania i naprawiania błędów. Thunderbit pobiera te same dane w około 3 minuty, w dwóch kliknięciach.

Odpowiedzialne scrapowanie Goodreads: robots.txt, Warunki korzystania i etyka

Tu potrzebna jest szczera odpowiedź, a nie rzucenie lakonicznego zastrzeżenia.

Co naprawdę mówi robots.txt Goodreads

Aktualny robots.txt Goodreads jest zaskakująco szczegółowy. Strony szczegółów książek (/book/show/), publiczne listy (/list/show/), publiczne półki (/shelf/show/) i strony autorów (/author/show/) nie są blokowane. Zablokowane są: /api, /book/reviews/, /review/list, /review/show, /search i kilka innych ścieżek. GPTBot i CCBot (Common Crawl) są całkowicie blokowane przez Disallow: /. Dla bingbot istnieje dyrektywa Crawl-delay: 5, ale nie ma globalnego opóźnienia.

Warunki korzystania z Goodreads prostymi słowami

Regulamin (ostatnio zaktualizowany 28 kwietnia 2021) zabrania „jakiegokolwiek używania narzęd

Spis treści

Wypróbuj Thunderbit

Pobieraj leady i inne dane w zaledwie 2 kliknięcia. Napędzane przez AI.

Pobierz Thunderbit To za darmo
Wyciągaj dane z pomocą AI
Łatwo przenieś dane do Google Sheets, Airtable lub Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week