Harva asia latistaa fiiliksen yhtä tehokkaasti kuin se, että kirjoitat 30 riviä Pythonia, käynnistät Goodreads-kaapijasi ja saat vastaukseksi []. Tyhjän listan. Ei mitään. Vain sinä ja vilkkuva kursori.
Olen nähnyt tämän tapahtuvan kymmeniä kertoja — meidän omissa sisäisissä kokeiluissamme , kehittäjäfoorumeilla ja GitHub-issueissa, joita kertyy hylättyjen scraper-repojen perään. Valitukset ovat lähes aina samat: "top reviews -osio on tyhjä, näyttää vain []", "oli sivunumero mikä tahansa, se kaappaa aina ensimmäisen sivun", "koodini toimi viime vuonna, nyt se on rikki." Ja vielä pahempaa: Goodreadsin API poistettiin käytöstä joulukuussa 2020, joten vanhoissa ohjeissa toistuva neuvo "käytä vain APIa" ei enää vie mihinkään.
Jos haluat tänään Goodreadsista jäsenneltyä kirjadataa — nimiä, kirjailijoita, arvioita, arvosteluja, genrejä, ISBN-numeroita — kaapiminen on käytännössä se reitti, joka toimii. Tässä oppaassa käydään läpi toimiva ja kattava tapa kaapata Goodreads Pythonilla: käsitellään JavaScriptillä renderöity sisältö, sivutus, estojen kiertäminen ja viennit. Ja jos Python ei ole sinun juttusi, näytän myös koodittoman vaihtoehdon, jolla homma hoituu noin kahdella klikkauksella.
Mitä Goodreadsin kaappaaminen tarkoittaa – ja miksi tehdä se Pythonilla?
Goodreadsin kaappaaminen tarkoittaa kirjadatan automaattista poimimista Goodreadsin verkkosivuilta koodin avulla sen sijaan, että kopioisit kaiken käsin. Tietoja voivat olla esimerkiksi kirjan nimi, kirjailija, arvio, arvostelujen määrä, genret, ISBN, sivumäärä, julkaisupäivä ja paljon muuta.
Goodreads on yksi maailman suurimmista kirjatietokannoista: sillä on yli ja noin . Yli 18 miljoonaa kirjaa päätyy "Want to Read" -hyllyille joka kuukausi. Juuri tällainen jatkuvasti päivittyvä, jäsennelty data kiinnostaa kustantajia, data-analyytikoita, kirjakauppiaita ja tutkijoita vuodesta toiseen.
Python on tämän tyyppiseen työhön käytännössä oletuskieli — se pyörittää noin kaikista kaapimisprojekteista. Kirjastot ovat kypsiä (requests, BeautifulSoup, Selenium, Playwright, pandas), syntaksi on aloittelijaystävällinen ja yhteisö valtava.
Jos et ole koskaan kaapinut verkkosivua ennen, Python on hyvä paikka aloittaa.
Miksi kaapata Goodreadsia Pythonilla? Käytännön käyttötapaukset
Ennen kuin mennään koodiin, kannattaa kysyä: kuka tätä dataa oikeasti tarvitsee ja mihin sitä käytetään?
| Käyttötapaus | Kenelle hyödyllinen | Mitä kaapataan |
|---|---|---|
| Kustantajien markkinatutkimus | Kustantajat, kirjallisuusagentit | Nousussa olevat genret, parhaiten arvioidut nimet, uudet kirjailijat, kilpailijoiden arviot |
| Kirjasuositusjärjestelmät | Data-analyytikot, harrastajat, sovelluskehittäjät | Arviot, genret, käyttäjähyllyt, arvostelujen sävy |
| Hinta- ja varastoseuranta | Verkkokauppiaat, kirjakauppiaat | Trendaavat nimet, arvostelujen määrä, "Want to Read" -lukemat |
| Akateeminen tutkimus | Tutkijat, opiskelijat | Arvosteluteksti, arviojakaumat, genreluokittelut |
| Lukemisanalytiikka | Kirjabloggaajat, omat projektit | Omat hyllytiedot, lukuhistoria, vuoden yhteenvetotilastot |
Muutama konkreettinen esimerkki: UCSD Book Graph — yksi eniten siteeratuista akateemisista aineistoista suosittelututkimuksessa — sisältää , ja kaikki on koottu julkisesti saatavilla olevista Goodreads-hyllyistä. Useat Kaggle-aineistot (goodbooks-10k, Best Books Ever jne.) lähtivät liikkeelle Goodreadsin kaappaamisesta. Lisäksi vuoden 2025 Big Data and Society -tutkimus kokosi laskennallisesti analysoidakseen, miten sponsoroinnit vaikuttavat alustaan.
Kaupallisella puolella Bright Data myy valmiiksi kaapattuja Goodreads-aineistoja jo noin 0,50 dollarin hinnalla per 1,000 tietuetta — hyvä osoitus siitä, että tällä datalla on oikeaa markkina-arvoa.
Goodreadsin API on poissa – tässä on sen korvannut ratkaisu
Jos olet viime aikoina etsinyt "Goodreads APIa", olet todennäköisesti päätynyt vanhentuneeseen ohjeeseen. 8. joulukuuta 2020 Goodreads lopetti hiljaisesti uusien kehittäjäavainten myöntämisen. Ei blogipostausta, ei sähköpostia — vain pieni ilmoitus dokumentaation sivulla ja iso määrä hämmentyneitä kehittäjiä.

