Как собрать данные с Goodreads с помощью Python (без пустых результатов)

Последнее обновление: April 16, 2026

Нет ничего более обидного, чем написать 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 для разработчиков. Никакого поста в блоге, никаких рассылок — только небольшой баннер на странице документации и множество озадаченных разработчиков.

goodreads-data-access-tools.webp

Последствия не заставили себя ждать. Один разработчик, 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-сценариев.

troubleshooting-empty-array-causes.webp

Пагинация, которая всегда возвращает только первую страницу

Вот коварная проблема. Вы пишете цикл, увеличиваете ?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 альтернативы

goodreads-scraping-flow.webp

Шаг 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

  1. Установите расширение Thunderbit для Chrome из и откройте страницу списка, полки или поиска на Goodreads.
  2. Нажмите «AI Suggest Fields» в боковой панели Thunderbit. AI прочитает страницу и предложит столбцы — обычно название, автор, рейтинг, URL обложки и ссылку на книгу.
  3. Нажмите «Scrape» — данные за несколько секунд соберутся в структурированную таблицу.
  4. Экспортируйте в 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 и программирования.

Узнать больше

Shuai Guan
Shuai Guan
Co-founder/CEO @ Thunderbit. Passionate about cross section of AI and Automation. He's a big advocate of automation and loves making it more accessible to everyone. Beyond tech, he channels his creativity through a passion for photography, capturing stories one picture at a time.
Содержание

Попробуй Thunderbit

Собирай лиды и другие данные всего за 2 клика. На базе ИИ.

Получить Thunderbit Это бесплатно
Извлекай данные с помощью ИИ
Легко передавай данные в Google Sheets, Airtable или Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week