Cara Scrape Goodreads dengan Python (Tanpa Hasil Kosong Lagi)

Terakhir diperbarui pada April 16, 2026

Tidak banyak hal yang lebih bikin frustrasi daripada nulis 30 baris Python, jalanin Goodreads scraper, lalu lihat hasilnya []. Daftar kosong. Nggak ada apa-apa. Cuma kamu dan kursor yang kedap-kedip.

Saya sudah sering lihat ini — di eksperimen internal kami di , di forum developer, dan di issue GitHub yang numpuk di repo scraper yang sudah ditinggal. Keluhannya hampir selalu sama: "bagian review teratas kosong, cuma keluar []", "berapa pun nomor halaman yang saya jalankan, hasilnya tetap halaman pertama", "kode saya dulu jalan tahun lalu, sekarang rusak." Dan yang lebih parah, Goodreads API sudah dihentikan pada Desember 2020, jadi saran "pakai API aja" yang masih sering muncul di tutorial lama sekarang sudah mentok.

Kalau hari ini kamu butuh data buku yang terstruktur dari Goodreads — judul, penulis, rating, review, genre, ISBN — scraping memang jadi jalan utamanya. Panduan ini akan nunjukin cara yang benar-benar bisa dipakai untuk scrape Goodreads dengan Python, termasuk konten yang dirender JavaScript, pagination, anti-blocking, dan ekspor data. Dan kalau kamu nggak mau ribet dengan Python, saya juga akan tunjukkan alternatif tanpa kode yang bisa beres cuma dalam beberapa klik.

Apa Itu Scraping Goodreads (dan Kenapa Pakai Python)?

Scraping Goodreads berarti mengambil data buku secara otomatis — judul, penulis, rating, jumlah review, genre, ISBN, jumlah halaman, tanggal terbit, dan lainnya — dari halaman Goodreads menggunakan kode, bukan copy-paste manual.

Goodreads adalah salah satu basis data buku terbesar di dunia, dengan lebih dari dan sekitar . Lebih dari 18 juta buku masuk ke rak "Want to Read" setiap bulan. Data yang selalu diperbarui dan terstruktur seperti ini yang bikin penerbit, data scientist, penjual buku, dan peneliti terus balik lagi.

Python adalah bahasa andalan untuk kerjaan seperti ini — sekitar proyek scraping memakainya. Pustakanya matang (requests, BeautifulSoup, Selenium, Playwright, pandas), sintaksnya ramah pemula, dan komunitasnya besar banget.

Kalau kamu belum pernah scrape website sebelumnya, Python adalah tempat paling pas buat mulai.

Kenapa Scrape Goodreads dengan Python? Contoh Kegunaan di Dunia Nyata

Sebelum masuk ke kode, bagus juga buat tanya dulu: siapa yang sebenarnya butuh data ini, dan dipakai buat apa?

Contoh PenggunaanYang DiuntungkanData yang Diambil
Riset pasar penerbitPenerbit, literary agentGenre yang sedang naik, judul dengan rating tertinggi, penulis baru, rating kompetitor
Sistem rekomendasi bukuData scientist, hobiis, pembuat aplikasiRating, genre, rak pengguna, sentimen review
Pemantauan harga & stokPenjual buku ecommerceJudul populer, volume review, jumlah "Want to Read"
Riset akademikPeneliti, mahasiswaTeks review, distribusi rating, klasifikasi genre
Analitik bacaanBlogger buku, proyek pribadiData rak pribadi, riwayat baca, statistik tahunan

Beberapa contoh nyata: UCSD Book Graph — salah satu dataset akademik yang paling sering dikutip dalam riset rekomendasi — berisi , semuanya dikumpulkan dari rak Goodreads yang bisa diakses publik. Banyak dataset Kaggle (goodbooks-10k, Best Books Ever, dan lain-lain) juga berawal dari scraping Goodreads. Dan sebuah studi 2025 di Big Data and Society mengkurasi secara komputasional untuk menganalisis bagaimana review bersponsor membentuk platform.

Dari sisi bisnis, Bright Data menjual dataset Goodreads yang sudah di-scrape dengan harga mulai $0,50 per 1.000 record — bukti kalau data ini memang punya nilai pasar nyata.