Seuraukset näkyivät heti. Yksi kehittäjä, Kyle K, rakensi Discord-botin, jolla jaettiin kirjasuosituksia — "ja yhtäkkiä PUF, se vain lakkasi toimimasta." Toinen, Matthew Jones, menetti API-yhteyden viikkoa ennen Redditin r/Fantasy Stabby Awards -äänestystä, mikä pakotti palaamaan Google Formsiin. Opiskelija nimeltä Elena Neacsu sai maisterityönsä projektin sekoamaan kesken kehityksen.
Mitä vaihtoehtoja siis on jäljellä? Tämänhetkinen tilanne näyttää tältä:
| Tapa | Saatavilla oleva data | Käytön helppous | Rajoitukset | Tila |
|---|---|---|---|---|
| Goodreads API | Täydet metatiedot, arvostelut | Helppo (oli) | 1 pyyntö/s | Poistettu käytöstä (joulu 2020) — ei uusia avaimia |
| Open Library API | Nimet, kirjailijat, ISBN:t, kannet (~30M nimekettä) | Helppo | 1-3 pyyntöä/s | Aktiivinen, ilmainen, ei tunnistautumista |
| Google Books API | Metatiedot, esikatselut | Helppo | 1,000/päivä ilmaiseksi | Aktiivinen (aukkoja ei-englanninkielisissä ISBN-tiedoissa) |
| Python-kaappaus (requests + BS4) | Kaikki, mikä on mukana ensimmäisessä HTML:ssä | Kohtalainen | Itse hallittava | Toimii staattiselle sisällölle |
| Python-kaappaus (Selenium/Playwright) | Myös JavaScriptillä renderöity sisältö | Vaikeampi | Itse hallittava | Tarvitaan arvosteluihin ja joihinkin listoihin |
| Thunderbit (kooditon Chrome-laajennus) | Kaikki näkyvissä oleva data | Erittäin helppo (2 klikkausta) | Kreditipohjainen | Aktiivinen — Pythonia ei tarvita |
Open Library on erinomainen täydentävä lähde, erityisesti ISBN-hakuun ja perusmetatietoihin. Mutta jos tarvitset arvioita, arvosteluja, genre-tageja tai "Want to Read" -määriä, kaivat dataa suoraan Goodreadsista — joko Pythonilla tai työkalulla kuten Thunderbit, joka voi kaapata Goodreads-sivuja (myös kirjan alisivuja) AI:n ehdottamilla kentillä ja viedä tiedot suoraan Google Sheetsiin, Notioniin tai Airtableen.
Miksi Goodreadsin Python-kaapijasi palauttaa tyhjiä tuloksia – ja miten korjaat sen
Tämä on se osio, jonka toivoin itsekin löytäneeni aloittaessani Goodreads-datan parissa. "Tyhjät tulokset" on ylivoimaisesti yleisin valitus kehittäjäfoorumeilla, ja sille on useita eri syitä — jokaiselle oma korjauksensa.
| Oire | Syy | Korjaus |
|---|---|---|---|
| Arvostelut/arviot palautuvat muodossa [] | JavaScriptillä renderöity sisältö (React/lazy load) | Käytä Seleniumia tai Playwrightia requests:in sijaan |
| Kaappaa aina vain sivun 1 | Sivutusparametri ohitetaan tai sisältö tulee JS:llä | Anna ?page=N oikein; käytä selausautomaatioita loputtomaan skrolliin |
| Koodi toimi viime vuonna, nyt epäonnistuu | Goodreads muutti HTML-luokkien nimiä | Käytä kestävämpiä valitsimia (JSON-LD, data-testid-attribuutit) |
| 403 / esto muutaman pyynnön jälkeen | Otsikot puuttuvat / pyynnöt liian nopeita | Lisää User-Agent, time.sleep(), kierrätä proxyjä |
| Kirjaudu sisään -seinämä hylly- ja listasivuilla | Eväste-/istuntotunnus vaaditaan | Käytä requests.Session():ia evästeillä tai selauspohjaista kaappausta |
JavaScriptillä renderöity sisältö: arvostelut ja arviot näkyvät tyhjinä
Goodreads käyttää React-pohjaista käyttöliittymää. Kun kutsut requests.get()-funktiota kirjasivulta, saat alkuperäisen HTML:n — mutta arvostelut, arviojakaumat ja monet "lisätiedot" latautuvat myöhemmin JavaScriptin kautta. Kaapijasi ei yksinkertaisesti näe niitä.
Ratkaisu: aina kun tarvitset JavaScriptillä renderöityä sisältöä, siirry Seleniumiin tai Playwrightiin. Uusissa projekteissa suosittelen Playwrightia — se on WebSocket-pohjaisen protokollansa ansiosta, ja siinä on paremmat oletusominaisuudet stealthiin ja async-käyttöön.

