Нет ничего более обидного, чем написать 30 строк Python, запустить скрипт для Goodreads и увидеть в ответ []. Пустой список. Ничего. Только вы и мигающий курсор.
Я видел это десятки раз — в наших внутренних экспериментах в , на форумах разработчиков и в GitHub-репозиториях заброшенных скраперов, где копятся одинаковые жалобы: «блок с топ-отзывами пустой, просто показывает мне []», «какой бы номер страницы я ни указывал, он всё равно собирает только первую», «мой код работал в прошлом году, а теперь сломался». И, что ещё хуже, API Goodreads был выведен из эксплуатации в декабре 2020 года, так что совет «просто воспользуйтесь API», который до сих пор встречается в старых туториалах, уже никуда не ведёт.
Если вам сегодня нужны структурированные данные о книгах с Goodreads — названия, авторы, рейтинги, отзывы, жанры, ISBN — основной путь один: парсинг. В этом руководстве я покажу рабочий, полный способ собрать данные с Goodreads с помощью Python: разберём JS-рендеринг, пагинацию, защиту от блокировок и экспорт. А если Python — не ваш вариант, я покажу no-code альтернативу, которая справляется примерно в два клика.
Что такое парсинг Goodreads и зачем делать это на Python?
Парсинг Goodreads — это автоматическое извлечение данных о книгах: названий, авторов, рейтингов, количества отзывов, жанров, ISBN, числа страниц, даты публикации и многого другого — прямо со страниц Goodreads, без ручного копирования.
Goodreads — одна из крупнейших книжных баз данных в мире: более и примерно . Каждый месяц более 18 миллионов книг попадают на полки «Want to Read». Именно такие постоянно обновляемые структурированные данные и нужны издателям, аналитикам, продавцам книг и исследователям.
Python — язык по умолчанию для таких задач: на него приходится около всех проектов по сбору данных. Библиотеки зрелые и удобные для новичков (requests, BeautifulSoup, Selenium, Playwright, pandas), синтаксис понятный, а сообщество огромное.
Если вы ещё ни разу не парсили сайты, начинать лучше всего именно с Python.
Зачем собирать данные с Goodreads на Python? Практические сценарии
Прежде чем переходить к коду, стоит понять: кому вообще нужны эти данные и что с ними потом делают?
| Сценарий | Кому это полезно | Что собирают |
|---|---|---|
| Исследование книжного рынка для издателей | Издатели, литературные агенты | Популярные жанры, книги с высоким рейтингом, новые авторы, рейтинги конкурентов |
| Системы рекомендаций книг | Аналитики, энтузиасты, разработчики приложений | Рейтинги, жанры, личные полки пользователей, тональность отзывов |
| Мониторинг цен и ассортимента | Интернет-продавцы книг | Популярные книги, объёмы отзывов, количество добавлений в «Want to Read» |
| Академические исследования | Исследователи, студенты | Тексты отзывов, распределение рейтингов, классификация жанров |
| Аналитика чтения | Книжные блогеры, личные проекты | Данные личной полки, история чтения, статистика за год |
Несколько конкретных примеров: UCSD Book Graph — один из самых цитируемых академических наборов данных для исследований рекомендательных систем — содержит , собранных из общедоступных полок Goodreads. Несколько наборов на Kaggle (goodbooks-10k, Best Books Ever и другие) тоже выросли из парсинга Goodreads. А исследование 2025 года в Big Data and Society вручную и с помощью вычислительных методов собрало , чтобы изучить влияние спонсируемых рецензий на платформу.
С коммерческой точки зрения Bright Data продаёт уже собранные датасеты Goodreads по цене от $0,50 за 1 000 записей — это само по себе доказывает, что данные имеют реальную рыночную ценность.
API Goodreads больше нет — вот что пришло ему на замену
Если вы недавно искали «Goodreads API», скорее всего, наткнулись на устаревшее руководство. 8 декабря 2020 года Goodreads тихо перестал выдавать новые ключи API для разработчиков. Никакого поста в блоге, никаких рассылок — только небольшой баннер на странице документации и множество озадаченных разработчиков.