Goodreads API Sudah Hilang — Ini Penggantinya

Kalau kamu cari "Goodreads API" belakangan ini, kemungkinan besar kamu ketemu tutorial yang sudah ketinggalan zaman. Pada 8 Desember 2020, Goodreads diam-diam berhenti ngeluarin API key developer baru. Nggak ada posting blog, nggak ada email massal — cuma banner kecil di halaman dokumentasi dan banyak developer yang bingung.

goodreads-data-access-tools.webp

Dampaknya langsung kerasa. Seorang developer, Kyle K, bikin bot Discord buat berbagi rekomendasi buku — "tiba-tiba POOF, langsung berhenti bekerja." Matthew Jones kehilangan akses API seminggu sebelum voting Reddit r/Fantasy Stabby Awards, jadi terpaksa balik lagi ke Google Forms. Seorang mahasiswa pascasarjana bernama Elena Neacsu bahkan proyek tesis masternya terganggu di tengah jalan.

Jadi, apa yang tersisa? Gambaran sekarang kurang lebih begini:

PendekatanData yang TersediaKemudahan PakaiRate LimitStatus
Goodreads APIMetadata lengkap, reviewMudah (dulu)1 req/detikDihentikan (Des 2020) — tidak ada key baru
Open Library APIJudul, penulis, ISBN, cover (~30 juta judul)Mudah1-3 req/detikAktif, gratis, tanpa autentikasi
Google Books APIMetadata, pratinjauMudah1.000/hari gratisAktif (ada celah ISBN non-Inggris)
Scraping Python (requests + BS4)Apa pun yang ada di HTML awalMenengahDikelola sendiriCocok untuk konten statis
Scraping Python (Selenium/Playwright)Juga konten yang dirender JSLebih sulitDikelola sendiriDibutuhkan untuk review, sebagian daftar
Thunderbit (ekstensi Chrome tanpa kode)Data apa pun yang terlihat di halamanSangat mudah (2 klik)Berbasis kreditAktif — tanpa Python

Open Library adalah pelengkap yang kuat, terutama buat pencarian ISBN dan metadata dasar. Tapi kalau kamu butuh rating, review, tag genre, atau jumlah "Want to Read", berarti kamu memang harus scrape Goodreads langsung — entah pakai Python atau pakai tool seperti Thunderbit, yang bisa scrape halaman Goodreads (termasuk subpage detail buku) dengan field yang disarankan AI dan ekspor langsung ke Google Sheets, Notion, atau Airtable.

Kenapa Python Goodreads Scraper Anda Menghasilkan Hasil Kosong (dan Cara Memperbaikinya)

Bagian ini adalah sesuatu yang saya harap sudah ada waktu pertama kali saya kerja dengan data Goodreads. Masalah "hasil kosong" adalah keluhan paling umum di forum developer, dan penyebabnya ada beberapa — masing-masing punya solusi sendiri.

GejalaPenyebab UtamaSolusi
Review/rating mengembalikan []Konten dirender JS (React/lazy-load)Gunakan Selenium atau Playwright, bukan requests
Selalu hanya scrape halaman 1Parameter pagination diabaikan atau digerakkan JSKirim parameter ?page=N dengan benar; pakai browser automation untuk infinite scroll
Kode dulu jalan, sekarang gagalGoodreads mengubah nama class HTMLGunakan selector yang lebih tahan banting (JSON-LD, atribut data-testid)
403/terblokir setelah beberapa requestHeader kurang lengkap / request terlalu cepatTambahkan User-Agent, time.sleep(), rotasi proxy
Login wall di halaman shelf/listPerlu cookie/sesi loginGunakan requests.Session() dengan cookie atau scraping lewat browser

Konten yang Dirender JavaScript: Review dan Rating Tampil Kosong

Frontend Goodreads berjalan dengan React. Saat kamu manggil requests.get() ke halaman buku, yang kamu dapat cuma HTML awal — sementara review, distribusi rating, dan banyak bagian "info lebih lanjut" dimuat asinkron lewat JavaScript. Artinya, scraper kamu memang nggak bisa lihat data itu.