Sivutus, joka palauttaa vain sivun 1
Tämä on salakavala ongelma. Kirjoitat silmukan, kasvatat ?page=N-parametria, mutta saat silti joka kerta samat tulokset. Goodreadsissa hyllysivut palauttavat hiljaa sivun 1 sisällön riippumatta ?page=-parametrista, jos et ole kirjautunut sisään. Ei virheilmoitusta, ei uudelleenohjausta — vain sama ensimmäinen sivu yhä uudelleen.
Ratkaisu: lisää todennettu istuntoeväste (erityisesti _session_id2). Kerron tästä lisää sivutusosiossa alempana.
Koodi, joka toimi viime vuonna, ei enää toimi
Goodreads vaihtaa aika ajoin HTML-luokkien nimiä ja sivurakennetta. GitHubissa suosittu maria-antoniak/goodreads-scraper -repo sisältää nykyään pysyvän huomautuksen: "This project is unmaintained and no longer functioning." Ratkaisu on käyttää kestävämpiä valitsimia — JSON-LD-rakennettua dataa (joka noudattaa schema.org-standardia ja muuttuu harvoin) tai data-testid-attribuutteja hauraiden luokkanimien sijaan.
403-virheet tai estot
Pythonin requests-kirjastolla on erilainen TLS-jälki kuin Chromella. Vaikka käyttäisit Chromea muistuttavaa User-Agent-merkkijonoa, botintunnistusjärjestelmät kuten AWS WAF (jota Goodreads käyttää Amazonin tytäryhtiönä) voivat huomata ristiriidan. Ratkaisu: lisää realistiset selainotsikot, käytä time.sleep()-viiveitä 3–8 sekuntia pyyntöjen välissä ja harkitse curl_cffi-kirjastoa TLS-jäljen vastaavuuden parantamiseksi, jos kaappaat suuria määriä dataa.
Kirjautumisseinät hylly- ja listasivuilla
Jotkin Goodreadsin hylly- ja listasivut vaativat kirjautumisen, jotta koko sisältö näkyy, erityisesti sivun 5 jälkeen. Käytä requests.Session()-objektia selaimestasi viedyillä evästeillä tai selauspohjaista kaappausta Seleniumilla/Playwrightilla kirjautuneessa profiilissa. Thunderbit hoitaa tämän luonnollisesti, koska se toimii omassa kirjautuneessa Chrome-selaimessasi.
Ennen kuin aloitat
- Vaikeustaso: Keskitaso (perus-Pythonin tuntemus oletetaan)
- Aika: Noin 20–30 minuuttia koko läpikäyntiin
- Tarvitset:
- Python 3.8+
- Chrome-selain (DevTools-tarkastelua ja Selenium/Playwrightia varten)
- Kirjastot:
requests,beautifulsoup4,seleniumtaiplaywright,pandas - (Valinnainen)
gspreadGoogle Sheets -vientiin - (Valinnainen) koodittomaan vaihtoehtoon