Последствия не заставили себя ждать. Один разработчик, Kyle K, сделал Discord-бота для обмена книжными рекомендациями — и «вдруг ПУФ, он просто перестал работать». Другой, Matthew Jones, потерял доступ к API за неделю до голосования Reddit r/Fantasy Stabby Awards и был вынужден вернуться к Google Forms. У магистрантки Elena Neacsu проект по диссертации начал разваливаться прямо в середине разработки.
Что же осталось? Картина сейчас такая:
| Подход | Какие данные доступны | Насколько просто использовать | Ограничения по запросам | Статус |
|---|---|---|---|---|
| Goodreads API | Полные метаданные, отзывы | Раньше было просто | 1 запрос/сек | Устарел (декабрь 2020) — новых ключей нет |
| Open Library API | Названия, авторы, ISBN, обложки (~30 млн книг) | Просто | 1–3 запроса/сек | Активен, бесплатный, без авторизации |
| Google Books API | Метаданные, превью | Просто | 1 000 в день бесплатно | Активен (есть пробелы по неанглоязычным ISBN) |
| Парсинг на Python (requests + BS4) | Всё, что есть в исходном HTML | Средняя сложность | Управляется вами | Работает для статического контента |
| Парсинг на Python (Selenium/Playwright) | В том числе JS-рендеренный контент | Сложнее | Управляется вами | Нужен для отзывов и части списков |
| Thunderbit (no-code расширение для Chrome) | Любые видимые данные на странице | Очень просто (2 клика) | По кредитам | Активен — Python не нужен |
Open Library — отличный дополнительный источник, особенно для поиска ISBN и базовых метаданных. Но если вам нужны рейтинги, отзывы, теги жанров или количество добавлений в «Want to Read», придётся обращаться прямо к Goodreads — либо через Python, либо через инструмент вроде Thunderbit, который умеет собирать страницы Goodreads (включая подстраницы с деталями книги) с помощью AI-подсказок по полям и сразу экспортировать данные в Google Sheets, Notion или Airtable.
Почему Python-скрапер Goodreads выдаёт пустые результаты — и как это исправить
Это тот раздел, который мне самому хотелось бы увидеть в самом начале работы с данными Goodreads. Проблема «пустых результатов» — самая частая жалоба на форумах разработчиков, и у неё есть несколько разных причин, каждая со своим решением.
| Симптом | Причина | Решение |
|---|---|---|
Отзывы/рейтинги возвращаются как [] | Контент рендерится через JS (React/lazy-load) | Используйте Selenium или Playwright вместо requests |
| Всегда собирается только первая страница | Параметры пагинации игнорируются или страница управляется JS | Передавайте ?page=N корректно; для бесконечной прокрутки используйте автоматизацию браузера |
| Код работал в прошлом году, теперь падает | Goodreads поменял HTML-классы | Используйте более устойчивые селекторы (JSON-LD, атрибуты data-testid) |
| Ошибка 403 / блокировка после нескольких запросов | Нет заголовков / запросы слишком быстрые | Добавьте User-Agent, time.sleep(), ротируйте прокси |
| Страница полки/списка просит войти | Нужны cookie/сессия | Используйте requests.Session() с cookie или парсинг через браузер |
JS-рендеренный контент: отзывы и рейтинги выглядят пустыми
На Goodreads используется frontend на React. Когда вы вызываете requests.get() для страницы книги, вы получаете только начальный HTML — а отзывы, распределение оценок и многие блоки «дополнительной информации» подгружаются асинхронно через JavaScript. Ваш скрипт буквально их не видит.
Решение: если вам нужен JS-рендеренный контент, переходите на Selenium или Playwright. Для новых проектов я рекомендую Playwright — он благодаря WebSocket-протоколу и лучше подходит для stealth-режима и async-сценариев.