Solusinya: untuk halaman yang butuh konten hasil render JS, pindah ke Selenium atau Playwright. Untuk proyek baru, Playwright adalah pilihan saya — berkat protokol berbasis WebSocket, dan punya dukungan stealth serta async yang lebih baik.

troubleshooting-empty-array-causes.webp

Pagination yang Selalu Mengembalikan Halaman 1

Yang ini sering banget ngejebak. Kamu bikin loop, naikin ?page=N, tapi hasilnya tetap sama terus. Di Goodreads, halaman shelf bisa diam-diam ngasih konten halaman 1 meskipun parameter ?page= sudah diubah, kalau kamu belum login. Nggak ada error, nggak ada redirect — cuma data halaman pertama yang diulang-ulang.

Solusinya: sertakan cookie sesi yang terautentikasi (khususnya _session_id2). Detailnya ada di bagian pagination di bawah.

Kode yang Dulu Jalan Sekarang Gagal

Goodreads kadang mengubah nama class HTML dan struktur halaman. Repo populer maria-antoniak/goodreads-scraper di GitHub sekarang punya catatan permanen: "This project is unmaintained and no longer functioning." Solusinya adalah pakai selector yang lebih tahan perubahan — structured data JSON-LD (yang mengikuti standar schema.org dan jarang berubah) atau atribut data-testid alih-alih class name yang rapuh.

Error 403 atau Terblokir

Library requests milik Python punya TLS fingerprint yang beda dari Chrome. Bahkan dengan string User-Agent Chrome, sistem deteksi bot seperti AWS WAF (yang dipakai Goodreads, karena merupakan anak perusahaan Amazon) masih bisa mendeteksi ketidaksesuaian. Solusinya: tambahkan header browser yang realistis, beri jeda time.sleep() 3-8 detik antar request, dan pertimbangkan curl_cffi untuk menyamakan TLS fingerprint kalau kamu scraping dalam skala besar.

Login Wall di Halaman Shelf dan List

Sebagian halaman shelf dan list Goodreads butuh autentikasi untuk akses konten penuh, terutama setelah halaman 5. Pakai requests.Session() dengan cookie yang diekspor dari browser, atau pakai Selenium/Playwright dengan profil yang sudah login. Thunderbit menangani ini secara natural karena jalan di Chrome kamu sendiri yang sudah login.

Sebelum Mulai

  • Tingkat kesulitan: Menengah (diasumsikan sudah paham dasar Python)
  • Waktu yang dibutuhkan: ~20-30 menit untuk walkthrough lengkap
  • Yang kamu perlukan:
    • Python 3.8+
    • Browser Chrome (untuk inspeksi DevTools dan Selenium/Playwright)
    • Library: requests, beautifulsoup4, selenium atau playwright, pandas
    • (Opsional) gspread untuk ekspor ke Google Sheets
    • (Opsional) sebagai alternatif tanpa kode

goodreads-scraping-flow.webp

Langkah 1: Siapkan Environment Python Anda

Install library yang dibutuhkan. Buka terminal dan jalankan:

1pip install requests beautifulsoup4 selenium pandas lxml

Kalau kamu lebih suka Playwright (direkomendasikan untuk proyek baru):

1pip install playwright
2playwright install chromium

Untuk ekspor ke Google Sheets (opsional):

1pip install gspread oauth2client

Pastikan kamu memakai Python 3.8 atau lebih tinggi. Cek dengan python --version.

Setelah instalasi, kamu harus bisa mengimpor semua library tanpa error. Coba python -c "import requests, bs4, pandas; print('Ready')" untuk memastikan.

Langkah 2: Kirim Request Pertama dengan Header yang Tepat

Buka halaman genre shelf atau list Goodreads di browser — misalnya, https://www.goodreads.com/list/show/1.Best_Books_Ever. Sekarang kita ambil halaman itu dengan Python.

1import requests
2from bs4 import BeautifulSoup
3headers = {
4    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
5                  "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
6    "Accept-Language": "en-US,en;q=0.9",
7    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
8}
9url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
10response = requests.get(url, headers=headers, timeout=15)
11print(f"Status: {response.status_code}")

