Poucas coisas são mais frustrantes do que escrever 30 linhas de Python, rodar o seu scraper do Goodreads e ver ele devolver []. Uma lista vazia. Nada. Só você e o cursor piscando.
Já vi isso acontecer dezenas de vezes — nos nossos próprios testes internos na , em fóruns de desenvolvedores e nos issues do GitHub que se acumulam em repositórios de scraping largados às traças. As reclamações quase sempre são as mesmas: "a seção de avaliações está em branco, só aparece []", "não importa o número da página que eu uso, ele sempre raspa a primeira página", "meu código funcionava no ano passado, agora quebrou". E, para piorar, a API do Goodreads foi descontinuada em dezembro de 2020, então aquele conselho de "é só usar a API" que você encontra em tutoriais antigos não leva a lugar nenhum.
Se hoje você quer dados estruturados de livros no Goodreads — títulos, autores, notas, avaliações, gêneros, ISBNs — o caminho principal é fazer scraping. Este guia vai te mostrar uma abordagem completa e funcional para fazer scraping do Goodreads com Python, cobrindo conteúdo renderizado por JS, paginação, prevenção de bloqueios e exportação. E, se Python não for a sua praia, vou te mostrar uma alternativa sem código que resolve tudo em uns dois cliques.
O Que É Scraping do Goodreads e Por Que Fazer Isso com Python?
Scraping do Goodreads significa extrair automaticamente dados de livros — títulos, autores, notas, quantidade de avaliações, gêneros, ISBNs, número de páginas, datas de publicação e muito mais — das páginas do Goodreads usando código, em vez de copiar e colar manualmente.
O Goodreads é uma das maiores bases de dados sobre livros do planeta, com mais de e cerca de . Mais de 18 milhões de livros entram em estantes de "Want to Read" todo mês. Esse tipo de dado estruturado e sempre atualizado é exatamente o que faz editores, cientistas de dados, livreiros e pesquisadores voltarem sempre.
Python é a linguagem preferida para esse tipo de trabalho — ela movimenta cerca de de todos os projetos de scraping. As bibliotecas são maduras (requests, BeautifulSoup, Selenium, Playwright, pandas), a sintaxe é amigável para iniciantes e a comunidade é enorme.
Se você nunca fez scraping de um site antes, Python é um ótimo ponto de partida.
Por Que Fazer Scraping do Goodreads com Python? Casos de Uso Reais
Antes de entrar no código, vale a pena perguntar: quem realmente precisa desses dados e o que faz com eles?
| Caso de uso | Quem se beneficia | O que você extrai |
|---|---|---|
| Pesquisa de mercado para editoras | Editoras, agentes literários | Gêneros em alta, títulos mais bem avaliados, autores em ascensão, notas de concorrentes |
| Sistemas de recomendação de livros | Cientistas de dados, entusiastas, criadores de apps | Notas, gêneros, estantes de usuários, sentimento das avaliações |
| Monitoramento de preço e estoque | Livreiros de e-commerce | Títulos em alta, volume de avaliações, contagem de "Want to Read" |
| Pesquisa acadêmica | Pesquisadores, estudantes | Texto das avaliações, distribuição de notas, classificação por gênero |
| Análise de leitura | Blogueiros de livros, projetos pessoais | Dados de estantes pessoais, histórico de leitura, estatísticas do ano |
Alguns exemplos concretos: o UCSD Book Graph — um dos conjuntos de dados acadêmicos mais citados em pesquisas de recomendação — reúne , tudo coletado de estantes públicas do Goodreads. Vários datasets do Kaggle (goodbooks-10k, Best Books Ever etc.) nasceram de scraping do Goodreads. E um estudo de 2025 publicado em Big Data and Society curou computacionalmente para analisar como avaliações patrocinadas moldam a plataforma.
No lado comercial, a Bright Data vende conjuntos de dados pré-coletados do Goodreads por apenas US$ 0,50 por 1.000 registros — prova de que esses dados têm valor real de mercado.
A API do Goodreads Sumiu — O Que Entrou no Lugar
Se você pesquisou por "Goodreads API" recentemente, provavelmente caiu em um tutorial desatualizado. Em 8 de dezembro de 2020, o Goodreads parou discretamente de emitir novas chaves de API para desenvolvedores. Não houve post no blog nem e-mail em massa — apenas um aviso pequeno na página de documentação e um monte de desenvolvedores confusos.

