# Goodreads Scraper — Books, Reviews, Authors & Lists (`tortuga/goodreads-scraper`) Actor

Scrape Goodreads books, reviews, authors, series, shelves and Listopia lists: ratings, rating distribution, review text, genres, ISBN, editions and author profiles. Search by title, ISBN or URL.

- **URL**: https://apify.com/tortuga/goodreads-scraper.md
- **Developed by:** [Trevor Ortega](https://apify.com/tortuga) (community)
- **Categories:** Social media, Education
- **Features:** Limited permissions
- **Stats:** 39 total users, 22 monthly users, 99.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 books

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Goodreads Scraper

Scrape Goodreads books, reviews, authors, series, shelves and Listopia lists: ratings, rating distribution, review text, genres, ISBN, editions and author profiles. Search by title, ISBN or URL.

Goodreads Scraper reads the same public data API that the Goodreads website itself uses, so every field on a book page is available as clean JSON: average rating with the full 1-5 star distribution, ratings and review counts per language, genres, ISBN-10/13 and ASIN, pages, format, publisher, publication and first-publication dates, series and position, awards, characters, places, edition count, and how many people are currently reading or want to read the book. Reviews come with the star rating, full text, date, likes, comments, the reviewer's public display name and the shelves they put the book on, and can be sorted newest/oldest or filtered by language and star rating. Author profiles, Listopia lists, shelves/genres, series pages and search results are covered too. No Goodreads account is needed. Download JSON, CSV or Excel, or use the API from Python, Node, n8n, Make or Google Sheets. You pay only for the items you get.

### What data does Goodreads Scraper extract?

Every item has a `type` field: `book`, `review`, `author`, `list_entry` or `search_result`.

#### Book items (`type: "book"`, mode `books`)

| Field | Description |
|---|---|
| `id`, `url`, `workId`, `workUrl` | Goodreads book (edition) ID and URL, plus the work ID shared by all editions |
| `title`, `titleComplete`, `originalTitle` | Title as shown, full title with series suffix, and the work's original title |
| `authors`, `authorName` | `[{name, url, id, role, isGoodreadsAuthor}]`; `role` is `Author`, `Illustrator`, `Translator`, `Editor`, ... |
| `isbn`, `isbn13`, `asin` | Identifiers of this edition (`isbn` fields are `null` for Kindle-only editions) |
| `description`, `descriptionHtml` | Plain-text and HTML description |
| `genres` | Top community genres, e.g. `["Fantasy", "Classics", "Fiction"]` |
| `averageRating`, `ratingsCount`, `reviewsCount` | Work-level rating stats (all editions) |
| `ratingDistribution` | `{"5": n, "4": n, "3": n, "2": n, "1": n}` counts |
| `reviewsByLanguage` | Text-review counts per ISO language code, e.g. `{"en": 79593, "es": 3691}` |
| `pages`, `format`, `publisher`, `publishedAt`, `firstPublishedAt`, `language` | Edition details; dates are `YYYY-MM-DD` |
| `series`, `allSeries` | `{name, position, url, id}` of the primary series and all series the book belongs to |
| `coverImageUrl` | Full-size cover |
| `awards` | `[{name, category, designation, year, url}]` including Goodreads Choice Awards (`designation` is `WINNER` or `NOMINEE`) |
| `characters`, `places` | Names from the work's "Characters" and "Setting" sections |
| `editionsCount`, `editionsUrl`, `quotesCount` | Number of editions and quotes for the work |
| `currentlyReadingCount`, `wantToReadCount` | Social counters shown on the book page |
| `kindleUrl`, `kindlePriceUsd` | Amazon Kindle link and price when Goodreads shows one |
| `kcaId`, `workKcaId` | Goodreads' internal GraphQL IDs (useful for the API) |
| `scrapedAt` | ISO 8601 timestamp |
| `changeType` | Only with **Only new or changed items** on: `new` on every item (this field appears on all item types) |

#### Review items (`type: "review"`, mode `reviews`)

| Field | Description |
|---|---|
| `bookId`, `bookTitle` | Book the review belongs to |
| `reviewId`, `url` | Goodreads review ID and public review URL (`/review/show/<id>`) |
| `rating` | Integer 1-5, or `null` for a text-only review without stars |
| `text`, `textHtml` | Review body as plain text and as HTML |
| `createdAt`, `updatedAt` | ISO 8601 UTC timestamps |
| `likes`, `commentsCount` | Like and comment counts |
| `reviewerName`, `reviewerUrl`, `reviewerId` | Reviewer's public display name, public Goodreads profile URL (`/user/show/<id>-<name>`) and numeric user ID, as flat columns. Book, list and search items use `authorName`/`authorUrl`/`authorId` for the book's author. Goodreads shows no separate username/handle, so there is no reviewer username field |
| `reviewer` | `{displayName, url, id, isAuthor, reviewsCount, followersCount}` as shown publicly next to the review |
| `shelf`, `shelves` | Exclusive shelf (`read`, `currently-reading`, `to-read`) and the reviewer's custom shelves/tags |
| `isSpoiler` | Whether the reviewer flagged the review as containing spoilers |

#### Author items (`type: "author"`, mode `author`)

`id`, `name`, `url`, `imageUrl`, `bio`, `born`, `bornPlace`, `died`, `website`, `genres`, `influences`, `memberSince`, `isGoodreadsAuthor`, `averageRating`, `ratingsCount`, `reviewsCount`, `followers`, `booksCount` (distinct works), `topBooks` (`[{bookId, title, url, averageRating, ratingsCount, publishedYear, editionsCount, coverImageUrl}]`, up to 10).

#### List, shelf and series rows (`type: "list_entry"`, mode `list`)

`listId`, `listTitle`, `listUrl`, `listKind` (`list`, `shelf` or `series`), `rank`, `bookId`, `title`, `authorName`, `authorUrl`, `score` and `votes` (Listopia), `shelvedCount` (shelves), `seriesPosition` and `seriesLabel` (series), `averageRating`, `ratingsCount`, `reviewsCount`, `publishedYear`, `editionsCount`, `url`, `coverImageUrl`.

#### Search results (`type: "search_result"`, mode `search`)

`searchTerm`, `rank`, `bookId`, `title`, `authorName`, `authorUrl`, `authorId`, `averageRating`, `ratingsCount`, `publishedYear`, `editionsCount`, `url`, `coverImageUrl`.

### How to use Goodreads Scraper

1. Pick a **Mode**: `books`, `reviews`, `author`, `list` or `search`.
2. Paste **Start URLs** (book, author, Listopia list, shelf/genre, series or search pages) and/or **Search terms** (titles or ISBNs), one per line. In `books` and `reviews` mode a list, shelf, series, author or search URL expands to the books on it (up to **Max books per list**).
3. Set **Max items** (and **Max reviews per book** in `reviews` mode) to cap the run and the cost.
4. Optionally choose the **Review order** (popular, newest, oldest), a **Review language** (`en`, `es`, ...) or a **Review star rating**.
5. Click **Start**. Results appear in the **Dataset** tab; export from there or use the API.

### Input example

```json
{
  "mode": "reviews",
  "startUrls": [{ "url": "https://www.goodreads.com/book/show/5907.The_Hobbit" }],
  "searchTerms": ["9780593135204", "Dune Frank Herbert"],
  "maxItems": 1000,
  "maxReviewsPerBook": 300,
  "reviewSort": "newest",
  "reviewLanguage": "en"
}
```

```json
{ "mode": "books", "startUrls": [{ "url": "https://www.goodreads.com/list/show/1.Best_Books_Ever" }], "maxBooksPerListing": 100, "maxItems": 100 }
```

```json
{ "mode": "list", "startUrls": [{ "url": "https://www.goodreads.com/shelf/show/science-fiction" }], "maxItems": 500 }
```

### Output example

```json
{
  "type": "book",
  "id": 54493401,
  "url": "https://www.goodreads.com/book/show/54493401-project-hail-mary",
  "title": "Project Hail Mary",
  "authors": [{ "name": "Andy Weir", "url": "https://www.goodreads.com/author/show/6540057.Andy_Weir", "id": 6540057, "role": "Author", "isGoodreadsAuthor": true }],
  "isbn": "0593135202",
  "isbn13": "9780593135204",
  "asin": "0593135202",
  "genres": ["Science Fiction", "Fiction", "Audiobook", "Space", "Adventure"],
  "averageRating": 4.51,
  "ratingsCount": 1859357,
  "reviewsCount": 182903,
  "ratingDistribution": { "5": 1165287, "4": 522731, "3": 135481, "2": 27349, "1": 8509 },
  "pages": 476,
  "format": "Hardcover",
  "publisher": "Ballantine Books",
  "publishedAt": "2021-05-04",
  "firstPublishedAt": "2021-05-04",
  "language": "English",
  "series": null,
  "awards": [{ "name": "Hugo Award", "category": "Best Novel", "designation": "NOMINEE", "year": "2022", "url": "https://www.goodreads.com/award/show/9-hugo-award" }],
  "editionsCount": 134,
  "currentlyReadingCount": 154810,
  "wantToReadCount": 1349020,
  "coverImageUrl": "https://m.media-amazon.com/images/S/compressed.photo.goodreads.com/books/1764703833i/54493401.jpg",
  "scrapedAt": "2026-09-09T16:40:12Z"
}
```

```json
{
  "type": "review",
  "bookId": 5907,
  "bookTitle": "The Hobbit, or There and Back Again",
  "reviewId": "36953286",
  "url": "https://www.goodreads.com/review/show/36953286",
  "rating": 5,
  "text": "In a hole in the ground there lived a hobbit.\n\nBooks exist in time and place ...",
  "createdAt": "2008-11-05T15:18:37Z",
  "likes": 872,
  "commentsCount": 150,
  "reviewerName": "Will Byrnes",
  "reviewerUrl": "https://www.goodreads.com/user/show/1526851-will-byrnes",
  "reviewerId": 1526851,
  "reviewer": { "displayName": "Will Byrnes", "url": "https://www.goodreads.com/user/show/1526851-will-byrnes", "id": 1526851, "isAuthor": false, "reviewsCount": 1373, "followersCount": 121499 },
  "shelf": "read",
  "shelves": ["all-time-favorites-fiction", "young-adult", "fiction"],
  "isSpoiler": false
}
```

### How to scrape Goodreads reviews for a book

Set **Mode** to `reviews`, paste the book URL (or its ISBN/title as a search term) and set **Max reviews per book**. Reviews are fetched 100 per request through Goodreads' own data API, so thousands of reviews per book are reachable; use **Review order** `newest` to follow a book over time, or filter by **Review language** and **Review star rating** (for example only 1-star English reviews).

To get only recent reviews, set **Only reviews newer than** to a date (`2026-10-01`) or a period (`7 days`, `24 hours`): the Actor reads newest first and stops at the first older review.

### Monitor changes on a schedule

Turn on **Only new or changed items** and the Actor remembers what earlier runs of the same input returned, so each run outputs only what is new: new reviews of the books you follow (read newest first, stopping at the first review an earlier run already saw), new books on a Listopia list, shelf, series, author or search page (books already seen are not fetched or charged again) and new list or search rows. Items earlier runs saved are dropped before they are saved, so they are not charged. Save the input as a task and add a Schedule to it in Apify Console (Schedules → Create new, for example daily). Every output item carries `changeType: "new"`; Goodreads items are only reported once (ratings and counts change constantly, so books are not re-reported when they change). **Max reviews per book** caps the new reviews per book per run, so set it above what a book gets between two runs; for lists and searches the first **Max items** rows of each are checked every run. Use **Monitoring key** to keep several schedules on the same input separate.

### Can I look up books by ISBN?

Yes. Put ISBN-10 or ISBN-13 numbers into **Search terms** (dashes are fine). Each ISBN resolves to its Goodreads edition; in `books` mode you get the full book item, in `reviews` mode its reviews. Titles work the same way; add the author name to a title to disambiguate (`Dune Frank Herbert`).

### How to get all books from a Goodreads list, shelf or genre

Use `list` mode with a Listopia URL (`/list/show/...`), a shelf or genre URL (`/shelf/show/fantasy`, `/genres/fantasy`) or a series URL to get the ranked rows with score, votes, rating and book URL. Listopia lists are paginated in full (100 rows per page); shelf and genre pages show their top 50 books to visitors who are not signed in, so a shelf yields 50 rows. Use `books` mode with the same URL to get the full book item for every book on it (**Max books per list** controls how many are expanded).

### Does it work without a Goodreads account?

Yes. The actor only reads pages and API responses that Goodreads serves to anonymous visitors. Private profiles, private shelves and anything that requires signing in are never accessed.

### Is scraping Goodreads legal?

This Actor collects only data Goodreads shows publicly to signed-out visitors: book metadata, aggregate ratings, public reviews with the reviewer's display name, public profile link, user ID and public review/follower counts as shown next to the review, public author profiles and public lists. It does not collect reviewer photos/avatars, emails, phone numbers or any data from private profiles or shelves, and nothing behind a login. Reviewer names and profile links are personal data under GDPR and similar laws: make sure you have a lawful basis for processing them, and do not use them to contact or profile individuals. You are responsible for how you use the data and for complying with Goodreads' terms and applicable law (including GDPR/CCPA if you process reviewer names).

### Pricing

Pay per item: `book` $0.003, `author` $0.003, `review` $0.001, list/shelf/series/search row $0.001. There is no subscription and no start fee; Apify's free plan is enough to try it.

### Integrations and API

Use the run in Zapier, Make, n8n, Google Sheets, or call it from Python/Node with the Apify client. See the **API** tab for ready-made snippets.

### Support

Found a bug or need a field added? Open an issue in the **Issues** tab; it is usually answered within a day.

### Related scrapers

- [Steam Reviews Scraper](https://apify.com/tortuga/steam-scraper): Game Reviews, Prices & Tags
- [IMDb Scraper](https://apify.com/tortuga/imdb-scraper): Movies, TV, Ratings & Reviews
- [Letterboxd Scraper](https://apify.com/tortuga/letterboxd-scraper): Films, Reviews, Ratings & Lists
- [Metacritic Scraper](https://apify.com/tortuga/metacritic-scraper): Metascores, Reviews, Games & Movies

### Found it useful?

A ★ review on this Actor's Apify Store page helps other users find it and tells us what to improve. Issues and feature requests are welcome in the Actor's **Issues** tab.

# Actor input Schema

## `mode` (type: `string`):

What to scrape. `books`: one `book` item per book (from book URLs, search terms, or every book on list/shelf/series/author pages). `reviews`: `review` items for each book. `author`: one `author` profile per author URL (book URLs resolve to their author). `list`: `list_entry` rows from Listopia lists, shelves/genres and series pages. `search`: `search_result` rows for each search term.

## `startUrls` (type: `array`):

Goodreads URLs, one per line: book pages (`/book/show/5907.The_Hobbit`), author pages (`/author/show/656983`), Listopia lists (`/list/show/1.Best_Books_Ever`), shelves or genres (`/shelf/show/fantasy`, `/genres/fantasy`), series (`/series/66175-middle-earth`) or search pages (`/search?q=dune`).

## `searchTerms` (type: `array`):

Book titles, `title author` pairs or ISBN-10/ISBN-13 numbers, one per line. In `books`/`reviews` mode each term resolves to its best-matching book; in `search` mode every match is returned.

## `maxItems` (type: `integer`):

Overall cap on dataset items for the run (all item types combined). Keeps cost predictable.

## `maxReviewsPerBook` (type: `integer`):

Cap on reviews collected for each book in `reviews` mode. Goodreads exposes the full review list, so large values work (100 per request).

## `maxBooksPerListing` (type: `integer`):

In `books` and `reviews` mode, how many books to take from each list, shelf, series, author or search start URL before fetching their details.

## `reviewSort` (type: `string`):

`default`: Goodreads' popular-first order (same as the website). `newest` / `oldest`: by review date.

## `reviewLanguage` (type: `string`):

ISO 639-1 code to keep only reviews written in that language, e.g. `en`, `es`, `de`, `fr`, `pt`. Leave empty for all languages.

## `reviewRating` (type: `integer`):

Keep only reviews with this star rating (1-5). Leave empty for all ratings.

## `reviewsNewerThan` (type: `string`):

Only reviews newer than: a date (YYYY-MM-DD) or a period such as '7 days' or '24 hours'. Applies in `reviews` mode: reviews are then read newest first and paging stops at the first older review, so a daily run reads only a page or two per book.

## `onlyNewOrChanged` (type: `boolean`):

Remember what earlier runs of this search returned and output only new items or items whose price/status changed. Use with a Schedule; unchanged items are not charged. On Goodreads this means items earlier runs did not output: new reviews (read newest first; Max reviews per book caps the new reviews per book per run), new books on a list, shelf, series, author or search page (known books are not fetched again), and new list or search rows (the first Max items rows of each list or search are checked).

## `monitoringKey` (type: `string`):

Name for this watch, so several schedules on the same search stay separate; defaults to a key derived from the search input.

## `proxyConfiguration` (type: `object`):

Apify Proxy is recommended. Goodreads puts an AWS WAF JavaScript challenge in front of search and book pages after a few dozen requests from one IP; the actor rotates IPs when that happens. If the run summary reports `wafChallenges`, switch to residential proxies.

## Actor input object example

```json
{
  "mode": "books",
  "startUrls": [
    {
      "url": "https://www.goodreads.com/book/show/5907.The_Hobbit"
    },
    {
      "url": "https://www.goodreads.com/book/show/54493401-project-hail-mary"
    }
  ],
  "searchTerms": [],
  "maxItems": 100,
  "maxReviewsPerBook": 30,
  "maxBooksPerListing": 50,
  "reviewSort": "default",
  "onlyNewOrChanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        {
            "url": "https://www.goodreads.com/book/show/5907.The_Hobbit"
        },
        {
            "url": "https://www.goodreads.com/book/show/54493401-project-hail-mary"
        }
    ],
    "searchTerms": [],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("tortuga/goodreads-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [
        { "url": "https://www.goodreads.com/book/show/5907.The_Hobbit" },
        { "url": "https://www.goodreads.com/book/show/54493401-project-hail-mary" },
    ],
    "searchTerms": [],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("tortuga/goodreads-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    {
      "url": "https://www.goodreads.com/book/show/5907.The_Hobbit"
    },
    {
      "url": "https://www.goodreads.com/book/show/54493401-project-hail-mary"
    }
  ],
  "searchTerms": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call tortuga/goodreads-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tortuga/goodreads-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/egalTdiO6NBRkgWPU/builds/km8y2KEMIMy0SEQ4e/openapi.json