Kamu seharusnya lihat Status: 200. Kalau muncul 403, cek lagi header kamu — AWS WAF milik Goodreads memeriksa User-Agent yang realistis dan akan menolak request yang terlalu minimalis. Header di atas meniru sesi browser Chrome yang asli.

Langkah 3: Inspeksi Halaman dan Temukan Selector yang Tepat

Buka Chrome DevTools (F12) di halaman list Goodreads. Klik kanan pada judul buku lalu pilih "Inspect." Kamu akan lihat struktur DOM untuk tiap entri buku.

Untuk halaman list, tiap buku biasanya dibungkus di elemen <tr> dengan itemtype="http://schema.org/Book". Di dalamnya, kamu akan menemukan:

  • Judul: a.bookTitle (teks link adalah judul, href memberi URL buku)
  • Penulis: a.authorName
  • Rating: span.minirating (berisi rating rata-rata dan jumlah rating)
  • Gambar sampul: img di dalam baris buku

Untuk halaman detail buku individual, abaikan selector CSS dan langsung pakai JSON-LD. Goodreads menyematkan structured data di tag <script type="application/ld+json"> yang mengikuti format schema.org Book. Ini jauh lebih stabil daripada class name, yang bisa berubah kapan saja oleh Goodreads.

Langkah 4: Ambil Data Buku dari Satu Halaman List

Mari parse halaman list dan ambil info dasar tiap buku:

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"Found {len(books)} books on page 1")
28for b in books[:3]:
29    print(b)

Kamu seharusnya lihat sekitar 100 buku per halaman list. Tiap entri akan punya judul, penulis, string rating seperti "4.28 avg rating — 9,031,257 ratings," dan URL ke halaman detail buku.

Langkah 5: Scrape Subpage untuk Informasi Buku yang Lebih Lengkap

Halaman listing kasih kamu data dasar, tapi data paling berharga — ISBN, deskripsi lengkap, tag genre, jumlah halaman, tanggal terbit — ada di halaman masing-masing buku. Di sinilah JSON-LD sangat berguna.