Vaihe 1: Valmistele Python-ympäristö
Asenna tarvittavat kirjastot. Avaa terminaali ja suorita:
1pip install requests beautifulsoup4 selenium pandas lxml
Jos suosittelet Playwrightia (suosittelen uusiin projekteihin):
1pip install playwright
2playwright install chromium
Google Sheets -vientiin (valinnainen):
1pip install gspread oauth2client
Varmista, että käytössäsi on Python 3.8 tai uudempi. Tarkista versio komennolla python --version.
Asennuksen jälkeen kaikkien kirjastojen pitäisi aueta ilman virheitä. Kokeile python -c "import requests, bs4, pandas; print('Ready')" varmistaaksesi, että kaikki toimii.
Vaihe 2: Lähetä ensimmäinen pyyntö kunnollisilla otsikoilla
Avaa selaimessa Goodreadsin genre- tai listasivu — esimerkiksi https://www.goodreads.com/list/show/1.Best_Books_Ever. Haetaan nyt sivu Pythonilla.
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}")
Sinun pitäisi nähdä Status: 200. Jos saat 403, tarkista otsikot uudelleen — Goodreadsin AWS WAF tarkistaa realistisen User-Agentin ja hylkää liian pelkistetyt pyynnöt. Yllä olevat otsikot jäljittelevät aitoa Chrome-selaimen istuntoa.
Vaihe 3: Tarkastele sivua ja löydä oikeat valitsimet
Avaa Chrome DevTools (F12) Goodreadsin listasivulla. Napsauta hiiren oikealla kirjan nimeä ja valitse "Inspect". Näet kunkin kirjaelementin DOM-rakenteen.
Listasivuilla jokainen kirja on tyypillisesti kääritty <tr>-elementtiin, jossa on itemtype="http://schema.org/Book". Sen sisältä löydät:
- Nimi:
a.bookTitle(linkkiteksti on kirjan nimi,hrefkertoo kirjan URL:n) - Kirjailija:
a.authorName - Arvio:
span.minirating(sisältää keskiarvon ja arvioiden määrän) - Kansikuva:
imgkirjarivin sisällä
Yksittäisten kirjasivujen kohdalla kannattaa hypätä suoraan JSON-LD:hen. Goodreads upottaa rakenteellisen datan <script type="application/ld+json"> -tagiin schema.org Book -muodossa. Se on huomattavasti vakaampi kuin luokkanimet, joita Goodreads muuttaa mielensä mukaan.
Vaihe 4: Poimi kirjatiedot yhdeltä listasivulta
Puretaan listasivu ja kerätään perusinfo kustakin kirjasta:
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"Löytyi {len(books)} kirjaa sivulta 1")
28for b in books[:3]:
29 print(b)
Sinun pitäisi nähdä suunnilleen 100 kirjaa per listasivu. Jokaisella rivillä on nimi, kirjailija, arvio, esimerkiksi "4.28 avg rating — 9,031,257 ratings", sekä URL kirjan omalle sivulle.
Vaihe 5: Kaappaa alisivuilta tarkemmat kirjatiedot
Listasivu antaa perustiedot, mutta todellinen hyöty — ISBN, koko kuvaus, genre-tägit, sivumäärä, julkaisupäivä — löytyy kunkin kirjan omalta sivulta. Tässä JSON-LD loistaa.
1import json
2import time
3def scrape_book_detail(book_url, headers):
4 """Käy yksittäisellä kirjasivulla ja poimi tarkat metatiedot JSON-LD:n avulla."""
5 resp = requests.get(book_url, headers=headers, timeout=15)
6 if resp.status_code != 200:
7 return {}
8 soup = BeautifulSoup(resp.text, "lxml")
9 script = soup.find("script", {"type": "application/ld+json"})
10 if not script:
11 return {}
12 data = json.loads(script.string)
13 agg = data.get("aggregateRating", {})
14 # Genre-tägit eivät ole JSON-LD:ssä; käytetään HTML:ää vararatkaisuna
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], # lyhennetty esikatseluun
25 "genres": ", ".join(genres[:5]),
26 }
27# Esimerkki: rikastetaan ensimmäiset 3 kirjaa
28for book in books[:3]:
29 details = scrape_book_detail(book["book_url"], headers)
30 book.update(details)
31 print(f"Kaapattu: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
32 time.sleep(4) # kunnioita rajoituksia
Lisää pyyntöjen väliin time.sleep() 3–8 sekunnin pituisena. Goodreadsin rajoitukset alkavat yleensä näkyä noin 20–30 pyynnön jälkeen minuutissa yhdestä IP-osoitteesta, ja jos etenet liian nopeasti, saat 403-virheitä tai CAPTCHA-testejä.
Tämä kahden vaiheen malli — ensin kaikki kirja-URL:t listasivuilta, sitten jokainen kirjan oma sivu — on luotettavampi ja helpompi jatkaa, jos prosessi keskeytyy. Sitä käyttävät useimmat toimivat Goodreads-kaapimet.
Sivuhuomio: osaa tehdä tämän automaattisesti alisivukaappauksella. AI käy jokaisella kirjan omalla sivulla ja täydentää taulukkoon ISBN:n, kuvauksen, genret ja muut tiedot — ilman koodia, silmukoita tai sleep-ajastimia.
Vaihe 6: Käsittele JavaScriptillä renderöity sisältö Seleniumilla
Jos tarvitsemasi sisältö latautuu JavaScriptin kautta — arvostelut, arviojakaumat, "more details" -osiot — tarvitset selainautomaatiota. Tässä esimerkki Seleniumilla:
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# Odota, että arvostelut latautuvat
14try:
15 WebDriverWait(driver, 10).until(
16 EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
17 )
18except:
19 print("Arvostelut eivät latautuneet — sivu voi vaatia kirjautumista tai JS aikakatkaisi")
20# Parsitaan nyt täysin renderöity sivu
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"Arvio: {stars.get_text(strip=True) if stars else 'N/A'}")
28 print(f"Arvostelu: {text.get_text(strip=True)[:150] if text else 'N/A'}...")
29 print()
30driver.quit()
Milloin käyttää Seleniumia ja milloin requestsia:
- Käytä
requests+ BeautifulSoup -yhdistelmää kirjametatietoihin (JSON-LD), listasivuihin, hyllysivuihin (sivu 1) ja Choice Awards -dataan - Käytä Seleniumia tai Playwrightia arvosteluihin, arviojakaumiin ja kaikkeen, mikä ei näy raakassa HTML:ssä
Playwright on yleensä parempi valinta uusiin projekteihin — se on nopeampi, kevyempi muistille ja stealth-oletuksiltaan parempi. Seleniumilla on kuitenkin suurempi yhteisö ja enemmän valmiita Goodreads-esimerkkejä.
Sivutus, joka oikeasti toimii: koko Goodreads-listan kaappaaminen
Sivutus on Goodreads-kaapimien yleisin kompastuskivi, enkä ole löytänyt yhtäkään kilpailijan ohjetta, joka käsittelisi sen kunnolla. Näin saat sen toimimaan.
Miten Goodreadsin sivutus-URL:t toimivat
Goodreads käyttää useimmilla sivuilla yksinkertaista ?page=N-parametria:
- Listat:
https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2 - Hyllyt:
https://www.goodreads.com/shelf/show/thriller?page=2 - Haku:
https://www.goodreads.com/search?q=fantasy&page=2
Jokaisella listasivulla on yleensä 100 kirjaa. Hyllyillä niitä on 50 sivua kohden.
Sivutussilmukka, joka tietää milloin lopettaa
1import time
2all_books = []
3base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
4for page_num in range(1, 50): # varmuusraja 50 sivua
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"Sivu {page_num}: status {resp.status_code}, lopetetaan.")
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"Sivu {page_num}: ei kirjoja löytynyt, loppu saavutettu.")
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"Sivu {page_num}: kaapattu {len(rows)} kirjaa (yhteensä: {len(all_books)})")
23 time.sleep(5) # 5 sekunnin viive sivujen välissä
24print(f"\nValmis. Kerätty yhteensä: {len(all_books)} kirjaa")
Viimeinen sivu löytyy tarkistamalla, onko tuloslista tyhjä (ei tr[itemtype="http://schema.org/Book"] -elementtejä) tai puuttuuko "next"-linkki (a.next_page).
Erikoistapaus: kirjautuminen vaaditaan sivun 5 jälkeen
Tämä on ansa, johon lähes kaikki lankeavat: jotkin Goodreadsin hylly- ja listasivut palauttavat hiljaa sivun 1 sisällön, kun pyydät sivua 6+ ilman tunnistautumista. Ei virheilmoitusta, ei uudelleenohjausta — vain sama data toistuu.
Korjaus: vie _session_id2-eväste selaimestasi (esimerkiksi evästeiden vientilaajennuksella tai Chrome DevTools > Application > Cookies) ja lisää se pyyntöihin:
1session = requests.Session()
2session.headers.update(headers)
3session.cookies.set("_session_id2", "YOUR_SESSION_COOKIE_VALUE_HERE", domain=".goodreads.com")
4# Käytä nyt session.get() requests.get():n sijaan
5resp = session.get(f"{base_url}?page=6", timeout=15)
Thunderbit hoitaa sekä klikkauspohjaisen että loputtoman sivutuksen natiivisti ilman koodia tai evästeiden hallintaa. Jos sivutuslogiikkasi hajoaa jatkuvasti, sitä kannattaa harkita.
Täysi, kopioitava Python-skripti
Tässä on koko koottu skripti. Se käsittelee otsikot, sivutuksen, alisivukaappauksen JSON-LD:n avulla, viiveet ja CSV-viennin. Olen testannut tämän live-Goodreads-sivuilla vuoden 2025 puolivälissä.
1"""
2goodreads_scraper.py — Kaappaa Goodreads-listan sivutuksella ja rikastaa tiedot kirjan omilta sivuilta.
3Käyttö: python goodreads_scraper.py
4Tuloste: 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 # säädä tarpeen mukaan
16DELAY_LISTING = 5 # sekuntia listasivujen välillä
17DELAY_DETAIL = 4 # sekuntia kirjasivujen välillä
18OUTPUT_FILE = "goodreads_books.csv"
19def scrape_listing_page(url):
20 """Palauttaa yhden listasivun kirjat dict-listana: nimi, kirjailija, book_url."""
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 """Käy kirjasivulla ja poimi metatiedot JSON-LD:stä + HTML-varalla."""
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 # --- Vaihe 1: kerää kirja-URL:t listasivuilta ---
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"Sivu {page}: tyhjä — sivutus lopetetaan.")
70 break
71 all_books.extend(page_books)
72 print(f"Sivu {page}: {len(page_books)} kirjaa (yhteensä: {len(all_books)})")
73 time.sleep(DELAY_LISTING)
74 # --- Vaihe 2: rikasta jokainen kirja sivukohtaisella datalla ---
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 # --- Vie CSV:ksi ---
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"\nTallennettu {len(all_books)} kirjaa tiedostoon {OUTPUT_FILE}")
88 else:
89 print("Kirjoja ei kaapattu.")
90if __name__ == "__main__":
91 main()
Kun MAX_PAGES = 3, tämä skripti kerää noin 300 kirjaa "Best Books Ever" -listalta, käy jokaisen kirjan omalla sivulla ja kirjoittaa kaiken CSV:hen. Omalla koneellani tähän menee noin 25 minuuttia (suurin osa ajasta kuluu 4 sekunnin viiveisiin detail-sivujen välillä). Tulostettava CSV sisältää sarakkeet kuten title, author, book_url, isbn, pages, avg_rating, rating_count, review_count, description, genres, language, format ja published.
CSV:tä pidemmälle: Google Sheets gspreadillä
Jos haluat datan Google Sheetsiin CSV:n sijaan tai lisäksi, lisää tämä CSV-viennin jälkeen:
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 siirretty Google Sheetiin.")
Tarvitset Google Cloud -palvelutilin, jossa Sheets- ja Drive-API:t on otettu käyttöön. käy asetukset läpi noin viidessä minuutissa. Käytä batch-toimintoja (append_rows() listalla listoista), jos lähetät yli muutaman sadan rivin verran dataa — Googlen raja on 300 pyyntöä per 60 sekuntia per projekti.
Jos kaikki tämä tuntuu ylimitoitetulta, Thunderbit vie tiedot Google Sheetsiin, Airtableen, Notioniin, Exceliin, CSV:ksi ja JSONiksi — ei kirjastojen asennusta, ei tunnistetiedostoja, ei API-kiintiöiden kanssa painimista.
Kooditon vaihtoehto: kaappaa Goodreads Thunderbitilla
Kaikki eivät halua ylläpitää Python-skriptiä. Ehkä olet kustantaja, joka tekee kertaluonteista markkina-analyysiä, tai kirjabloggaaja, joka haluaa vain taulukon tämän vuoden myydyimmistä kirjoista. Juuri tätä varten Thunderbit tehtiin.
Näin kaappaat Goodreadsin Thunderbitilla
- Asenna Thunderbit Chrome -laajennus ja avaa Goodreadsin lista-, hylly- tai hakutulosivu.
- Klikkaa "AI Suggest Fields" Thunderbitin sivupalkissa. AI lukee sivun ja ehdottaa sarakkeita — yleensä nimi, kirjailija, arvio, kansikuvan URL ja kirjalinkki.
- Klikkaa "Scrape" — data poimitaan jäsenneltyyn taulukkoon sekunneissa.
- Vie tiedot Google Sheetsiin, Exceliin, Airtableen, Notioniin, CSV:ksi tai JSONiksi.
Tarkempaa kirjadataa varten Thunderbitin alisivukaappaus käy jokaisen kirjan omalla sivulla ja täydentää taulukon automaattisesti ISBN:llä, kuvauksella, genreillä, sivumäärällä ja muulla — ilman silmukoita, sleep-ajastimia tai debuggausta.
Thunderbit käsittelee myös sivutetut listat natiivisti. Kerrot sille, että klikkaa "Next" tai rullaa eteenpäin, ja se kerää datan kaikilta sivuilta ilman koodia.
Kompromissi on yksinkertainen: Python-skripti antaa täyden hallinnan ja on ilmainen (lukuun ottamatta aikaasi), kun taas Thunderbit vaihtaa osan joustavuudesta valtavaan ajansäästöön ja lähes olemattomaan ylläpitoon. 300 kirjan listalla Python-skripti vie noin 25 minuuttia ajonaikaa plus sen ajan, jonka käytit kirjoittamiseen ja virheenkorjaukseen. Thunderbit saa saman datan noin kolmessa minuutissa kahdella klikkauksella.
Goodreadsin kaappaaminen vastuullisesti: robots.txt, käyttöehdot ja etiikka
Tämä ansaitsee suoran vastauksen, ei heitellen kirjoitettua vastuuvapauslauseketta.
Mitä Goodreadsin robots.txt oikeasti sanoo
Goodreadsin nykyinen robots.txt on yllättävän tarkka. Kirjan sivut (/book/show/), julkiset listat (/list/show/), julkiset hyllyt (/shelf/show/) ja kirjailijasivut (/author/show/) eivät ole estettyjä. Estettyjä ovat /api, /book/reviews/, /review/list, /review/show, /search ja useita muita polkuja. GPTBot ja CCBot (Common Crawl) on estetty kokonaan asetuksella Disallow: /. Bingbotille on määritetty Crawl-delay: 5, mutta yleistä viivettä ei ole.
Goodreadsin käyttöehdot selkokielellä
Käyttöehdot (viimeksi päivitetty 28.4.2021) kieltävät "kaikenlaisen data miningin, bottien tai vastaavien tiedonkeruu- ja poimintatyökalujen käytön". Tämä on laaja muotoilu, ja se kannattaa ottaa vakavasti — mutta oikeuskäytännössä käyttöehtojen rikkominen ei yksinään tarkoita rikollista "valtuuttamatonta pääsyä". -ratkaisussa todettiin, että "käyttöehtorikkomusten kriminalisointi vaarantaa sen, että jokaisesta verkkosivusta tulee oma rikosoikeudellinen lainkäyttöalueensa."
Parhaat käytännöt
- Pidä pyyntöjen väliin viive: 3–8 sekuntia pyyntöjen välillä (Goodreadsin oma robots.txt ehdottaa bottien viiveeksi 5 sekuntia)
- Pysy alle 5,000 pyynnössä päivässä yhdestä IP-osoitteesta
- Kaappaa vain julkisesti saatavilla olevia sivuja — vältä kirjautumisen takana olevan datan massakaappaamista
- Älä jaa raakoja arvostelutekstejä kaupallisesti — arvostelut ovat tekijänoikeudellista luovaa sisältöä
- Tallenna vain tarpeellinen ja määritä säilytysaikataulu
- Henkilökohtainen tutkimus vs. kaupallinen käyttö: Julkisen datan kaappaaminen omaan analyysiin tai akateemiseen tutkimukseen hyväksytään yleensä paremmin. Kaupallinen uudelleenjakelu nostaa juridista riskiä.
Thunderbitin kaltaisen työkalun käyttö (joka kaappaa omassa selainistunnossasi) näyttää visuaalisesti aivan tavalliselta selaamiselta, mutta samat eettiset periaatteet pätevät työkalusta riippumatta. Jos haluat syventyä aiheeseen, käsittelimme erikseen.
Vinkit ja yleiset sudenkuopat
Vinkki: Aloita aina JSON-LD:stä. Ennen kuin kirjoitat monimutkaisia CSS-valitsimia, tarkista, löytyykö tarvitsemasi data <script type="application/ld+json"> -tagista. Se on vakaampi, helpompi purkaa ja harvemmin hajoaa Goodreadsin etusivun muuttuessa.
Vinkki: Käytä kahden vaiheen strategiaa. Kerää ensin kaikki kirja-URL:t listasivuilta ja käy sitten jokainen detail-sivu läpi. Näin kaapija on helpompi jatkaa, jos se kaatuu puolivälissä, ja URL-listan voi tallentaa levylle tarkistuspisteeksi.
Sudenkuoppa: Puuttuvien kenttien käsittelemättä jättäminen. Kaikilla kirjasivuilla ei ole ISBN:ää, genre-tägejä tai kuvausta. Käytä aina .get()-metodia oletusarvon kanssa tai ympäröi valitsimet if-tarkistuksilla. Yksi NoneType-virhe voi kaataa kolmen tunnin kaappausajon.
Sudenkuoppa: Liian nopea ajo. On houkuttelevaa laittaa time.sleep(0.5) ja mennä kovaa. Goodreads alkaa kuitenkin palauttaa 403-virheitä noin 20–30 nopean pyynnön jälkeen, ja kun sinut on liputettu, joudut ehkä odottamaan tunteja tai vaihtamaan IP-osoitetta. 4–5 sekunnin viive on yleensä paras kohta.
Sudenkuoppa: Vanhentuneisiin ohjeisiin luottaminen. Jos oppaassa viitataan Goodreads APIin tai käytetään luokkanimiä kuten .field.value tai #bookTitle, se on todennäköisesti vanhentunut. Tarkista aina valitsimet live-sivulta ennen kuin rakennat kaapijasi.
Lisää vinkkejä oikeiden kaappaustyökalujen ja -kehysten valintaan löydät oppaistamme: ja .
Yhteenveto ja tärkeimmät opit
Goodreadsin kaappaaminen Pythonilla on täysin mahdollista — kunhan tiedät, missä sudenkuopat ovat. Tiivistettynä:
- Goodreadsin API on poissa (joulukuusta 2020 alkaen). Kaappaaminen on käytännössä tärkein tapa saada alustalta jäsenneltyä kirjadataa.
- Tyhjät tulokset johtuvat lähes aina JavaScriptillä renderöidystä sisällöstä, vanhentuneista valitsimista, puuttuvista otsikoista tai sivutuksen tunnistautumisongelmista — eivät siitä, että koodisi olisi väärin.
- JSON-LD on paras ystäväsi kirjametatietojen kanssa. Se on vakaa, jäsennelty ja muuttuu harvoin.
- Sivutus vaatii tunnistautumista monilla hylly- ja listasivuilla sivun 5 jälkeen. Lisää
_session_id2-eväste. - Rajoitukset ovat todellisia. Käytä 3–8 sekunnin viiveitä ja pysy alle 5,000 pyynnössä päivässä.
- Kahden vaiheen strategia (URL:t ensin, detail-sivut sitten) on luotettavampi ja helpompi jatkaa keskeytyksen jälkeen.
- Ei-koodaajille (tai kaikille, jotka arvostavat vapaita iltapäiviään) hoitaa kaiken tämän — JS-renderöinnin, sivutuksen, alisivujen rikastamisen ja viennin — noin kahdella klikkauksella.
Kaappaa vastuullisesti, kunnioita robots.txt:tä, ja toivottavasti kirjadata palautuu jatkossa aina muuna kuin [].
Usein kysytyt kysymykset
Voiko Goodreadsin APIa vielä käyttää?
Ei. Goodreads poisti julkisen API:n käytöstä joulukuussa 2020 eikä myönnä enää uusia kehittäjäavaimia. Myös avaimet, jotka olivat käyttämättä 30 päivää, deaktivoitiin automaattisesti. Tällä hetkellä ohjelmalliseen kirjadatan hakuun vaihtoehtoina ovat verkkokaappaus tai vaihtoehtoiset API:t (kuten Open Library tai Google Books).
Miksi Goodreads-kaapijani palauttaa tyhjiä tuloksia?
Yleisin syy on JavaScriptillä renderöity sisältö. Goodreads lataa arvostelut, arviojakaumat ja monet detail-osat Reactin/JavaScriptin kautta, eikä tavallinen requests.get() näe niitä. Käytä sellaisilla sivuilla Seleniumia tai Playwrightia. Muita syitä ovat vanhentuneet CSS-valitsimet (Goodreads muutti HTML:ää), puuttuvat User-Agent-otsikot (jotka aiheuttavat 403-eston) tai kirjautumattomat pyynnöt sivutetuille hyllysivuille.
Onko Goodreadsin kaappaaminen laillista?
Julkisesti saatavilla olevan datan kaappaaminen henkilökohtaiseen tai tutkimuskäyttöön hyväksytään yleensä nykyisen oikeuskäytännön valossa (hiQ v. LinkedIn, Meta v. Bright Data). Goodreadsin käyttöehdot kuitenkin kieltävät automaattisen tiedonkeruun, ja robots.txt kannattaa aina tarkistaa. Vältä tekijänoikeudella suojatun arvostelutekstin kaupallista uudelleenjakelua ja pidä pyyntömäärät maltillisina, jotta et kuormita palvelua.
Miten kaappaan useita sivuja Goodreadsissa?
Lisää ?page=N hylly- tai lista-URL:iin ja käy sivunumerot läpi silmukassa. Tarkista tyhjät tulokset tai "next"-linkin puuttuminen viimeisen sivun tunnistamiseksi. Tärkeää: jotkin hyllysivut vaativat tunnistautumisen (_session_id2-eväste) näyttääkseen tulokset sivun 5 jälkeen — ilman sitä saat hiljaisesti vain sivun 1 dataa toistettuna.
Voinko kaapata Goodreadsia ilman koodia?
Kyllä. on Chrome-laajennus, jolla kaappaat Goodreadsin kahdella klikkauksella — AI ehdottaa kentät, klikkaat "Scrape" ja viet datan suoraan Google Sheetsiin, Exceliin, Airtableen tai Notioniin. Se käsittelee JavaScriptillä renderöidyn sisällön, sivutuksen ja alisivujen rikastamisen automaattisesti ilman Pythonia tai ohjelmointia.
Lue lisää