Пагинация, которая всегда возвращает только первую страницу
Вот коварная проблема. Вы пишете цикл, увеличиваете ?page=N, а в ответ всё равно получаете одни и те же результаты. На Goodreads страницы полок могут молча возвращать содержимое первой страницы, даже если вы меняете ?page=, если вы не авторизованы. Без ошибок, без редиректа — просто снова и снова та же первая страница.
Решение: передавайте cookie авторизованной сессии, а именно _session_id2. Подробно об этом ниже, в разделе про пагинацию.
Код, который работал в прошлом году, теперь ломается
Goodreads периодически меняет HTML-классы и структуру страниц. В популярном GitHub-репозитории maria-antoniak/goodreads-scraper теперь висит постоянное предупреждение: «This project is unmaintained and no longer functioning.» Решение — использовать более устойчивые селекторы: структурированные данные JSON-LD (по стандарту schema.org, которые меняются редко) или атрибуты data-testid вместо нестабильных CSS-классов.
Ошибки 403 и блокировки
У библиотеки Python requests TLS-фингерпринт отличается от Chrome. Даже если подставить User-Agent браузера Chrome, системы антибот-защиты вроде AWS WAF (а Goodreads использует AWS, поскольку принадлежит Amazon) могут заметить несоответствие. Решение: добавьте реалистичные browser headers, делайте задержки time.sleep() по 3–8 секунд между запросами и для больших объёмов рассмотрите curl_cffi, если важно совпадение TLS-фингерпринта.
Страницы полок и списков требуют логина
Некоторые страницы полок и списков Goodreads требуют авторизации, особенно если вы заходите дальше 5-й страницы. Используйте requests.Session() с cookie, экспортированными из браузера, или Selenium/Playwright с уже авторизованным профилем. Thunderbit обрабатывает это естественно, потому что работает прямо в вашем обычном авторизованном Chrome.
Прежде чем начать
- Сложность: средняя (предполагается базовое знание Python)
- Время: около 20–30 минут на полное прохождение
- Что понадобится:
- Python 3.8+
- Браузер Chrome (для проверки через DevTools и для Selenium/Playwright)
- Библиотеки:
requests,beautifulsoup4,seleniumилиplaywright,pandas - (Опционально)
gspreadдля экспорта в Google Sheets - (Опционально) для no-code альтернативы