1import json
2import time
3def scrape_book_detail(book_url, headers):
4    """Kunjungi satu halaman buku dan ambil metadata detail lewat 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    # Tag genre tidak ada di JSON-LD; gunakan fallback 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],  # dipotong untuk preview
25        "genres": ", ".join(genres[:5]),
26    }
27# Contoh: memperkaya 3 buku pertama
28for book in books[:3]:
29    details = scrape_book_detail(book["book_url"], headers)
30    book.update(details)
31    print(f"Scraped: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
32    time.sleep(4)  # hormati rate limit

Tambahkan time.sleep() 3-8 detik di antara request. Goodreads biasanya mulai ngebatesin sekitar 20-30 request per menit dari satu IP, dan kamu akan mulai lihat 403 atau CAPTCHA kalau terlalu cepat.

Pendekatan dua tahap ini — pertama kumpulkan semua URL buku dari halaman listing, lalu kunjungi setiap halaman detail — jauh lebih andal dan lebih gampang dilanjutkan kalau prosesnya berhenti. Ini juga strategi yang dipakai sebagian besar scraper Goodreads yang sukses.

Catatan: bisa melakukan ini secara otomatis dengan subpage scraping. AI akan membuka halaman detail tiap buku dan memperkaya tabel kamu dengan ISBN, deskripsi, genre, dan lainnya — tanpa kode, tanpa loop, tanpa sleep timer.

Langkah 6: Tangani Konten yang Dirender JavaScript dengan Selenium

Untuk halaman yang kontennya dimuat lewat JavaScript — review, breakdown rating, bagian "more details" — kamu butuh tool automation browser. Berikut contoh dengan 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# Tunggu sampai review selesai dimuat
14try:
15    WebDriverWait(driver, 10).until(
16        EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
17    )
18except:
19    print("Review tidak termuat — mungkin halaman perlu login atau JS timeout")
20# Sekarang parse halaman yang sudah ter-render penuh
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"Rating: {stars.get_text(strip=True) if stars else 'N/A'}")
28    print(f"Review: {text.get_text(strip=True)[:150] if text else 'N/A'}...")
29    print()
30driver.quit()

Kapan pakai Selenium vs. requests:

  • Pakai requests + BeautifulSoup untuk metadata buku (JSON-LD), halaman list, halaman shelf (halaman 1), dan data Choice Awards
  • Pakai Selenium atau Playwright untuk review, breakdown rating, dan konten apa pun yang nggak muncul di HTML mentah

Playwright umumnya pilihan yang lebih bagus untuk proyek baru — lebih cepat, lebih hemat memori, dan default stealth-nya lebih bagus. Tapi Selenium punya komunitas yang lebih besar dan lebih banyak contoh kode Goodreads yang sudah ada.

Pagination yang Benar-Benar Berjalan: Scraping Seluruh List Goodreads

Pagination adalah titik gagal paling umum pada scraper Goodreads, dan saya belum ketemu satu pun tutorial pesaing yang benar-benar bahas ini dengan tepat. Ini cara melakukannya dengan benar.

Cara Kerja URL Pagination Goodreads

Goodreads memakai parameter ?page=N yang simpel untuk sebagian besar halaman yang dipaginasi:

  • List: https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2
  • Shelf: https://www.goodreads.com/shelf/show/thriller?page=2
  • Search: https://www.goodreads.com/search?q=fantasy&page=2

Setiap halaman list biasanya menampilkan 100 buku. Shelf menampilkan 50 per halaman.

1import time
2all_books = []
3base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
4for page_num in range(1, 50):  # batas aman 50 halaman
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"Page {page_num}: status {resp.status_code}, berhenti.")
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"Page {page_num}: tidak ada buku, berarti sudah sampai akhir.")
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"Page {page_num}: berhasil scrape {len(rows)} buku (total: {len(all_books)})")
23    time.sleep(5)  # jeda 5 detik antar halaman
24print(f"\nSelesai. Total buku terkumpul: {len(all_books)}")

Kamu bisa deteksi halaman terakhir dengan cek apakah daftar hasil kosong (nggak ada elemen tr[itemtype="http://schema.org/Book"]) atau lihat apakah link "next" (a.next_page) sudah nggak ada.

Kasus Khusus: Login Diperlukan Setelah Halaman 5

Ini jebakan yang bikin hampir semua orang kena: sebagian halaman shelf dan list Goodreads diam-diam ngasih konten halaman 1 saat kamu minta halaman 6+ tanpa autentikasi. Nggak ada error, nggak ada redirect — cuma data yang sama berulang.

Untuk memperbaikinya, ekspor cookie _session_id2 dari browser kamu (pakai ekstensi export cookie atau Chrome DevTools > Application > Cookies) lalu sertakan di request:

1session = requests.Session()
2session.headers.update(headers)
3session.cookies.set("_session_id2", "NILAI_COOKIE_SESSION_ANDA", domain=".goodreads.com")
4# Sekarang gunakan session.get() вместо requests.get()
5resp = session.get(f"{base_url}?page=6", timeout=15)

Thunderbit menangani pagination berbasis klik maupun infinite scroll secara native, tanpa kode dan tanpa urusan cookie. Kalau logika pagination kamu terus bermasalah, ini layak dipertimbangkan.

Script Python Lengkap yang Siap Copy-Paste

Berikut script lengkap yang sudah digabung. Script ini menangani header, pagination, scraping subpage lewat JSON-LD, rate limiting, dan ekspor CSV. Saya sudah mengujinya pada halaman Goodreads aktif per pertengahan 2025.

1"""
2goodreads_scraper.py — Scrape daftar Goodreads dengan pagination dan enrichment detail buku.
3Usage: python goodreads_scraper.py
4Output: 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          # sesuaikan sesuai kebutuhan
16DELAY_LISTING = 5      # detik antar halaman list
17DELAY_DETAIL = 4       # detik antar halaman detail
18OUTPUT_FILE = "goodreads_books.csv"
19def scrape_listing_page(url):
20    """Kembalikan list dict berisi title, author, book_url dari satu halaman list."""
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    """Kunjungi halaman buku dan ambil metadata lewat JSON-LD + fallback 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    # --- Tahap 1: kumpulkan URL buku dari halaman list ---
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"Page {page}: kosong — hentikan pagination.")
70            break
71        all_books.extend(page_books)
72        print(f"Page {page}: {len(page_books)} buku (total: {len(all_books)})")
73        time.sleep(DELAY_LISTING)
74    # --- Tahap 2: perkaya setiap buku dengan data detail ---
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    # --- Ekspor ke 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"\nBerhasil menyimpan {len(all_books)} buku ke {OUTPUT_FILE}")
88    else:
89        print("Tidak ada buku yang berhasil di-scrape.")
90if __name__ == "__main__":
91    main()

Dengan MAX_PAGES = 3, script ini ngumpulin sekitar 300 buku dari daftar "Best Books Ever", lalu mengunjungi halaman detail tiap buku, lalu menulis semuanya ke CSV. Di mesin saya, proses ini memakan waktu sekitar 25 menit (terutama karena jeda 4 detik antar request halaman detail). CSV output kamu akan punya kolom seperti title, author, book_url, isbn, pages, avg_rating, rating_count, review_count, description, genres, language, format, dan published.

Ekspor Selain CSV: Google Sheets dengan gspread

Kalau kamu ingin data masuk ke Google Sheets вместо (atau selain) CSV, tambahkan kode ini setelah proses ekspor 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("Data berhasil dikirim ke Google Sheets.")

Kamu perlu Google Cloud service account dengan Sheets dan Drive API yang sudah diaktifkan. menjelaskan setup-nya dalam sekitar 5 menit. Gunakan operasi batch (append_rows() dengan daftar list) kalau kamu ngirim lebih dari beberapa ratus baris — rate limit Google adalah 300 request per 60 detik per project.

Tentu saja, kalau semua setup ini terasa berlebihan, Thunderbit mengekspor ke Google Sheets, Airtable, Notion, Excel, CSV, dan JSON dengan — tanpa setup library, tanpa file kredensial, tanpa quota API.

Alternatif Tanpa Kode: Scrape Goodreads dengan Thunderbit

Nggak semua orang mau memelihara script Python. Mungkin kamu penerbit yang cuma butuh analisis pasar sekali pakai, atau book blogger yang cuma pengin spreadsheet daftar bestseller tahun ini. Untuk skenario seperti itu, Thunderbit dibuat.

Cara Scrape Goodreads dengan Thunderbit

  1. Install ekstensi Chrome Thunderbit dari lalu buka halaman Goodreads list, shelf, atau hasil pencarian.
  2. Klik "AI Suggest Fields" di sidebar Thunderbit. AI akan membaca halaman dan menyarankan kolom — biasanya judul, penulis, rating, URL gambar cover, dan link buku.
  3. Klik "Scrape" — data langsung diekstrak ke tabel terstruktur dalam hitungan detik.
  4. Ekspor ke Google Sheets, Excel, Airtable, Notion, CSV, atau JSON.

Untuk data buku yang lebih detail (ISBN, deskripsi, genre, jumlah halaman), fitur subpage scraping Thunderbit akan membuka halaman detail tiap buku dan memperkaya tabel secara otomatis — tanpa loop, tanpa sleep timer, tanpa debugging.

Thunderbit juga menangani list berpaginasi secara native. Kamu tinggal minta tool untuk klik "Next" atau scroll, lalu data akan dikumpulkan lintas halaman tanpa kode apa pun.

Trade-off-nya sederhana: script Python kasih kamu kontrol penuh dan gratis dipakai (kecuali waktu kamu), sedangkan Thunderbit menukar sebagian fleksibilitas dengan penghematan waktu besar dan tanpa maintenance. Untuk daftar 300 buku, script Python butuh sekitar 25 menit runtime plus waktu yang kamu habiskan buat nulis dan debug. Thunderbit bisa dapetin data yang sama dalam sekitar 3 menit, cuma dengan dua klik.

Scraping Goodreads Secara Bertanggung Jawab: robots.txt, Terms of Service, dan Etika

Bagian ini penting dijelasin dengan jujur, bukan sekadar disclaimer asal lewat.

Apa Isi robots.txt Goodreads Sebenarnya

robots.txt Goodreads yang aktif ternyata cukup spesifik. Halaman detail buku (/book/show/), list publik (/list/show/), shelf publik (/shelf/show/), dan halaman penulis (/author/show/) tidak diblokir. Yang diblokir: /api, /book/reviews/, /review/list, /review/show, /search, dan beberapa path lain. GPTBot dan CCBot (Common Crawl) diblokir sepenuhnya dengan Disallow: /. Ada directive Crawl-delay: 5 untuk bingbot, tetapi tidak ada delay global.

Terms of Service Goodreads dalam Bahasa Sederhana

ToS (terakhir direvisi 28 April 2021) melarang "segala bentuk data mining, robot, atau alat pengumpulan dan ekstraksi data serupa." Kalimatnya memang luas, dan patut dianggap serius — tetapi pengadilan secara konsisten menyatakan bahwa pelanggaran ToS saja tidak otomatis berarti "unauthorized access" secara kriminal. Putusan menyebut bahwa "mengkriminalisasi pelanggaran terms of service berisiko menjadikan tiap website sebagai yurisdiksi kriminalnya sendiri."

Praktik Terbaik

  • Beri jeda request: 3-8 detik antar request (robots.txt Goodreads sendiri menyarankan 5 detik untuk bot)
  • Tetap di bawah 5.000 request per hari dari satu IP
  • Hanya scrape halaman yang bisa diakses publik — hindari scraping massal data yang cuma bisa diakses setelah login
  • Jangan mendistribusikan ulang teks review mentah untuk tujuan komersial — review adalah karya kreatif yang dilindungi hak cipta
  • Simpan hanya data yang kamu butuhkan dan tentukan jadwal retensi data
  • Penggunaan pribadi vs komersial: Scraping data publik untuk analisis pribadi atau riset akademik umumnya diterima. Risiko hukum meningkat ketika datanya didistribusikan ulang secara komersial.

Pakai tool seperti Thunderbit (yang bekerja lewat sesi browser kamu sendiri) membuat interaksinya secara visual sama seperti browsing biasa, tapi prinsip etika yang sama tetap berlaku apa pun tool yang kamu pakai. Kalau kamu ingin baca lebih lanjut tentang , kami bahas topik itu terpisah.

Tips dan Jebakan yang Sering Terjadi

Tips: Selalu mulai dari JSON-LD. Sebelum nulis selector CSS yang rumit, cek dulu apakah data yang kamu butuh ada di tag <script type="application/ld+json">. Format ini lebih stabil, lebih mudah diparse, dan lebih kecil kemungkinan rusak saat Goodreads mengubah frontend mereka.

Tips: Pakai strategi dua tahap. Pertama kumpulkan semua URL buku dari halaman list, lalu kunjungi halaman detail satu per satu. Ini bikin scraper lebih mudah dilanjutkan kalau prosesnya berhenti di tengah jalan, dan kamu bisa simpan daftar URL ke disk sebagai checkpoint.

Jebakan: Lupa menangani field yang kosong. Nggak semua halaman buku punya ISBN, tag genre, atau deskripsi. Selalu pakai .get() dengan nilai default, atau bungkus selector dengan pengecekan if. Satu error NoneType bisa bikin proses scraping 3 jam ambruk.

Jebakan: Terlalu cepat kirim request. Saya paham rasanya pengin pasang time.sleep(0.5) lalu ngebut. Tapi Goodreads akan mulai ngasih 403 setelah sekitar 20-30 request cepat, dan kalau sudah kena flag, kamu mungkin harus nunggu berjam-jam atau ganti IP. Jeda 4-5 detik adalah titik paling aman.

Jebakan: Percaya tutorial lama. Kalau sebuah panduan masih merujuk ke Goodreads API, atau pakai class name seperti .field.value atau #bookTitle, kemungkinan besar itu sudah usang. Selalu verifikasi selector di halaman live sebelum membangun scraper.

Untuk pilihan tool dan framework scraping yang tepat, lihat juga panduan kami tentang dan .

Kesimpulan dan Poin Penting

Scraping Goodreads dengan Python sepenuhnya bisa dilakukan — kamu cuma perlu tahu di mana jebakannya. Singkatnya:

  • Goodreads API sudah hilang (sejak Desember 2020). Scraping adalah cara utama buat mendapatkan data buku terstruktur dari platform ini.
  • Hasil kosong hampir selalu disebabkan oleh konten yang dirender JS, selector yang sudah usang, header yang kurang lengkap, atau masalah autentikasi pagination — bukan berarti kode kamu pasti salah.
  • JSON-LD adalah sahabat terbaikmu untuk metadata buku. Stabil, terstruktur, dan jarang berubah.
  • Pagination pada banyak halaman shelf dan list butuh autentikasi setelah halaman 5. Sertakan cookie _session_id2.
  • Rate limiting itu nyata. Pakai jeda 3-8 detik dan jangan lebih dari 5.000 request per hari.
  • Strategi dua tahap (kumpulkan URL dulu, baru scrape halaman detail) lebih andal dan lebih gampang dilanjutkan.
  • Buat non-coder (atau siapa pun yang ingin hemat waktu sore hari), menangani semuanya — render JS, pagination, enrichment subpage, dan ekspor — cuma dalam sekitar dua klik.

Scrape dengan bijak, hormati robots.txt, dan semoga data buku kamu selalu balik dengan isi lebih dari [].

FAQ

Apakah Goodreads API masih bisa dipakai?

Tidak. Goodreads menghentikan API publiknya pada Desember 2020 dan tidak lagi mengeluarkan developer key baru. Key lama yang tidak aktif selama 30 hari otomatis dinonaktifkan. Sekarang, web scraping atau alternatif API seperti Open Library dan Google Books adalah opsi yang tersedia untuk mengakses data buku secara programatis.

Kenapa Goodreads scraper saya menghasilkan hasil kosong?

Penyebab paling umum adalah konten yang dirender JavaScript. Goodreads memuat review, distribusi rating, dan banyak bagian detail lewat React/JavaScript, yang tidak bisa dilihat oleh requests.get() biasa. Gunakan Selenium atau Playwright untuk halaman seperti itu. Penyebab lain termasuk selector CSS yang sudah usang (HTML Goodreads berubah), header User-Agent yang hilang (memicu blok 403), atau request yang tidak terautentikasi pada halaman shelf berpaginasi.

Scraping data yang tersedia publik untuk keperluan pribadi atau riset umumnya dianggap boleh menurut preseden hukum saat ini (hiQ v. LinkedIn, Meta v. Bright Data). Namun, Terms of Service Goodreads melarang pengumpulan data otomatis, dan kamu sebaiknya selalu meninjau robots.txt mereka. Hindari redistribusi komersial teks review yang dilindungi hak cipta, dan batasi volume request agar tidak membebani server.

Bagaimana cara scrape beberapa halaman di Goodreads?

Tambahkan ?page=N ke URL shelf atau list, lalu loop melalui nomor halaman. Cek apakah hasil kosong atau tidak ada link "next" untuk mendeteksi halaman terakhir. Penting: beberapa halaman shelf memerlukan autentikasi (cookie _session_id2) agar hasilnya tampil setelah halaman 5 — tanpa itu, kamu akan diam-diam dapat data halaman 1 yang berulang.

Bisa scrape Goodreads tanpa menulis kode?

Bisa. adalah ekstensi Chrome yang memungkinkan kamu scrape Goodreads cuma dengan dua klik — AI menyarankan field data, kamu klik "Scrape", lalu ekspor langsung ke Google Sheets, Excel, Airtable, atau Notion. Tool ini menangani konten yang dirender JavaScript, pagination, dan enrichment subpage secara otomatis, tanpa Python atau coding.

Pelajari Lebih Lanjut

Daftar Isi

Coba Thunderbit

Ambil leads & data lainnya hanya dalam 2 klik. Didukung AI.

Dapatkan Thunderbit Gratis
Ekstrak Data dengan AI
Dengan mudah transfer data ke Google Sheets, Airtable, atau Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week