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 Thunderbit, 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 150 milionami zarejestrowanych użytkowników i około 90 milionami recenzji tworzonych przez użytkowników. 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 69,6% 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życia | Kto korzysta | Co pobierasz |
|---|---|---|
| Badania rynku wydawniczego | Wydawcy, agenci literaccy | Popularne gatunki, wysoko oceniane tytuły, nowi autorzy, oceny konkurencji |
| Systemy rekomendacji książek | Analitycy danych, hobbyści, twórcy aplikacji | Oceny, gatunki, półki użytkowników, sentyment recenzji |
| Monitorowanie cen i stanów magazynowych | Sprzedawcy książek online | Popularne tytuły, liczba recenzji, liczba zapisów „Want to Read” |
| Badania akademickie | Naukowcy, studenci | Treść recenzji, rozkład ocen, klasyfikacje gatunków |
| Analiza czytelnicza | Blogerzy książkowi, projekty osobiste | Dane 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 2,36 miliona unikalnych książek i ponad 228 milionów interakcji użytkownik-książka, 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 331 211 recenzji książek z zachętą, 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.

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ście | Dostępne dane | Łatwość użycia | Limity | Status |
|---|---|---|---|---|
| Goodreads API | Pełne metadane, recenzje | Łatwe (kiedyś) | 1 req/s | Wycofane (grudzień 2020) — brak nowych kluczy |
| Open Library API | Tytuły, autorzy, ISBN, okładki (~30 mln tytułów) | Łatwe | 1–3 req/s | Aktywne, darmowe, bez autoryzacji |
| Google Books API | Metadane, podglądy | Łatwe | 1000/dzień za darmo | Aktywne (braki w ISBN dla nieangielskich) |
| Scrapowanie w Pythonie (requests + BS4) | Wszystko w początkowym HTML | Średnie | Zarządzane samodzielnie | Działa dla treści statycznych |
| Scrapowanie w Pythonie (Selenium/Playwright) | Także treści renderowane przez JS | Trudniejsze | Zarządzane samodzielnie | Wymagane dla recenzji i części list |
| Thunderbit (rozszerzenie Chrome bez kodu) | Dowolne widoczne dane ze strony | Bardzo łatwe (2 kliknięcia) | System punktowy | Aktywne — 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.
| Objaw | Przyczyna | Rozwiązanie |
|---|---|---|
Recenzje/oceny zwracają [] | Treść renderowana przez JS (React/lazy-load) | Użyj Selenium albo Playwright zamiast requests |
| Zawsze pobiera tylko stronę 1 | Parametry paginacji ignorowane lub sterowane przez JS | Prawidłowo przekazuj ?page=N; przy infinite scroll użyj automatyzacji przeglądarki |
| Kod działał w zeszłym roku, teraz nie działa | Goodreads zmieniło nazwy klas HTML | Używaj odpornych selektorów (JSON-LD, atrybutów data-testid) |
| 403 / blokada po kilku żądaniach | Brak nagłówków / zbyt szybkie żądania | Dodaj User-Agent, time.sleep(), rotuj proxy |
| Logowanie wymagane na stronach półek/list | Potrzebny cookie/sesja | Uż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 o 35–45% szybszy od Selenium dzięki protokołowi opartemu na WebSocketach i ma lepsze wbudowane mechanizmy ukrywania automatyzacji oraz obsługi async.

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,seleniumlubplaywright,pandas - (Opcjonalnie)
gspreaddo eksportu do Google Sheets - (Opcjonalnie) Rozszerzenie Thunderbit Chrome jako alternatywa bez kodu