Шаг 1: Настройте Python-окружение
Установите нужные библиотеки. Откройте терминал и выполните:
1pip install requests beautifulsoup4 selenium pandas lxml
Если вы предпочитаете Playwright (рекомендуется для новых проектов):
1pip install playwright
2playwright install chromium
Для экспорта в Google Sheets (по желанию):
1pip install gspread oauth2client
Убедитесь, что у вас Python 3.8 или выше. Проверить это можно командой python --version.
После установки все библиотеки должны импортироваться без ошибок. Для проверки выполните python -c "import requests, bs4, pandas; print('Ready')".
Шаг 2: Отправьте первый запрос с правильными заголовками
Откройте в браузере страницу жанра или списка Goodreads — например, https://www.goodreads.com/list/show/1.Best_Books_Ever. Теперь попробуем получить эту страницу через 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}")
Вы должны увидеть Status: 200. Если приходит 403, перепроверьте заголовки — AWS WAF у Goodreads проверяет реалистичный User-Agent и может отклонять слишком «пустые» запросы. Заголовки выше имитируют обычную сессию Chrome.
Шаг 3: Посмотрите структуру страницы и выберите правильные селекторы
Откройте Chrome DevTools (F12) на странице списка Goodreads. Кликните правой кнопкой по названию книги и выберите «Inspect». Вы увидите DOM-структуру каждого элемента книги.
Для страниц списков каждая книга обычно обёрнута в элемент <tr> с itemtype="http://schema.org/Book". Внутри обычно находятся:
- Название:
a.bookTitle(текст ссылки — это название,hrefдаёт URL книги) - Автор:
a.authorName - Рейтинг:
span.minirating(там есть средний рейтинг и число оценок) - Обложка:
imgвнутри строки книги
Для страницы отдельной книги лучше не цепляться за CSS-селекторы, а сразу использовать JSON-LD. Goodreads встраивает структурированные данные в тег <script type="application/ld+json">, где формат соответствует schema.org Book. Это гораздо стабильнее, чем CSS-классы, которые Goodreads может менять в любой момент.
Шаг 4: Извлеките данные о книгах с одной страницы списка
Давайте распарсим страницу списка и соберём базовую информацию по каждой книге:
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)
Обычно на одной странице списка будет около 100 книг. У каждой записи будет название, автор, строка с рейтингом вроде "4.28 avg rating — 9,031,257 ratings" и ссылка на страницу книги.
Шаг 5: Соберите данные с подстраниц с подробной информацией о книге
Страница списка даёт только базу, а настоящие ценные данные — ISBN, полное описание, жанры, число страниц, дату публикации — находятся на индивидуальной странице книги. Здесь и помогает JSON-LD.
1import json
2import time
3def scrape_book_detail(book_url, headers):
4 """Открывает страницу книги и извлекает подробные метаданные через 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 # Теги жанров не входят в JSON-LD; берём их из 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], # обрезаем для превью
25 "genres": ", ".join(genres[:5]),
26 }
27# Пример: обогатим первые 3 книги
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) # соблюдаем лимиты
Добавляйте time.sleep() на 3–8 секунд между запросами. Goodreads начинает ограничивать частоту примерно после 20–30 запросов в минуту с одного IP, и если идти быстрее, быстро появятся 403 или CAPTCHA.
Такой двухпроходный подход — сначала собрать все URL книг со страниц списка, а потом переходить на страницы деталей — надёжнее и проще возобновляется после сбоя. Именно так работают многие успешные скраперы Goodreads.
Кстати: умеет делать это автоматически через subpage scraping. AI открывает каждую страницу книги и дополняет вашу таблицу ISBN, описанием, жанрами и другими полями — без кода, без циклов и без sleep.
Шаг 6: Обрабатывайте JS-рендеренный контент через Selenium
Если нужный вам контент подгружается через JavaScript — например, отзывы, распределение оценок, блоки «more details» — понадобится инструмент автоматизации браузера. Вот пример на 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# Ждём загрузки отзывов
14try:
15 WebDriverWait(driver, 10).until(
16 EC.presence_of_element_located((By.CSS_SELECTOR, "article.ReviewCard"))
17 )
18except:
19 print("Отзывы не загрузились — возможно, нужна авторизация или истёк таймаут JS")
20# Теперь парсим полностью отрендеренную страницу
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()
Когда использовать Selenium, а когда requests:
requests+ BeautifulSoup — для метаданных книги (JSON-LD), страниц списков, страниц полок (первая страница) и данных Choice Awards- Selenium или Playwright — для отзывов, распределения рейтингов и любого контента, которого нет в исходном HTML
Вообще для новых проектов Playwright обычно лучше: быстрее, экономнее по памяти, с более удачными настройками stealth. Но у Selenium больше сообщество и больше готовых примеров именно для Goodreads.
Пагинация, которая действительно работает: как собрать полный список Goodreads
Пагинация — самое частое место, где скраперы Goodreads ломаются, и я ещё не видел ни одного конкурирующего туториала, который объяснял бы это как следует. Вот как сделать правильно.
Как работают URL пагинации на Goodreads
На Goodreads для большинства страниц используется обычный параметр ?page=N:
- Списки:
https://www.goodreads.com/list/show/1.Best_Books_Ever?page=2 - Полки:
https://www.goodreads.com/shelf/show/thriller?page=2 - Поиск:
https://www.goodreads.com/search?q=fantasy&page=2
Обычно одна страница списка показывает 100 книг. Полки — по 50 на страницу.
Как написать цикл пагинации, который понимает, когда остановиться
1import time
2all_books = []
3base_url = "https://www.goodreads.com/list/show/1.Best_Books_Ever"
4for page_num in range(1, 50): # запасной предел: 50 страниц
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_num}: получен статус {resp.status_code}, останавливаемся.")
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_num}: книг не найдено, дошли до конца.")
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_num}: собрано {len(rows)} книг (всего: {len(all_books)})")
23 time.sleep(5) # пауза 5 секунд между страницами
24print(f"\nГотово. Всего собрано книг: {len(all_books)}")
Последнюю страницу можно определить по пустому списку результатов (нет элементов tr[itemtype="http://schema.org/Book"]) или по отсутствию ссылки «next» (a.next_page).
Крайний случай: после 5-й страницы нужен логин
Вот ловушка, в которую попадает почти каждый: некоторые страницы полок и списков Goodreads после 6-й страницы молча возвращают содержимое первой страницы, если вы не авторизованы. Без ошибки, без редиректа — просто повтор данных.
Чтобы это исправить, экспортируйте cookie _session_id2 из браузера (через расширение для экспорта cookie или Chrome DevTools > Application > Cookies) и передайте её в запросы:
1session = requests.Session()
2session.headers.update(headers)
3session.cookies.set("_session_id2", "YOUR_SESSION_COOKIE_VALUE_HERE", domain=".goodreads.com")
4# Теперь используйте session.get() вместо requests.get()
5resp = session.get(f"{base_url}?page=6", timeout=15)
Thunderbit умеет работать и с пагинацией по клику, и с infinite scroll без кода и без ручного управления cookie. Если ваша логика пагинации постоянно ломается, это стоит попробовать.
Полный Python-скрипт, который можно копировать и запускать
Ниже — полный собранный скрипт. Он учитывает заголовки, пагинацию, сбор данных с подстраниц через JSON-LD, ограничение скорости запросов и экспорт в CSV. Я тестировал его на живых страницах Goodreads в середине 2025 года.
1"""
2goodreads_scraper.py — сбор данных со списка Goodreads с пагинацией и обогащением страниц книг.
3Запуск: python goodreads_scraper.py
4Вывод: 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 # при необходимости измените
16DELAY_LISTING = 5 # пауза между страницами списка
17DELAY_DETAIL = 4 # пауза между страницами книги
18OUTPUT_FILE = "goodreads_books.csv"
19def scrape_listing_page(url):
20 """Возвращает список словарей с title, author, 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 """Открывает страницу книги и извлекает метаданные через JSON-LD + HTML fallback."""
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 # --- Проход 1: собираем URL книг со страниц списка ---
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}: пусто — останавливаем пагинацию.")
70 break
71 all_books.extend(page_books)
72 print(f"Страница {page}: {len(page_books)} книг (всего: {len(all_books)})")
73 time.sleep(DELAY_LISTING)
74 # --- Проход 2: обогащаем каждую книгу данными со страницы деталей ---
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 # --- Экспорт в 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"\nСохранено {len(all_books)} книг в {OUTPUT_FILE}")
88 else:
89 print("Книги не собраны.")
90if __name__ == "__main__":
91 main()
При MAX_PAGES = 3 этот скрипт собирает около 300 книг со списка «Best Books Ever», затем переходит на страницу каждой книги и записывает всё в CSV. На моём компьютере это занимает примерно 25 минут, в основном из-за 4-секундных пауз между запросами к страницам деталей. В итоговом CSV будут столбцы вроде title, author, book_url, isbn, pages, avg_rating, rating_count, review_count, description, genres, language, format и published.
Экспорт не только в CSV: Google Sheets через gspread
Если хотите загрузить данные в Google Sheets вместо CSV или дополнительно к нему, добавьте после экспорта в 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("Данные отправлены в Google Sheets.")
Вам понадобится service account Google Cloud с включёнными Sheets и Drive API. В настройка описана примерно за 5 минут. Если вы отправляете больше нескольких сотен строк, используйте batch-операции (append_rows() со списком списков) — у Google лимит 300 запросов на 60 секунд на проект.
Конечно, если всё это кажется слишком громоздким, Thunderbit экспортирует данные в Google Sheets, Airtable, Notion, Excel, CSV и JSON — без установки библиотек, без файла credentials и без лимитов API.
No-code альтернатива: соберите данные Goodreads через Thunderbit
Не всем хочется поддерживать Python-скрипт. Может быть, вы издатель и вам нужен разовый анализ рынка, или книжный блогер, которому просто нужна таблица с бестселлерами этого года. Именно для таких задач мы и делали Thunderbit.
Как собрать данные Goodreads через Thunderbit
- Установите расширение Thunderbit для Chrome из и откройте страницу списка, полки или поиска на Goodreads.
- Нажмите «AI Suggest Fields» в боковой панели Thunderbit. AI прочитает страницу и предложит столбцы — обычно название, автор, рейтинг, URL обложки и ссылку на книгу.
- Нажмите «Scrape» — данные за несколько секунд соберутся в структурированную таблицу.
- Экспортируйте в Google Sheets, Excel, Airtable, Notion, CSV или JSON.
Для подробных данных о книге (ISBN, описание, жанры, число страниц) функция subpage scraping в Thunderbit автоматически открывает каждую страницу книги и дополняет таблицу — без циклов, без таймеров, без отладки.
Thunderbit также умеет работать с пагинацией нативно. Вы просто указываете, что нужно нажимать «Next» или прокручивать страницу, и он собирает данные со всех страниц без единой строки кода.
Компромисс простой: Python-скрипт даёт полный контроль и бесплатен, если не считать вашего времени, а Thunderbit жертвует частью гибкости ради огромной экономии времени и отсутствия поддержки. Для списка на 300 книг Python-скрипт будет работать примерно 25 минут, плюс ещё время на написание и отладку. Thunderbit собирает те же данные примерно за 3 минуты и два клика.
Ответственный парсинг Goodreads: robots.txt, Terms of Service и этика
Здесь нужен прямой ответ, а не формальный абзац-дисклеймер.
Что на самом деле говорит robots.txt Goodreads
Живой robots.txt у Goodreads удивительно конкретный. Страницы книг (/book/show/), публичные списки (/list/show/), публичные полки (/shelf/show/) и страницы авторов (/author/show/) не запрещены. Заблокированы /api, /book/reviews/, /review/list, /review/show, /search и некоторые другие пути. GPTBot и CCBot (Common Crawl) полностью заблокированы через Disallow: /. Для bingbot задано Crawl-delay: 5, но глобальной задержки нет.
Terms of Service Goodreads простыми словами
В Terms of Service (последнее обновление — 28 апреля 2021 года) прямо сказано, что запрещается «любое использование data mining, robots или аналогичных инструментов сбора и извлечения данных». Формулировка широкая, и к ней стоит отнестись серьёзно — но суды обычно считают, что одно лишь нарушение ToS не равно уголовному «несанкционированному доступу». Решение по прямо указывало, что «криминализация нарушений условий использования рискует превратить каждый сайт в собственную уголовную юрисдикцию».
Лучшие практики
- Делайте задержки между запросами: 3–8 секунд (в robots.txt Goodreads для ботов рекомендовано 5 секунд)
- Не превышайте 5 000 запросов в день с одного IP
- Собирайте только общедоступные страницы — не парсите массово данные, доступные только после входа
- Не перепродавайте текст отзывов без обработки — отзывы являются объектами авторского права
- Храните только нужные данные и задайте срок их хранения
- Личное исследование vs. коммерческое использование: парсинг публичных данных для личного анализа или академических целей обычно воспринимается нормально. Коммерческая перепродажа повышает юридические риски.
Если использовать такой инструмент, как Thunderbit (который работает через вашу собственную браузерную сессию), взаимодействие выглядит почти так же, как обычный просмотр сайта, но этические принципы остаются теми же независимо от инструмента. Если хотите глубже разобраться в , у нас есть отдельный материал на эту тему.
Советы и типичные ошибки
Совет: всегда начинайте с JSON-LD. Прежде чем писать сложные CSS-селекторы, проверьте, нет ли нужных данных в теге <script type="application/ld+json">. Он стабильнее, проще для парсинга и реже ломается при обновлениях frontend Goodreads.
Совет: используйте двухпроходную стратегию. Сначала соберите все URL книг со страниц списка, а потом переходите на страницы деталей. Так скрапер проще восстановить после сбоя, а список URL можно сохранить на диск как контрольную точку.
Ошибка: не обрабатывать отсутствующие поля. У каждой книги может не быть ISBN, жанров или описания. Всегда используйте .get() со значением по умолчанию или оборачивайте селекторы в if. Одна ошибка NoneType может сорвать трёхчасовой прогон.
Ошибка: слишком высокая скорость. Понимаю, хочется поставить time.sleep(0.5) и пролететь всё за минуту. Но Goodreads начнёт отдавать 403 уже после примерно 20–30 быстрых запросов, и если вас пометят, придётся ждать часами или менять IP. Задержка 4–5 секунд — оптимальный вариант.
Ошибка: верить старым туториалам. Если в гайде упоминается Goodreads API или используются классы вроде .field.value или #bookTitle, скорее всего, он устарел. Всегда сверяйте селекторы с живой страницей перед тем, как строить скрапер.
Подробнее о выборе подходящих инструментов и фреймворков для парсинга читайте в наших руководствах: и .
Вывод и главные тезисы
Собирать данные с Goodreads на Python вполне реально — нужно только знать, где лежат основные подводные камни. Коротко:
- API Goodreads больше нет (с декабря 2020 года). Парсинг — основной способ получать структурированные данные с платформы.
- Пустые результаты почти всегда связаны с JS-рендерингом, устаревшими селекторами, отсутствующими заголовками или проблемами с авторизацией при пагинации — а не с тем, что ваш код «неправильный».
- JSON-LD — ваш лучший друг для метаданных книг. Он стабильный, структурированный и меняется редко.
- Для многих страниц полок и списков после 5-й страницы нужна авторизация. Передавайте cookie
_session_id2. - Ограничения по скорости реальны. Делайте задержки 3–8 секунд и не превышайте 5 000 запросов в день.
- Двухпроходный подход (сначала URL, потом страницы деталей) надёжнее и легче возобновляется.
- Для тех, кто не хочет кодить (или просто хочет сэкономить вечер), берёт на себя всё: JS-рендеринг, пагинацию, обогащение подстраниц и экспорт — примерно в два клика.
Парсите ответственно, уважайте robots.txt, и пусть ваши данные о книгах всегда возвращаются не в виде [].
FAQ
Можно ли ещё использовать API Goodreads?
Нет. Goodreads прекратил поддержку публичного API в декабре 2020 года и больше не выдаёт новые ключи разработчика. Ключи, которые не использовались 30 дней, автоматически деактивировались. Сейчас для программного доступа к данным о книгах остаются веб-скрапинг или альтернативные API вроде Open Library и Google Books.
Почему мой скрапер Goodreads возвращает пустые результаты?
Самая частая причина — контент, отрендеренный JavaScript. Goodreads загружает отзывы, распределение оценок и многие блоки деталей через React/JavaScript, а простой requests.get() этого не видит. Для таких страниц переходите на Selenium или Playwright. Другие причины — устаревшие CSS-селекторы (Goodreads поменял HTML), отсутствие заголовка User-Agent (что вызывает блокировку 403) или запросы без авторизации на страницах полок с пагинацией.
Законно ли парсить Goodreads?
Сбор общедоступных данных для личного или исследовательского использования обычно считается допустимым в рамках текущей судебной практики (hiQ v. LinkedIn, Meta v. Bright Data). Однако Terms of Service Goodreads запрещают автоматический сбор данных, и всегда стоит проверять robots.txt. Не перепродавайте текст авторских отзывов в коммерческих целях и не перегружайте сайт лишними запросами.
Как собрать несколько страниц Goodreads?
Добавляйте ?page=N к URL полки или списка и проходите по номерам страниц в цикле. Останавливайтесь, когда результаты пустые или пропадает ссылка «next». Важно: некоторые страницы полок требуют авторизации (cookie _session_id2), чтобы возвращать результаты дальше 5-й страницы — без неё вы будете молча получать повтор данных с первой страницы.
Можно ли парсить Goodreads без кода?
Да. — это расширение Chrome, которое позволяет собирать данные с Goodreads в два клика: AI подсказывает поля, вы нажимаете «Scrape» и сразу экспортируете в Google Sheets, Excel, Airtable или Notion. Он автоматически справляется с JS-рендерингом, пагинацией и обогащением подстраниц, без Python и программирования.
Узнать больше