# Goodreads Scraper (`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:** Other
- **Stats:** 2 total users, 1 monthly users, 100.0% 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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# 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 |

#### 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 |
| `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,
  "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).

### 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 publicly available data: book metadata, aggregate ratings, public reviews with the display name Goodreads shows next to them, public author profiles and public lists. It does not collect emails, phone numbers or any data from private profiles or shelves. 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.

# 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.

## `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",
  "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/pZVHRxsJqB6pAUwVq/openapi.json