Krok 1: Skonfiguruj środowisko Pythona
Zainstaluj potrzebne biblioteki. Otwórz terminal i uruchom:
pip install requests beautifulsoup4 selenium pandas lxml
Jeśli wolisz Playwright (polecany w nowych projektach):
pip install playwright
playwright install chromium
Do eksportu do Google Sheets (opcjonalnie):
pip 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.
import requests
from bs4 import BeautifulSoup
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}
url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
response = requests.get(url, headers=headers, timeout=15)
print(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ł, ahrefdaje URL książki) - Autor:
a.authorName - Ocena:
span.minirating(zawiera średnią ocenę i liczbę ocen) - Okładka:
imgwewną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:
import requests
from bs4 import BeautifulSoup
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
}
url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
response = requests.get(url, headers=headers, timeout=15)
soup = BeautifulSoup(response.text, "lxml")
books = []
rows = soup.select('tr[itemtype="http://schema.org/Book"]')
for row in rows:
title_tag = row.select_one("a.bookTitle")
author_tag = row.select_one("a.authorName")
rating_tag = row.select_one("span.minirating")
title = title_tag.get_text(strip=True) if title_tag else ""
book_url = "https://www.goodreads.com" + title_tag["href"] if title_tag else ""
author = author_tag.get_text(strip=True) if author_tag else ""
rating_text = rating_tag.get_text(strip=True) if rating_tag else ""
books.append({
"title": title,
"author": author,
"rating_info": rating_text,
"book_url": book_url,
})
print(f"Znaleziono {len(books)} książek na stronie 1")
for b in books[:3]:
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.
import json
import time
def scrape_book_detail(book_url, headers):
"""Wejdź na stronę pojedynczej książki i pobierz szczegółowe metadane przez JSON-LD."""
resp = requests.get(book_url, headers=headers, timeout=15)
if resp.status_code != 200:
return {}
soup = BeautifulSoup(resp.text, "lxml")
script = soup.find("script", {"type": "application/ld+json"})
if not script:
return {}
data = json.loads(script.string)
agg = data.get("aggregateRating", {})
# Tagi gatunków nie są w JSON-LD; używamy zapasowo HTML
genres = [g.get_text(strip=True) for g in soup.select('span.BookPageMetadataSection__genreButton a span')]
return {
"isbn": data.get("isbn", ""),
"pages": data.get("numberOfPages", ""),
"language": data.get("inLanguage", ""),
"format": data.get("bookFormat", ""),
"avg_rating": agg.get("ratingValue", ""),
"rating_count": agg.get("ratingCount", ""),
"review_count": agg.get("reviewCount", ""),
"description": data.get("description", "")[:200], # skrót podglądowy
"genres": ", ".join(genres[:5]),
}
# Przykład: wzbogacenie pierwszych 3 książek
for book in books[:3]:
details = scrape_book_detail(book["book_url"], headers)
book.update(details)
print(f"Zeskrobano: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
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: Thunderbit 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:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
driver = webdriver.Chrome(options=options)
driver.get("https://www.goodreads.com/book/show/5907.The_Hobbit")
# Poczekaj, aż recenzje się załadują
try:
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
)
except:
print("Recenzje się nie załadowały — strona może wymagać logowania albo JS przekroczył limit czasu")
# Teraz parsujemy w pełni wyrenderowaną stronę
page_source = driver.page_source
soup = BeautifulSoup(page_source, "lxml")
reviews = soup.select("article.ReviewCard")
for rev in reviews[:3]:
text = rev.select_one("span.Formatted")
stars = rev.select_one("span.RatingStars")
print(f"Ocena: {stars.get_text(strip=True) if stars else 'N/A'}")
print(f"Recenzja: {text.get_text(strip=True)[:150] if text else 'N/A'}...")
print()
driver.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ć
import time
all_books = []
base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
for page_num in range(1, 50): # bezpieczny limit do 50 stron
url = f"{base_url}?page={page_num}"
resp = requests.get(url, headers=headers, timeout=15)
if resp.status_code != 200:
print(f"Strona {page_num}: status {resp.status_code}, kończę.")
break
soup = BeautifulSoup(resp.text, "lxml")
rows = soup.select('tr[itemtype="http://schema.org/Book"]')
if not rows:
print(f"Strona {page_num}: brak książek, to koniec.")
break
for row in rows:
title_tag = row.select_one("a.bookTitle")
author_tag = row.select_one("a.authorName")
title = title_tag.get_text(strip=True) if title_tag else ""
book_url = "https://www.goodreads.com" + title_tag["href"] if title_tag else ""
author = author_tag.get_text(strip=True) if author_tag else ""
all_books.append({"title": title, "author": author, "book_url": book_url})
print(f"Strona {page_num}: zeskrobano {len(rows)} książek (łącznie: {len(all_books)})")
time.sleep(5) # 5 sekund przerwy między stronami
print(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ń:
session = requests.Session()
session.headers.update(headers)
session.cookies.set("_session_id2", "TWOJA_WARTOŚĆ_CIACHETKA_SESJI", domain=".goodreads.com")
# Teraz używaj session.get() zamiast requests.get()
resp = 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.
"""
goodreads_scraper.py — zeskrob listę Goodreads z paginacją i wzbogacaniem danych z podstron.
Użycie: python goodreads_scraper.py
Wyjście: goodreads_books.csv
"""
import csv, json, time, requests
from bs4 import BeautifulSoup
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}
BASE_URL = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
MAX_PAGES = 3 # dostosuj według potrzeb
DELAY_LISTING = 5 # sekundy między stronami listy
DELAY_DETAIL = 4 # sekundy między stronami szczegółów
OUTPUT_FILE = "goodreads_books.csv"
def scrape_listing_page(url):
"""Zwraca listę słowników z tytułem, autorem i book_url z jednej strony listy."""
resp = requests.get(url, headers=HEADERS, timeout=15)
if resp.status_code != 200:
return []
soup = BeautifulSoup(resp.text, "lxml")
rows = soup.select('tr[itemtype="http://schema.org/Book"]')
books = []
for row in rows:
t = row.select_one("a.bookTitle")
a = row.select_one("a.authorName")
if t:
books.append({
"title": t.get_text(strip=True),
"author": a.get_text(strip=True) if a else "",
"book_url": "https://www.goodreads.com" + t["href"],
})
return books
def scrape_book_detail(book_url):
"""Odwiedza stronę książki i pobiera metadane przez JSON-LD + zapasowy HTML."""
resp = requests.get(book_url, headers=HEADERS, timeout=15)
if resp.status_code != 200:
return {}
soup = BeautifulSoup(resp.text, "lxml")
script = soup.find("script", {"type": "application/ld+json"})
if not script:
return {}
data = json.loads(script.string)
agg = data.get("aggregateRating", {})
genres = [g.get_text(strip=True)
for g in soup.select("span.BookPageMetadataSection__genreButton a span")]
return {
"isbn": data.get("isbn", ""),
"pages": data.get("numberOfPages", ""),
"avg_rating": agg.get("ratingValue", ""),
"rating_count": agg.get("ratingCount", ""),
"review_count": agg.get("reviewCount", ""),
"description": (data.get("description", "") or "")[:300],
"genres": ", ".join(genres[:5]),
"language": data.get("inLanguage", ""),
"format": data.get("bookFormat", ""),
"published": data.get("datePublished", ""),
}
def main():
all_books = []
# --- Etap 1: zbierz URL-e książek ze stron listy ---
for page in range(1, MAX_PAGES + 1):
url = f"{BASE_URL}?page={page}"
page_books = scrape_listing_page(url)
if not page_books:
print(f"Strona {page}: pusta — kończę paginację.")
break
all_books.extend(page_books)
print(f"Strona {page}: {len(page_books)} książek (łącznie: {len(all_books)})")
time.sleep(DELAY_LISTING)
# --- Etap 2: wzbogac każdy rekord danymi ze strony szczegółów ---
for i, book in enumerate(all_books):
details = scrape_book_detail(book["book_url"])
book.update(details)
print(f"[{i+1}/{len(all_books)}] {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
time.sleep(DELAY_DETAIL)
# --- Eksport do CSV ---
if all_books:
fieldnames = list(all_books[0].keys())
with open(OUTPUT_FILE, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(all_books)
print(f"\nZapisano {len(all_books)} książek do {OUTPUT_FILE}")
else:
print("Nie udało się zeskrobać żadnych książek.")
if __name__ == "__main__":
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:
import gspread
from oauth2client.service_account import ServiceAccountCredentials
scope = ["https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive"]
creds = ServiceAccountCredentials.from_json_keyfile_name("credentials.json", scope)
client = gspread.authorize(creds)
sheet = client.open("Goodreads Scrape").sheet1
header = list(all_books[0].keys())
sheet.append_row(header)
for book in all_books:
sheet.append_row([str(book.get(k, "")) for k in header])
print("Dane przesłane do Google Sheets.")
Potrzebujesz konta usługi Google Cloud z włączonymi API Sheets i Drive. Dokumentacja gspread 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 jednym kliknięciem — 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
- Zainstaluj rozszerzenie Thunderbit Chrome z Chrome Web Store i przejdź na stronę listy, półki lub wyników wyszukiwania Goodreads.
- 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.
- Kliknij „Scrape” — dane trafiają do uporządkowanej tabeli w kilka sekund.
- 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.
Wypróbuj Thunderbit do scrapowania Goodreads
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