O impacto foi imediato. Um desenvolvedor, Kyle K, criou um bot no Discord para compartilhar recomendações de livros — "de repente PUF ele simplesmente para de funcionar". Outro, Matthew Jones, perdeu o acesso à API uma semana antes da votação do Reddit r/Fantasy Stabby Awards, e teve que voltar para o Google Forms. Uma estudante de pós-graduação chamada Elena Neacsu viu o projeto da tese de mestrado travar no meio do desenvolvimento.
Então, o que sobrou? O cenário atual é este:
| Abordagem | Dados disponíveis | Facilidade de uso | Limites de requisição | Status |
|---|---|---|---|---|
| Goodreads API | Metadados completos, avaliações | Fácil (era) | 1 req/seg | Descontinuada (dez/2020) — sem novas chaves |
| Open Library API | Títulos, autores, ISBNs, capas (~30M de títulos) | Fácil | 1-3 req/seg | Ativa, gratuita, sem autenticação |
| Google Books API | Metadados, prévias | Fácil | 1.000/dia grátis | Ativa (com lacunas de ISBN em outros idiomas) |
| Scraping com Python (requests + BS4) | Tudo o que está no HTML inicial | Moderado | Gerenciado por você | Funciona para conteúdo estático |
| Scraping com Python (Selenium/Playwright) | Também conteúdo renderizado por JS | Mais difícil | Gerenciado por você | Necessário para avaliações, algumas listas |
| Thunderbit (extensão Chrome sem código) | Qualquer dado visível na página | Muito fácil (2 cliques) | Baseado em créditos | Ativa — sem precisar de Python |
A Open Library é um ótimo complemento, especialmente para buscas por ISBN e metadados básicos. Mas se você precisa de notas, avaliações, tags de gênero ou contagens de "Want to Read", você vai raspar o Goodreads diretamente — com Python ou com uma ferramenta como a Thunderbit, que consegue extrair páginas do Goodreads (inclusive subpáginas de detalhes do livro) com campos sugeridos por IA e exportação direta para Google Sheets, Notion ou Airtable.
Por Que Seu Scraper do Goodreads em Python Retorna Resultados Vazios
Essa é a seção que eu gostaria que existisse quando comecei a trabalhar com dados do Goodreads. O problema de "resultados vazios" é a reclamação mais comum em fóruns de desenvolvedores, e ele tem várias causas diferentes — cada uma com sua própria solução.
| Sintoma | Causa raiz | Correção |
|---|---|---|
Avaliações/notas retornam [] | Conteúdo renderizado por JS (React/lazy-load) | Use Selenium ou Playwright em vez de requests |
| Só raspa sempre a página 1 | Parâmetros de paginação ignorados ou controlados por JS | Passe corretamente o parâmetro ?page=N; use automação de navegador para rolagem infinita |
| O código funcionava no ano passado, agora falha | O Goodreads mudou os nomes das classes HTML | Use seletores mais resilientes (JSON-LD, atributos data-testid) |
| Erros 403/bloqueio depois de poucas requisições | Faltam headers / requisições muito rápidas | Adicione User-Agent, time.sleep(), rotacione proxies |
| Login exigido em páginas de estante/lista | Cookie/sessão necessários | Use requests.Session() com cookies ou scraping via navegador |
Conteúdo Renderizado por JS: Avaliações e Notas Aparecem Vazias
O Goodreads usa uma interface baseada em React. Quando você chama requests.get() em uma página de livro, recebe o HTML inicial — mas avaliações, distribuição de notas e várias seções de "mais informações" carregam de forma assíncrona via JavaScript. Seu scraper literalmente não consegue enxergar isso.
A solução: para qualquer página em que você precise de conteúdo renderizado por JS, mude para Selenium ou Playwright. Para projetos novos, minha recomendação é o Playwright — ele é graças ao protocolo baseado em WebSocket e traz melhor suporte nativo a stealth e async.

