# Goodreads Scraper \[$1.00/1K💰] | Books + Reviews (`ahmed_jasarevic/goodreads-scraper`) Actor

Extract Goodreads books and review text from the official Goodreads API used by Amazon - ISBN, ratings, genres, editions plus reviewer, rating, date and full text. Books and reviews in one run.

- **URL**: https://apify.com/ahmed_jasarevic/goodreads-scraper.md
- **Developed by:** [Ahmed Jasarevic](https://apify.com/ahmed_jasarevic) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

Extract Goodreads book metadata and full review text from the same AWS AppSync GraphQL endpoint the Goodreads website itself uses — ISBN-10 and ISBN-13, ratings, genres, editions, cover art, plus reviewer name, rating, date and complete review text — books and reviews in a single run.

- **Books and reviews together.** One run writes both datasets. No second Actor, no second bill, no re-scraping to get review text.
- **No paid proxy by default.** Talks to Goodreads' own AppSync GraphQL API rather than crawling HTML pages.
- **No 10-page review ceiling.** Because it uses the JSON API instead of ordinary HTML pagination, review depth is not capped at the 10 pages that HTML-based DIY scraping hits.
- **About 5× cheaper per book** than the verified all-in-one Goodreads competitor at `$5.00` per 1,000, against `$1.00` per 1,000 here.

### Main Use Cases

- **Book metadata dataset** — build a structured catalogue with ISBN, publisher, format, page count, publication date, genres and cover URLs
- **Book review dataset** — collect complete review text with ratings, dates, likes, spoiler flags and reviewer profiles
- **Book recommendation engine training data** — join reviews to books to build rating and text-based recommendation models
- **Genre reading list extraction** — expand from a genre into thousands of books with ratings and reviews
- **ISBN and edition data** — use `includeEditions` to pull every other edition of a work, covering ISBN-10/ISBN-13 and format variants
- **Author catalogue research** — pull an author's works, contributor roles and Goodreads IDs

### How It Works

1. Books are resolved from `startUrls`, `bookIds` or `searchQueries` — any mix of the three.
2. Each book is queried through Goodreads' AWS AppSync GraphQL endpoint and parsed into one flat `Books` row.
3. When `includeReviews` is on (the default), reviews for each book are fetched into a separate `reviews` dataset, filtered and sorted by `reviewMinRating`, `reviewMaxRating` and `reviewSort`, up to `maxReviewsPerBook`.
4. When `includeEditions` is on, sibling editions of the same work are enqueued and emitted as their own book rows, so ISBN and format variants are not missed.

Three dataset views are available: `overview` for a quick scan, `details` for the full record, and `ratings` for rating-only analysis.

### Build Book and Review Datasets Without a Proxy

Goodreads' official public API has been documented as deprecated, which is why so many DIY approaches fall back to scraping HTML pages — and those approaches usually stall after about 10 pages of reviews and need a residential proxy to avoid blocks.

This Actor uses the same AWS AppSync GraphQL service that powers the Goodreads website, so you get structured, paginated JSON instead of scraped markup. That removes the review-depth ceiling and the proxy requirement.

### Input

| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
| `startUrls` | array | no | `https://www.goodreads.com/book/show/3` | Direct book URLs. Always populated with at least the default. |
| `bookIds` | array | no | — | Numeric Goodreads book IDs. |
| `searchQueries` | array | no | — | Search terms, e.g. `data science`. Each becomes a discovery run. |
| `maxItems` | integer | no | `100` | Maximum books to collect. On the FREE tier this is clamped to 10. |
| `includeReviews` | boolean | no | `true` | Fetch reviews into the `reviews` dataset. |
| `maxReviewsPerBook` | integer | no | `20` | Maximum reviews per book. |
| `reviewSort` | string | no | `DEFAULT` | One of `DEFAULT`, `NEWEST`, `OLDEST`. |
| `reviewMinRating` | integer | no | — | Only keep reviews at or above this rating. |
| `reviewMaxRating` | integer | no | — | Only keep reviews at or below this rating. |
| `includeEditions` | boolean | no | `false` | Also emit other editions of the same work as separate book rows. |
| `stripHtmlFromReviews` | boolean | no | `true` | Remove HTML tags from `reviewText`. Raw markup stays in `reviewTextHtml`. |
| `maxRequestsPerCrawl` | integer | no | `5000` | Request budget for the run. |
| `maxConcurrency` | integer | no | `5` | Parallel requests. |
| `proxy` | object | no | — | Proxy configuration. Not needed on the default AppSync path. |

### Output

**Default dataset — books** (31 fields):

`bookId`, `workId`, `title`, `titleComplete`, `author`, `authorId`, `authorUrl`, `isGoodreadsAuthor`, `authorRole`, `additionalContributors`, `isbn`, `isbn13`, `asin`, `format`, `pageCount`, `publisher`, `language`, `publishedAt`, `publishedAtIso`, `averageRating`, `ratingsCount`, `reviewsCount`, `ratingsDistribution`, `genres`, `genreUrls`, `coverUrl`, `coverUrlThumbnail`, `description`, `url`, `workUrl`, `sourceUrl`

**`reviews` dataset** (27 fields, one row per review, each carrying its own book context so you can group or join without a second lookup):

`bookId`, `workId`, `title`, `author`, `url`, `isbn`, `isbn13`, `averageRating`, `coverUrl`, `reviewId`, `reviewText`, `reviewTextHtml`, `reviewRating`, `reviewPreciseRating`, `reviewDate`, `reviewUpdatedAt`, `reviewLikes`, `reviewComments`, `reviewSpoilerStatus`, `reviewRecommends`, `reviewShelf`, `reviewTags`, `reviewerName`, `reviewerId`, `reviewerUrl`, `reviewerIsAuthor`

Join on `bookId`.

### Example Input

```json
{
  "searchQueries": ["machine learning", "deep learning"],
  "maxItems": 50,
  "includeReviews": true,
  "maxReviewsPerBook": 30,
  "reviewSort": "NEWEST",
  "reviewMinRating": 3,
  "includeEditions": false
}
```

### Example Output

```json
{
  "bookId": "35093465",
  "workId": "35544962",
  "title": "Designing Machine Learning Systems",
  "titleComplete": "Designing Machine Learning Systems: A Practical Guide",
  "author": "Chip Huyen",
  "authorId": "136985838",
  "authorUrl": "https://www.goodreads.com/author/show/136985838",
  "isGoodreadsAuthor": true,
  "authorRole": "Author",
  "additionalContributors": ["Sam Chen (Foreword)"],
  "isbn": "1098106062",
  "isbn13": "9781098106064",
  "asin": "B09FHK7BKW",
  "format": "Paperback",
  "pageCount": 320,
  "publisher": "O'Reilly Media",
  "language": "en",
  "publishedAt": "2022-02-02",
  "publishedAtIso": "2022-02-02T00:00:00Z",
  "averageRating": 4.32,
  "ratingsCount": 2417,
  "reviewsCount": 512,
  "ratingsDistribution": { "5": 1389, "4": 612, "3": 214, "2": 108, "1": 94 },
  "genres": ["Computers", "Computer Science", "Artificial Intelligence", "Machine Learning"],
  "genreUrls": ["https://www.goodreads.com/shelf/show/computers", "https://www.goodreads.com/shelf/show/artificial-intelligence"],
  "coverUrl": "https://i.gr-assets.com/images/S/Designing-Machine-Learning-Systems.jpg",
  "coverUrlThumbnail": "https://i.gr-assets.com/images/S/Designing-Machine-Learning-Systems-s.jpg",
  "description": "An iterative, production-focused guide to building ML systems.",
  "url": "https://www.goodreads.com/book/show/35093465",
  "workUrl": "https://www.goodreads.com/work/editions/35544962",
  "sourceUrl": "https://www.goodreads.com/book/show/35093465"
}
```

And one row from the `reviews` dataset:

```json
{
  "bookId": "35093465",
  "workId": "35544962",
  "title": "Designing Machine Learning Systems",
  "author": "Chip Huyen",
  "url": "https://www.goodreads.com/book/show/35093465",
  "isbn": "1098106062",
  "isbn13": "9781098106064",
  "averageRating": 4.32,
  "coverUrl": "https://i.gr-assets.com/images/S/Designing-Machine-Learning-Systems.jpg",
  "reviewId": "57129334801",
  "reviewText": "The chapter on data pipelines is the most practical treatment I have read. We adopted the feedback-loop structure almost verbatim.",
  "reviewTextHtml": "<p>The chapter on data pipelines is the most practical treatment I have read. We adopted the feedback-loop structure almost verbatim.</p>",
  "reviewRating": 5,
  "reviewPreciseRating": 4.5,
  "reviewDate": "2025-11-02",
  "reviewUpdatedAt": "2025-11-02",
  "reviewLikes": 87,
  "reviewComments": 4,
  "reviewSpoilerStatus": false,
  "reviewRecommends": true,
  "reviewShelf": "currently-reading",
  "reviewTags": ["machine learning", "engineering"],
  "reviewerName": "Priya R.",
  "reviewerId": "104392837",
  "reviewerUrl": "https://www.goodreads.com/author/show/104392837",
  "reviewerIsAuthor": false
}
```

### Pricing

Pay-per-result. A book costs `$1.00` per 1,000 on the FREE tier and each review costs `$0.80` per 1,000. On the FREE tier `maxItems` is clamped to 10 books, so plan a paid tier for real dataset work.

### Integrations & Automation

- **Apify API** — read both datasets from Python, Node or any HTTP client.
- **Schedules** — a daily run keeps a genre catalogue current; a weekly run is enough for review-volume tracking.
- **Webhooks** — push new reviews into a data warehouse as runs complete.
- **MCP** — callable by AI agents through the Apify MCP server.

### Related Actors

- `ahmed_jasarevic/link-preview-api` — extract Open Graph and JSON-LD metadata from Goodreads URLs for enrichment
- `ahmed_jasarevic/preply-scraper` — comparable structured-catalogue extraction for the language tutoring market

### FAQ

#### Is the official Goodreads API still available?

The official Goodreads public API has been documented as deprecated. This Actor uses the AWS AppSync GraphQL service behind the Goodreads website, which is a different endpoint and still returns structured data.

#### Why use this actor instead of scraping Goodreads HTML?

HTML scraping is fragile, usually needs a residential proxy, and runs into a review-depth ceiling of roughly 10 pages. The JSON path here returns structured, paginated results without that ceiling.

#### Can I get book reviews as well as metadata?

Yes, and that is the default — `includeReviews` is `true`. Reviews go into a separate `reviews` dataset, one row per review, each row carrying its own book context (`bookId`, `title`, `author`, `isbn`) so you can group without a join if you prefer.

#### How do I get ISBN and format data for every edition?

Set `includeEditions: true`. Sibling editions of the same work are then emitted as their own book rows, giving you ISBN-10, ISBN-13, ASIN, format, publisher and page count per edition.

#### Does it strip HTML from review text?

Yes by default, via `stripHtmlFromReviews: true`, which cleans `reviewText` while the original markup is preserved in `reviewTextHtml`. Set it to `false` if you need the raw text field to keep tags too.

#### What are alternatives to this actor?

The main alternatives are HTML-based Goodreads scrapers, and an all-in-one tool that also covers authors, lists and series. The all-in-one option is roughly 5× more expensive per result and defaults to a residential proxy; several competitors split books and reviews into two separate Actors, which doubles your runs. Goodreads itself, StoryGraph and LibraryThing are alternative *data sources* rather than equivalent scrapers.

### For AI Agents & LLM Apps

**Purpose:** returns Goodreads book rows in the default dataset and review rows in a named `reviews` dataset, each review row carrying its own book context.

**Minimal working input** — smallest input that produces a valid run:

```json
{ "searchQueries": ["machine learning"], "maxItems": 5, "includeReviews": false }
```

**Books plus reviews variant:**

```json
{
  "searchQueries": ["science fiction"],
  "maxItems": 200,
  "includeReviews": true,
  "maxReviewsPerBook": 50,
  "reviewSort": "NEWEST",
  "reviewMinRating": 4
}
```

**Editions variant:**

```json
{ "bookIds": ["35093465"], "includeEditions": true, "maxItems": 50 }
```

**Output fields** — books: `bookId, workId, title, titleComplete, author, authorId, authorUrl, isGoodreadsAuthor, authorRole, additionalContributors, isbn, isbn13, asin, format, pageCount, publisher, language, publishedAt, publishedAtIso, averageRating, ratingsCount, reviewsCount, ratingsDistribution, genres, genreUrls, coverUrl, coverUrlThumbnail, description, url, workUrl, sourceUrl`. Reviews (named dataset `reviews`): `bookId, workId, title, author, url, isbn, isbn13, averageRating, coverUrl, reviewId, reviewText, reviewTextHtml, reviewRating, reviewPreciseRating, reviewDate, reviewUpdatedAt, reviewLikes, reviewComments, reviewSpoilerStatus, reviewRecommends, reviewShelf, reviewTags, reviewerName, reviewerId, reviewerUrl, reviewerIsAuthor`.

**Behaviors an agent should know**

- `startUrls` is **never empty** — it ships with a default book URL. If you want only your own targets, pass `bookIds` or `searchQueries` and explicitly set `startUrls` to `[]`, or the default book will also be collected.
- Reviews land in a **separate named dataset** (`reviews`), not in the book row. Join on `bookId`.
- `includeReviews` defaults to **true**, so reviews are billed unless you turn it off. Set `includeReviews: false` to avoid surprise charges.
- `maxItems` counts **books**, not reviews. `maxReviewsPerBook` controls review depth per book.
- On the FREE pricing tier `maxItems` is clamped to 10 books.
- `includeEditions: true` adds sibling editions as **extra rows**, so row counts will exceed the number of works you asked for. Deduplicate on `workId` if you want one row per work.
- `stripHtmlFromReviews` only affects `reviewText`; the raw markup is always available in `reviewTextHtml`.
- `reviewMinRating` / `reviewMaxRating` filter reviews only, never books.
- Three dataset views exist: `overview`, `details`, `ratings`.
- No proxy is required on the default path.

**Billing:** pay-per-result — `$1.00` per 1,000 books on the FREE tier, plus `$0.80` per 1,000 reviews.

### Legal & Compliance

This Actor is an independent tool. It is not affiliated with, endorsed by, or sponsored by Goodreads or Amazon.

It reads only publicly accessible book pages, public review pages and public search results through Goodreads' own GraphQL service. It does not bypass authentication, does not use CAPTCHA solving, and does not attempt to defeat rate limits.

You are responsible for your own compliance with Goodreads' Terms of Service and with applicable data-protection law. Review output contains personal data — reviewer names, reviewer profile IDs and URLs. Under GDPR and comparable regimes you need a lawful basis for processing it, and you must handle it accordingly.

Please do not use extracted personal data for unsolicited commercial outreach in violation of applicable law, including CAN-SPAM, GDPR or TCPA.

### SEO Keywords

goodreads scraper, goodreads books dataset, goodreads reviews, goodreads api, goodreads api deprecated, goodreads review text, book review dataset, book metadata dataset, isbn and edition data, goodreads genre lists, reading list data, book recommendation dataset, goodreads series data, author data, shelf and list scraping, book price comparison, reading statistics database, goodreads book search, analyse reader sentiment, goodreads alternatives, build a book recommendation engine, 10 review page limit workaround

# Actor input Schema

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

Public Goodreads URLs. Supported: a book page (/book/show/3), a genre (/genres/art), a shelf (/shelf/show/...), a list (/list/show/...), an author's book list (/author/list/...), an ISBN page (/book/isbn/9780441013593) or a search URL (/search?q=dune). Genre, shelf, list and ISBN pages contribute up to 'max books per source' books, each with its own reviews.

## `bookIds` (type: `array`):

Goodreads book legacy IDs (the number in /book/show/<id>). Used when you do not want to build full URLs.

## `searchQueries` (type: `array`):

Title/author search terms, resolved to book IDs. Each term returns only a handful of matches, so this is best-effort - prefer startUrls with a search URL, genre or list page for full coverage.

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

Total number of books to scrape. This is the only size limit: it is shared by all start URLs, book IDs and search terms, and it is split evenly between them, so no single URL can use up the whole run.

## `includeReviews` (type: `boolean`):

Fetch individual review rows (text, rating, date, reviewer). Reviews go to their own separate dataset; books always go to the books dataset.

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

Maximum number of reviews to pull per book. Reviews are paged through with cursor pagination.

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

How to order reviews.

## `reviewMinRating` (type: `integer`):

Only return reviews with at least this star rating (1-5). Leave empty for all ratings.

## `reviewMaxRating` (type: `integer`):

Only return reviews with at most this star rating (1-5). Leave empty for all ratings.

## `includeEditions` (type: `boolean`):

Also resolve sibling editions of the same work (different ISBNs / formats of the same title).

## `stripHtmlFromReviews` (type: `boolean`):

Convert review text from Goodreads' HTML format to plain text. When false, the original HTML is kept in reviewTextHtml.

## `maxRequestsPerCrawl` (type: `integer`):

Hard safety cap on total network requests made by this run. Set to 0 for unlimited.

## `maxConcurrency` (type: `integer`):

Number of books processed in parallel. 5-10 is comfortable; keep it modest out of courtesy.

## `proxy` (type: `object`):

Select proxies to be used by your crawler.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.goodreads.com/book/show/3"
  ],
  "bookIds": [],
  "searchQueries": [],
  "maxItems": 100,
  "includeReviews": true,
  "maxReviewsPerBook": 20,
  "reviewSort": "DEFAULT",
  "includeEditions": false,
  "stripHtmlFromReviews": true,
  "maxRequestsPerCrawl": 5000,
  "maxConcurrency": 5,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `books` (type: `string`):

One row per Goodreads book/edition: title, author, ISBN, publisher, pages, publication date, genres, cover URL and work-level ratings statistics.

## `reviews` (type: `string`):

One row per public review: text, star rating, date, reviewer, shelf, tags, likes. Book context (bookId, title, author) is included on every row so the dataset joins on its own.

# 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 = {
    "proxy": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ahmed_jasarevic/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 = { "proxy": { "useApifyProxy": False } }

# Run the Actor and wait for it to finish
run = client.actor("ahmed_jasarevic/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 '{
  "proxy": {
    "useApifyProxy": false
  }
}' |
apify call ahmed_jasarevic/goodreads-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ahmed_jasarevic/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/a7uRhJhnVEdybBAkz/builds/rgKnTShHAynMdldlK/openapi.json