Paginação Que Só Retorna a Página 1
Esse é traiçoeiro. Você cria um loop, incrementa ?page=N e continua recebendo os mesmos resultados toda vez. No Goodreads, páginas de estantes devolvem silenciosamente o conteúdo da página 1, independentemente do parâmetro ?page=, se você não estiver autenticado. Sem erro, sem redirecionamento — só a mesma primeira página repetida.
A solução: inclua um cookie de sessão autenticada (especificamente _session_id2). Falo mais sobre isso na seção de paginação abaixo.
Código Que Funcionava no Ano Passado Agora Quebra
O Goodreads altera periodicamente nomes de classes HTML e a estrutura das páginas. O repositório popular maria-antoniak/goodreads-scraper no GitHub agora exibe um aviso permanente: "Este projeto está sem manutenção e não funciona mais." A solução é usar seletores mais robustos — dados estruturados JSON-LD (que seguem o padrão schema.org e quase nunca mudam) ou atributos data-testid em vez de nomes de classe frágeis.
Erros 403 ou Bloqueios
A biblioteca requests do Python tem uma impressão TLS diferente da do Chrome. Mesmo com uma string de User-Agent do Chrome, sistemas de detecção de bots como o AWS WAF (que o Goodreads usa, por ser uma subsidiária da Amazon) conseguem perceber a diferença. A solução: adicione headers de navegador realistas, implemente atrasos de time.sleep() de 3 a 8 segundos entre requisições e considere usar curl_cffi para combinar a impressão TLS se você for fazer scraping em grande escala.
Barreiras de Login em Estantes e Páginas de Lista
Algumas páginas de estantes e listas do Goodreads exigem autenticação para acessar o conteúdo completo, especialmente depois da página 5. Use requests.Session() com cookies exportados do navegador, ou use Selenium/Playwright com um perfil logado. A Thunderbit lida com isso naturalmente, já que roda no seu próprio Chrome autenticado.
Antes de Começar
- Dificuldade: Intermediária (pressupõe noções básicas de Python)
- Tempo necessário: ~20 a 30 minutos para o passo a passo completo
- O que você vai precisar:
- Python 3.8+
- Navegador Chrome (para inspeção com DevTools e Selenium/Playwright)
- Bibliotecas:
requests,beautifulsoup4,seleniumouplaywright,pandas - (Opcional)
gspreadpara exportar para Google Sheets - (Opcional) como alternativa sem código

Passo 1: Configurar Seu Ambiente Python
Instale as bibliotecas necessárias. Abra o terminal e execute:
1pip install requests beautifulsoup4 selenium pandas lxml
Se você preferir Playwright (recomendado para projetos novos):
1pip install playwright
2playwright install chromium
Para exportar para o Google Sheets (opcional):
1pip install gspread oauth2client
Garanta que está usando Python 3.8 ou superior. Você pode verificar com python --version.
Depois da instalação, você deve conseguir importar todas as bibliotecas sem erro. Tente python -c "import requests, bs4, pandas; print('Pronto')" para confirmar.
Passo 2: Enviar Sua Primeira Requisição com Headers Adequados
Abra no navegador uma página de gênero ou lista do Goodreads — por exemplo, https://www.goodreads.com/list/show/1.Best_Books_Ever. Agora vamos buscar essa página com 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}")
Você deve ver Status: 200. Se aparecer 403, revise os headers — o AWS WAF do Goodreads verifica se o User-Agent parece real e rejeita requisições muito básicas. Os headers acima simulam uma sessão real do Chrome.
Passo 3: Inspecionar a Página e Identificar os Seletores Certos
Abra o DevTools do Chrome (F12) na página de lista do Goodreads. Clique com o botão direito em um título de livro e selecione "Inspecionar". Você verá a estrutura DOM de cada item da lista.
Nas páginas de lista, cada livro normalmente fica dentro de um elemento <tr> com itemtype="http://schema.org/Book". Dentro dele, você encontra:
- Título:
a.bookTitle(o texto do link é o título, e ohrefdá a URL do livro) - Autor:
a.authorName - Nota:
span.minirating(contém a nota média e a quantidade de avaliações) - Imagem da capa:
imgdentro da linha do livro
Nas páginas individuais de detalhes do livro, ignore os seletores CSS e vá direto para o JSON-LD. O Goodreads incorpora dados estruturados em uma tag <script type="application/ld+json"> que segue o formato Book do schema.org. Isso é muito mais estável do que nomes de classe, que o Goodreads altera com frequência.
Passo 4: Extrair Dados de Livros de Uma Única Página de Lista
Vamos fazer o parse da página de lista e extrair as informações básicas de cada livro:
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"Encontrados {len(books)} livros na página 1")
28for b in books[:3]:
29 print(b)
Você deve ver cerca de 100 livros por página de lista. Cada item terá título, autor, uma string de nota como "4.28 avg rating — 9,031,257 ratings" e uma URL para a página de detalhes do livro.
Passo 5: Raspar Subpáginas para Obter Informações Detalhadas do Livro
A página da lista dá o básico, mas o ouro de verdade — ISBN, descrição completa, tags de gênero, número de páginas, data de publicação — fica na página individual de cada livro. É aqui que o JSON-LD brilha.
1import json
2import time
3def scrape_book_detail(book_url, headers):
4 """Acessa uma página de livro e extrai metadados detalhados via 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 # Tags de gênero não vêm no JSON-LD; use o HTML como fallback
15 genres = [g.get_text(strip=True) for g in soup.select('span.BookPageMetadataSection__genreButton a span')]
16 return {
17 "isbn": data.get("isbn", ""),
18 "pages": data.get("numberOfPages", ""),
19 "language": data.get("inLanguage", ""),
20 "format": data.get("bookFormat", ""),
21 "avg_rating": agg.get("ratingValue", ""),
22 "rating_count": agg.get("ratingCount", ""),
23 "review_count": agg.get("reviewCount", ""),
24 "description": data.get("description", "")[:200], # corta para prévia
25 "genres": ", ".join(genres[:5]),
26 }
27# Exemplo: enriquecer os 3 primeiros livros
28for book in books[:3]:
29 details = scrape_book_detail(book["book_url"], headers)
30 book.update(details)
31 print(f"Raspado: {book['title']} — ISBN: {book.get('isbn', 'N/A')}")
32 time.sleep(4) # respeite os limites de requisição
Adicione um time.sleep() de 3 a 8 segundos entre requisições. O rate limit do Goodreads começa a aparecer em cerca de 20 a 30 requisições por minuto a partir de um único IP, e você começará a ver 403s ou CAPTCHAs se for mais rápido.
Essa abordagem em duas etapas — primeiro coletar todas as URLs dos livros das páginas de lista, depois visitar cada página de detalhes — é mais confiável e mais fácil de retomar se algo for interrompido. É a estratégia usada pela maioria dos scrapers bem-sucedidos do Goodreads.
Observação: a faz isso automaticamente com scraping de subpáginas. A IA visita cada página de detalhes do livro e enriquece sua tabela com ISBN, descrição, gêneros e muito mais — sem código, sem loops, sem timers de espera.
Passo 6: Lidar com Conteúdo Renderizado por JavaScript com Selenium
Para páginas em que o conteúdo que você precisa é carregado via JavaScript — avaliações, distribuição de notas, seções de "mais detalhes" — você vai precisar de uma ferramenta de automação de navegador. Aqui vai um exemplo com 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# Aguarda as avaliações carregarem
14try:
15 WebDriverWait(driver, 10).until(
16 EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
17 )
18except:
19 print("As avaliações não carregaram — a página pode exigir login ou o JS expirou")
20# Agora faça o parse da página totalmente renderizada
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"Nota: {stars.get_text(strip=True) if stars else 'N/A'}")
28 print(f"Avaliação: {text.get_text(strip=True)[:150] if text else 'N/A'}...")
29 print()
30driver.quit()
Quando usar Selenium vs. requests:
- Use
requests+ BeautifulSoup para metadados de livros (JSON-LD), páginas de lista, páginas de estante (página 1) e dados do Choice Awards - Use Selenium ou Playwright para avaliações, distribuição de notas e qualquer conteúdo que não apareça no HTML bruto
De modo geral, o Playwright é a melhor escolha para projetos novos — mais rápido, menos uso de memória e com padrões de stealth melhores. Mas o Selenium tem uma comunidade maior e mais exemplos prontos especificamente para o Goodreads.
Paginação Que Realmente Funciona: Raspar Listas Inteiras do Goodreads
A paginação é o ponto de falha mais comum em scrapers do Goodreads, e eu ainda não vi nenhum tutorial concorrente tratar isso do jeito certo. É assim que você acerta.
Como Funcionam as URLs de Paginação do Goodreads
O Goodreads usa um parâmetro simples ?page=N na maioria das páginas paginadas:
- Listas:
https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2 - Estantes:
https://www.goodreads.com/shelf/show/thriller?page=2 - Busca:
https://www.goodreads.com/search?q=fantasy&page=2
Cada página de lista normalmente mostra 100 livros. Estantes mostram 50 por página.
Como Escrever um Loop de Paginação Que Sabe Quando Parar
1import time
2all_books = []
3base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
4for page_num in range(1, 50): # limite de segurança em 50 páginas
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"Página {page_num}: status {resp.status_code}, encerrando.")
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"Página {page_num}: nenhum livro encontrado, fim da lista.")
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"Página {page_num}: raspados {len(rows)} livros (total: {len(all_books)})")
23 time.sleep(5) # pausa de 5 segundos entre páginas
24print(f"\nPronto. Total de livros coletados: {len(all_books)}")
Você detecta a última página verificando se a lista de resultados está vazia (nenhum elemento tr[itemtype="http://schema.org/Book"] encontrado) ou pela ausência do link "next" (a.next_page).
Caso Especial: Login Necessário Depois da Página 5
Esse é o truque que pega quase todo mundo: algumas páginas de estantes e listas do Goodreads retornam silenciosamente o conteúdo da página 1 quando você solicita a página 6+ sem autenticação. Sem erro, sem redirecionamento — só os mesmos dados repetidos.
Para corrigir isso, exporte o cookie _session_id2 do navegador (use uma extensão de exportação de cookies ou o Chrome DevTools > Application > Cookies) e inclua-o nas requisições:
1session = requests.Session()
2session.headers.update(headers)
3session.cookies.set("_session_id2", "YOUR_SESSION_COOKIE_VALUE_HERE", domain=".goodreads.com")
4# Agora use session.get() em vez de requests.get()
5resp = session.get(f"{base_url}?page=6", timeout=15)
A Thunderbit lida nativamente com paginação por clique e rolagem infinita, sem código e sem gerenciamento de cookies. Se sua lógica de paginação continua quebrando, vale considerar.
O Script Python Completo, Pronto Para Copiar e Colar
Aqui está o script completo e consolidado. Ele lida com headers, paginação, scraping de subpáginas via JSON-LD, rate limiting e exportação para CSV. Eu testei isso em páginas reais do Goodreads em meados de 2025.
1"""
2goodreads_scraper.py — Raspa uma lista do Goodreads com paginação e enriquecimento das páginas de detalhes.
3Uso: python goodreads_scraper.py
4Saída: 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 # ajuste conforme necessário
16DELAY_LISTING = 5 # segundos entre páginas de lista
17DELAY_DETAIL = 4 # segundos entre páginas de detalhes
18OUTPUT_FILE = "goodreads_books.csv"
19def scrape_listing_page(url):
20 """Retorna uma lista de dicts com title, author, book_url de uma página de lista."""
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 """Acessa uma página de livro e extrai metadados via JSON-LD + fallback em 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 # --- Passo 1: coletar URLs dos livros nas páginas de lista ---
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"Página {page}: vazia — encerrando paginação.")
70 break
71 all_books.extend(page_books)
72 print(f"Página {page}: {len(page_books)} livros (total: {len(all_books)})")
73 time.sleep(DELAY_LISTING)
74 # --- Passo 2: enriquecer cada livro com dados da página de detalhes ---
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 # --- Exportar para 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"\nSalvos {len(all_books)} livros em {OUTPUT_FILE}")
88 else:
89 print("Nenhum livro foi raspado.")
90if __name__ == "__main__":
91 main()
Com MAX_PAGES = 3, esse script coleta cerca de 300 livros da lista "Best Books Ever", visita a página de detalhes de cada livro e grava tudo em um CSV. No meu computador, isso leva aproximadamente 25 minutos (principalmente por causa dos atrasos de 4 segundos entre as requisições das páginas de detalhes). Seu CSV de saída terá colunas como title, author, book_url, isbn, pages, avg_rating, rating_count, review_count, description, genres, language, format e published.
Exportando Além do CSV: Google Sheets com gspread
Se você quiser os dados no Google Sheets em vez de, ou além de, um CSV, adicione isso depois da exportação:
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("Dados enviados para o Google Sheets.")
Você vai precisar de uma service account do Google Cloud com as APIs do Sheets e Drive ativadas. A explica a configuração em cerca de 5 minutos. Use operações em lote (append_rows() com uma lista de listas) se estiver enviando mais de algumas centenas de linhas — o limite de taxa do Google é de 300 requisições por 60 segundos por projeto.
Claro, se toda essa configuração parecer exagerada, a Thunderbit exporta para Google Sheets, Airtable, Notion, Excel, CSV e JSON com — sem configurar bibliotecas, sem arquivo de credenciais, sem limites de API.
A Alternativa Sem Código: Raspe o Goodreads com a Thunderbit
Nem todo mundo quer manter um script em Python. Talvez você seja um editor fazendo uma análise pontual de mercado, ou um blogueiro de livros que só quer uma planilha dos best-sellers deste ano. Foi exatamente para esse cenário que criamos a Thunderbit.
Como Raspar o Goodreads com a Thunderbit
- Instale a extensão Thunderbit para Chrome na e abra uma página de lista, estante ou resultados de busca do Goodreads.
- Clique em "AI Suggest Fields" na barra lateral da Thunderbit. A IA lê a página e sugere colunas — normalmente título, autor, nota, URL da imagem da capa e link do livro.
- Clique em "Scrape" — os dados são extraídos para uma tabela estruturada em segundos.
- Exporte para Google Sheets, Excel, Airtable, Notion, CSV ou JSON.
Para dados detalhados do livro (ISBN, descrição, gêneros, número de páginas), o recurso de scraping de subpáginas da Thunderbit visita cada página de detalhes e enriquece a tabela automaticamente — sem loops, sem timers de espera e sem depuração.
A Thunderbit também lida nativamente com listas paginadas. Você diz para clicar em "Next" ou rolar, e ela coleta os dados de todas as páginas sem exigir código.
A troca é simples: o script em Python te dá controle total e é gratuito (tirando o seu tempo), enquanto a Thunderbit troca parte dessa flexibilidade por uma enorme economia de tempo e zero manutenção. Para uma lista de 300 livros, o script em Python leva cerca de 25 minutos de execução, além do tempo que você gastou escrevendo e corrigindo. A Thunderbit entrega os mesmos dados em cerca de 3 minutos, com dois cliques.
Scraping do Goodreads com Responsabilidade: robots.txt, Termos de Serviço e Ética
Isso merece uma resposta direta, não um parágrafo qualquer de aviso.
O Que o robots.txt do Goodreads Realmente Diz
O robots.txt ativo do Goodreads é surpreendentemente específico. As páginas de detalhes de livros (/book/show/), listas públicas (/list/show/), estantes públicas (/shelf/show/) e páginas de autores (/author/show/) não estão bloqueadas. O que está bloqueado: /api, /book/reviews/, /review/list, /review/show, /search e vários outros caminhos. GPTBot e CCBot (Common Crawl) estão totalmente bloqueados com Disallow: /. Há uma diretiva Crawl-delay: 5 para o bingbot, mas não há atraso global.
Termos de Serviço do Goodreads em Linguagem Simples
Os Termos de Serviço (revisados pela última vez em 28 de abril de 2021) proíbem "qualquer uso de data mining, robots ou ferramentas semelhantes de coleta e extração de dados". É uma linguagem ampla, e vale a pena levá-la a sério — mas os tribunais têm entendido de forma consistente que violar ToS, por si só, não equivale a "acesso não autorizado" criminal. A decisão afirmou que "criminalizar violações de termos de serviço corre o risco de transformar cada site em sua própria jurisdição criminal".
Boas Práticas
- Espere entre requisições: 3 a 8 segundos entre cada uma (o próprio robots.txt do Goodreads sugere 5 segundos para bots)
- Mantenha-se abaixo de 5.000 requisições por dia a partir de um único IP
- Raspe apenas páginas publicamente acessíveis — evite coletar em massa dados que exigem login
- Não redistribua comercialmente o texto bruto das avaliações — avaliações são obras criativas protegidas por direitos autorais
- Armazene só o que você precisa e defina uma política de retenção de dados
- Uso pessoal/pesquisa vs. comercial: raspar dados públicos para análise pessoal ou pesquisa acadêmica costuma ser aceito. O risco legal aumenta quando há redistribuição comercial.
Usar uma ferramenta como a Thunderbit (que faz o scraping pela sua própria sessão no navegador) mantém a interação visualmente idêntica à navegação normal, mas os mesmos princípios éticos continuam valendo, independentemente da ferramenta. Se quiser se aprofundar nas , já cobrimos esse assunto separadamente.
Dicas e Armadilhas Comuns
Dica: Comece sempre pelo JSON-LD. Antes de escrever seletores CSS complexos, verifique se os dados que você precisa estão na tag <script type="application/ld+json">. Ela é mais estável, mais fácil de analisar e menos propensa a quebrar quando o Goodreads atualiza o frontend.
Dica: Use a estratégia em duas etapas. Primeiro, colete todas as URLs dos livros nas páginas de lista; depois, visite cada página de detalhes. Isso torna seu scraper mais fácil de retomar se travar no meio, e você ainda pode salvar a lista de URLs em disco como ponto de retomada.
Armadilha: Esquecer de lidar com campos ausentes. Nem toda página de livro tem ISBN, tags de gênero ou descrição. Use sempre .get() com valor padrão ou coloque os seletores dentro de verificações if. Um único erro de NoneType pode derrubar uma execução de scraping de 3 horas.
Armadilha: Rodar rápido demais. Eu sei que dá vontade de colocar time.sleep(0.5) e sair disparando. Mas o Goodreads começa a devolver 403 depois de cerca de 20 a 30 requisições rápidas, e, quando você é sinalizado, talvez precise esperar horas ou trocar de IP. Um atraso de 4 a 5 segundos costuma ser o ponto ideal.
Armadilha: Confiar em tutoriais antigos. Se um guia menciona a API do Goodreads ou usa nomes de classe como .field.value ou #bookTitle, provavelmente está desatualizado. Sempre valide os seletores na página ao vivo antes de construir o scraper.
Para mais dicas sobre como escolher as ferramentas e frameworks certos, confira nossos guias sobre e .
Conclusão e Principais Aprendizados
Fazer scraping do Goodreads com Python é totalmente possível — você só precisa saber onde estão as armadilhas. A versão curta:
- A API do Goodreads acabou (desde dezembro de 2020). Scraping é a principal forma de obter dados estruturados de livros na plataforma.
- Resultados vazios quase sempre são causados por conteúdo renderizado por JS, seletores desatualizados, headers ausentes ou problemas de autenticação na paginação — não porque seu código está "errado".
- JSON-LD é seu melhor amigo para metadados de livros. Ele é estável, estruturado e raramente muda.
- A paginação exige autenticação em muitas páginas de estantes e listas depois da página 5. Inclua o cookie
_session_id2. - Rate limiting é real. Use atrasos de 3 a 8 segundos e fique abaixo de 5.000 requisições por dia.
- A estratégia em duas etapas (coletar URLs primeiro, depois raspar páginas de detalhes) é mais confiável e mais fácil de retomar.
- Para quem não programa (ou para quem valoriza a própria tarde), a resolve tudo isso — renderização JS, paginação, enriquecimento de subpáginas e exportação — em cerca de dois cliques.
Raspe com responsabilidade, respeite o robots.txt e que seus dados de livros nunca mais voltem como [].
FAQs
Ainda dá para usar a API do Goodreads?
Não. O Goodreads descontinuou sua API pública em dezembro de 2020 e não emite mais novas chaves de desenvolvedor. Chaves existentes que ficaram inativas por 30 dias foram desativadas automaticamente. Hoje, as opções para acessar dados de livros de forma programática são web scraping ou APIs alternativas (como Open Library ou Google Books).
Por que meu scraper do Goodreads retorna resultados vazios?
A causa mais comum é conteúdo renderizado por JavaScript. O Goodreads carrega avaliações, distribuições de nota e várias seções de detalhes via React/JavaScript, algo que uma chamada simples requests.get() não enxerga. Use Selenium ou Playwright nessas páginas. Outras causas incluem seletores CSS desatualizados (o Goodreads mudou o HTML), falta de headers User-Agent (gerando bloqueio 403) ou requisições sem autenticação em páginas paginadas de estantes.
É legal raspar o Goodreads?
Raspar dados publicamente disponíveis para uso pessoal ou de pesquisa é geralmente aceito sob precedentes jurídicos atuais (hiQ v. LinkedIn, Meta v. Bright Data). Porém, os Termos de Serviço do Goodreads proíbem coleta automatizada de dados, e você deve sempre revisar o robots.txt. Evite redistribuir comercialmente o texto das avaliações, que é protegido por direitos autorais, e limite o volume de requisições para não sobrecarregar os recursos do site.
Como faço scraping de várias páginas no Goodreads?
Adicione ?page=N à URL da estante ou lista e percorra os números das páginas em loop. Verifique se os resultados estão vazios ou se o link "next" desapareceu para identificar a última página. Importante: algumas páginas de estante exigem autenticação (o cookie _session_id2) para retornar resultados além da página 5 — sem ele, você receberá silenciosamente os dados da página 1 repetidos.
Posso raspar o Goodreads sem escrever código?
Sim. A é uma extensão do Chrome que permite raspar o Goodreads em dois cliques — a IA sugere os campos de dados, você clica em "Scrape" e exporta diretamente para Google Sheets, Excel, Airtable ou Notion. Ela lida automaticamente com conteúdo renderizado por JavaScript, paginação e enriquecimento de subpáginas, sem precisar de Python ou programação.
Saiba mais