# Goodreads Review Scraper By Star Rating & Spoiler Filter (`simpleapi/goodreads-review-scraper`) Actor

📚 Goodreads Review Scraper extracts ratings, reviews, dates, reviewers & metadata from Goodreads book pages at scale. 🔍 Export clean data to CSV/JSON for sentiment, market research & content analysis. 🚀 Ideal for authors, publishers, researchers & SEO teams.

- **URL**: https://apify.com/simpleapi/goodreads-review-scraper.md
- **Developed by:** [SimpleAPI](https://apify.com/simpleapi) (community)
- **Categories:** Developer tools, Automation, SEO tools
- **Stats:** 3 total users, 0 monthly users, 78.8% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.99 / 1,000 results

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 Review Scraper — Star Ratings, Spoilers and Likes

Goodreads Review Scraper pulls public book reviews straight from Goodreads' own review feed and returns only the ones that match your criteria: a star-rating range, spoiler-free or spoilers-only, a minimum number of likes or comments, and a minimum review length. Every row carries the reviewer's name and profile, the full review text, rating, spoiler flag, engagement counts and absolute ISO timestamps. Publishers, book marketers, reader-sentiment researchers and AI teams building book-recommendation tools use it to pull exactly the reviews they need instead of downloading a book's entire review history and filtering it afterward.

### 📚 What is Goodreads Review Scraper?

Goodreads Review Scraper is an Apify Actor that reads a Goodreads book's public review list and returns a filtered, typed JSON dataset — one row per matching review. No Goodreads account or login is required; every field it returns is already visible to any anonymous visitor on the book's reviews page.

- **Discovery by book URL or bare book ID** — paste a full Goodreads book link, or just the numeric ID (e.g. `26032825`), and the Actor builds the URL for you
- **Star-rating range filter** — `minStars` / `maxStars` narrow the result to any 0–5★ band
- **Spoiler filtering on two layers** — Goodreads' own spoiler flag, plus an optional AI-based second pass
- **Engagement and length thresholds** — minimum likes, minimum comments, minimum review length, and a text-only toggle
- **Absolute ISO timestamps** — `createdAtIso`, `updatedAtIso`, `lastRevisionAtIso` and `scrapedAt` alongside the raw epoch-millisecond fields
- **Sort, language and edition controls** — popular / newest / oldest ordering, a review-language filter, and whole-work vs. this-edition-only scope

### What data can I extract with Goodreads Review Scraper?

Every matching review is one dataset row carrying the review's own content, its spoiler and engagement signals, the reviewer's profile, and derived timestamps.

| Field | Example value | Use case |
| ----- | ----- | ----- |
| `bookUrl` | `"https://www.goodreads.com/book/show/26032825"` | Joins every row back to the book it was scraped from |
| `id` | `"5972134816"` | Unique review identifier |
| `rating` | `4` | The star rating the review left; what `minStars`/`maxStars` filter on |
| `spoilerStatus` | `true` | Goodreads' own raw spoiler flag on the review |
| `isSpoiler` | `true` | Decorated boolean mirror of `spoilerStatus` |
| `aiIsSpoiler` | `true` / `false` / `null` | AI-detected spoiler flag; `null` until `aiSpoilerDetection` runs |
| `aiSpoilerConfidence` | `0.93` / `null` | The AI model's confidence in `aiIsSpoiler`, 0.0–1.0 |
| `text` | `"This book completely wrecked me…"` | Full review text — sentiment mining, quote pulls |
| `textLength` | `812` | Character count of `text`; what `minTextLength`/`textOnly` filter on |
| `likeCount` | `143` | Likes on the review; what `minLikeCount` filters on |
| `commentCount` | `12` | Comments on the review; what `minCommentCount` filters on |
| `recommendFor` | `null` | Goodreads' own "recommended for" tag on the review, when set |
| `createdAt` | `1610013600000` | Raw creation time, epoch milliseconds |
| `updatedAt` | `1612000000000` | Raw last-update time, epoch milliseconds |
| `lastRevisionAt` | `1612086400000` | Raw last-text-edit time, epoch milliseconds |
| `createdAtIso` | `"2021-01-07T10:00:00Z"` | Absolute ISO creation date, sortable without conversion |
| `updatedAtIso` | `"2021-01-30T09:06:40Z"` | Absolute ISO last-update date |
| `lastRevisionAtIso` | `"2021-01-31T09:06:40Z"` | Absolute ISO last-text-edit date |
| `shelving` | `{ "shelf": {...}, "taggings": [...], "webUrl": "..." }` | The reader's shelf and any reader-added tags attached to this review |
| `creator` | `{ "name": "...", "webUrl": "...", ... }` | The reviewer's public profile — see below |
| `__typename` | `"Review"` | GraphQL type name, passed through as-is |
| `scrapedAt` | `"2026-07-26T12:00:00Z"` | When the Actor captured this row |

`creator` is a nested object: `id`, `name`, `webUrl`, `imageUrlSquare`, `isAuthor`, `followersCount`, `textReviewsCount`, `__typename`, and `contributor` (`id`, `works.totalCount`, `__typename`). `shelving` nests `shelf` (`name`, `webUrl`, `__typename`) and `taggings` (an array of `{ tag: { name, webUrl, __typename } }`).

Two Goodreads fields are **dropped rather than returned as dead null columns**: the top-level `viewerHasLiked` and `creator.viewerRelationshipStatus`. Both are always empty for a logged-out (anonymous) request, so the Actor omits them instead of shipping a column that is never anything but `null`.

#### Spoiler and engagement signals

`isSpoiler` (Goodreads' own flag) and `aiIsSpoiler` (the optional AI layer) combine into a single spoiler verdict that `spoilerFilter` acts on — a review counts as a spoiler if *either* flag says so. `likeCount` and `commentCount` are the community's own signal of which reviews are worth reading; a marketing team pulling quotes for a book page typically wants `spoilerFilter: "exclude"` plus a `minLikeCount` floor, so only well-liked, spoiler-free reviews come back.

#### Star rating and length-targeting fields

`rating`, `textLength` and `spoilerStatus` are the three fields the input schema turns into filters — `minStars`/`maxStars` on rating, `minTextLength`/`textOnly` on length, and `spoilerFilter` on the spoiler flags. `createdAtIso` lets you scope a run to a launch window or a specific print run without touching epoch math.

### ⚠️ Why not build this yourself?

Goodreads previously ran a public developer API for third parties, but Amazon, which owns Goodreads, stopped issuing new API keys years ago, and the program is no longer open to new developers — so building a fresh integration against an official Goodreads API is not an option today.

The practical alternative — scraping the site directly — has its own obstacle. Goodreads puts its book HTML pages behind an AWS WAF JavaScript challenge: a plain server-side request gets back an HTTP 202 with an `x-amzn-waf-action: challenge` header instead of a page, so a request never reaches the reviews at all.

This Actor works around that without touching the WAF-protected HTML page. It resolves each book's internal work/review resource ID through Goodreads' own public GraphQL endpoint — the same AWS AppSync endpoint and public `apiKey` value that Goodreads' website ships in its own client-side JS bundle to every visitor — which sits outside the WAF. If that stable key ever stops working, the Actor falls back to re-extracting a fresh one straight from the page's JS bundles. Maintaining that discovery step, plus a direct → Apify datacenter → Apify residential proxy escalation ladder for when a request is blocked outright, is the ongoing work this Actor absorbs so you don't have to.

### What can you do with Goodreads review data?

- 📣 **A publisher's marketing team** launching a title pulls `spoilerFilter: "exclude"`, `minStars: 4` and a `minLikeCount` floor to source spoiler-free, well-liked quotes for the book's sales page — no manual read-through of hundreds of reviews.
- 🔬 **An author or literary agent** researching reader reception sets `minStars: 1`, `maxStars: 2` and a `minTextLength` floor to surface substantive complaints rather than one-line pans, ahead of a sequel or a revised edition.
- 📊 **A market or reading-trend researcher** runs the same filter set across many `urls` in one call and compares `rating` distributions and `textLength` across a genre or an author's backlist.
- 🤖 **An AI engineer** building a book-recommendation or sentiment model indexes `text`, `rating` and `isSpoiler` per book into a vector store or training set, with `aiSpoilerDetection` catching plot spoilers Goodreads' own flag missed before the text reaches readers.

### ⬇️ Input

Only `urls` is required; every filter below defaults to "keep everything."

| Parameter | Required | Type | Description | Example value |
| ----- | ----- | ----- | ----- | ----- |
| `urls` | Yes | array | Goodreads book links, or bare numeric book IDs — the Actor auto-corrects an ID into a full URL. | `["26032825"]` |
| `maxItems` | No | integer | Matching reviews to save **per book**. Min `1`, max `10000`, default `20`. The Actor paginates until it collects this many matches or the review list ends. | `50` |
| `minStars` | No | integer | Keep reviews rated at least this many stars. Min `0`, max `5`, default `0`. `0` = no minimum (also keeps unrated reviews). | `1` |
| `maxStars` | No | integer | Keep reviews rated at most this many stars. Min `0`, max `5`, default `5`. | `2` |
| `spoilerFilter` | No | string (enum: `all`, `exclude`, `only`) | `all` — every review · `exclude` — spoiler-free only · `only` — spoilers only. Uses Goodreads' own spoiler flag, combined with AI detection when enabled. Default `all`. | `"exclude"` |
| `minLikeCount` | No | integer | Keep reviews with at least this many likes. Min `0`, default `0`. | `10` |
| `minCommentCount` | No | integer | Keep reviews with at least this many comments. Min `0`, default `0`. | `0` |
| `minTextLength` | No | integer | Keep reviews whose text is at least this many characters. Min `0`, default `0`. | `200` |
| `textOnly` | No | boolean | Skip rating-only reviews that have no written text. Default `false`. | `true` |
| `aiSpoilerDetection` | No | boolean | Off by default. When on, an AI model flags plot spoilers Goodreads' own flag missed; result lands in `aiIsSpoiler`. Requires `aiModel` and `aiApiKey`. | `false` |
| `aiModel` | No | string (enum, 26 values — see below) | The AI model to use for spoiler detection. Default `"claude-haiku-4-5"`. | `"gpt-4o-mini"` |
| `aiApiKey` | No | string (secret) | API key for the chosen provider. Falls back to the provider's own environment variable if left blank. | `"sk-..."` |
| `filtersAndOptions` | No | object | Sort order, review language and edition scope — see sub-fields below. | `{ "sortBy": "newest" }` |
| `proxyConfiguration` | No | object | Apify Proxy configuration. Prefilled `{"useApifyProxy": false}`; the Actor escalates automatically if a request is blocked. | `{"useApifyProxy": true}` |

**`aiModel` enum (26 values):** `claude-haiku-4-5` (Claude Haiku 4.5 — Anthropic, cheapest), `claude-sonnet-5` (Claude Sonnet 5 — Anthropic), `claude-opus-4-8` (Claude Opus 4.8 — Anthropic, best), `claude-fable-5` (Claude Fable 5 — Anthropic, most capable), `gpt-4o-mini` (GPT-4o mini — OpenAI, cheap), `gpt-4o` (GPT-4o — OpenAI), `gpt-4.1-mini` (GPT-4.1 mini — OpenAI), `gpt-4.1` (GPT-4.1 — OpenAI), `o3-mini` (o3-mini — OpenAI, reasoning), `o1` (o1 — OpenAI, reasoning), `gemini-2.0-flash-lite` (Gemini 2.0 Flash-Lite — Google, cheapest), `gemini-2.0-flash` (Gemini 2.0 Flash — Google, cheap), `gemini-1.5-pro` (Gemini 1.5 Pro — Google), `gemini-2.5-flash` (Gemini 2.5 Flash — Google), `gemini-2.5-pro` (Gemini 2.5 Pro — Google, best), `grok-2-latest` (Grok 2 latest — xAI), `grok-3-mini` (Grok 3 mini — xAI, cheap), `grok-3` (Grok 3 — xAI), `grok-beta` (Grok Beta — xAI), `deepseek-chat` (DeepSeek Chat/V3, cheap), `deepseek-reasoner` (DeepSeek Reasoner/R1), `sonar` (Sonar — Perplexity, cheap), `sonar-pro` (Sonar Pro — Perplexity), `sonar-reasoning` (Sonar Reasoning — Perplexity), `mistral-small-latest` (Mistral Small, cheap), `mistral-large-latest` (Mistral Large).

**`filtersAndOptions` sub-fields:**

| Sub-field | Type | Enum | Default |
| ----- | ----- | ----- | ----- |
| `sortBy` | string | `popular`, `newest`, `oldest` | `popular` |
| `languageCode` | string | `all`, `en`, `bn`, `fr`, `de`, `es`, `it`, `pt`, `ru`, `ja`, `ko`, `zh`, `ar`, `hi`, `nl`, `pl`, `tr`, `vi`, `id`, `th` | `all` |
| `reviewEdition` | string | `ALL` (whole work), `only_this_book` (this edition only) | `ALL` |

**Bring-your-own AI key, always.** `aiSpoilerDetection` is a second, optional detection layer on top of Goodreads' own spoiler flag — it does not ship with a bundled AI credential for any provider. To use it you supply your own `aiApiKey` for the provider matching your chosen `aiModel` (or the Actor falls back to reading that provider's own environment variable, e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`, `DEEPSEEK_API_KEY`, `PERPLEXITY_API_KEY`, `MISTRAL_API_KEY`, if you've set one on the Actor's environment). No provider in the list above is bundled, endorsed, or required — it is simply the set of models the Actor knows how to call. If no usable key is found, the run continues and `aiIsSpoiler`/`aiSpoilerConfidence` are left `null` rather than failing.

#### Example input

```json
{
  "urls": ["26032825"],
  "maxItems": 50,
  "minStars": 1,
  "maxStars": 2,
  "spoilerFilter": "exclude",
  "minLikeCount": 10,
  "minTextLength": 200,
  "filtersAndOptions": { "sortBy": "newest", "languageCode": "en" }
}
```

**Common pitfall:** `maxItems` counts **matching** reviews, not reviews scanned. A narrow combination — say `minStars: 5`, `spoilerFilter: "only"` and a high `minLikeCount` — can mean the Actor pages through most of a book's review list without ever reaching your `maxItems` target, simply because too few reviews satisfy every condition at once. That is expected behavior, not a bug: the run stops once it reaches `maxItems` matches, the review list ends, or an internal safety cap of 1,500 pages per book is hit (a ceiling this Actor imposes on itself so an extremely strict filter can't loop forever — it is not a limit Goodreads itself documents).

### ⬆️ Output

Typed, normalized JSON with the same 22 keys on every row. Rows are pushed to the dataset live as each matching review is found. Export as JSON, CSV or Excel, or read the dataset through the Apify API.

#### Example output

```json
{
  "bookUrl": "https://www.goodreads.com/book/show/26032825",
  "__typename": "Review",
  "id": "5972134816",
  "creator": {
    "id": "12345678",
    "imageUrlSquare": "https://images.gr-assets.com/users/1234567890p2/12345678.jpg",
    "isAuthor": false,
    "followersCount": 214,
    "__typename": "User",
    "textReviewsCount": 88,
    "name": "Alex Rivera",
    "webUrl": "https://www.goodreads.com/user/show/12345678-alex-rivera",
    "contributor": null
  },
  "recommendFor": null,
  "updatedAt": 1612000000000,
  "createdAt": 1610013600000,
  "spoilerStatus": false,
  "lastRevisionAt": 1612086400000,
  "text": "This book completely wrecked me in the best way. The pacing in the back half is relentless and the ending earns every bit of its weight.",
  "rating": 5,
  "shelving": {
    "shelf": { "name": "read", "webUrl": "https://www.goodreads.com/review/list/12345678?shelf=read", "__typename": "Shelf" },
    "taggings": [],
    "webUrl": "https://www.goodreads.com/review/list/12345678?shelf=favorites",
    "__typename": "Shelving"
  },
  "likeCount": 143,
  "commentCount": 12,
  "createdAtIso": "2021-01-07T10:00:00Z",
  "updatedAtIso": "2021-01-30T09:06:40Z",
  "lastRevisionAtIso": "2021-01-31T09:06:40Z",
  "textLength": 138,
  "isSpoiler": false,
  "aiIsSpoiler": false,
  "aiSpoilerConfidence": 0.04,
  "scrapedAt": "2026-07-26T12:00:00Z"
}
```

### 🎯 How do you filter and target specific reviews?

Filtering happens after the Actor fetches the review list for the book(s) named in `urls` — there is no site-wide Goodreads search here, so completeness depends on listing every book you care about.

Within a book's review list, four filter axes stack: **star range** (`minStars`/`maxStars`, both 0–5; `minStars: 0` explicitly also keeps unrated reviews, since an unrated review is treated as a `0` for this comparison), **spoiler mode** (`spoilerFilter: all/exclude/only`, evaluated against the combined Goodreads-flag-or-AI-flag verdict), **quality thresholds** (`minLikeCount`, `minCommentCount`, `minTextLength`, and `textOnly` — note `textOnly` and `minTextLength` are independent, ANDed conditions: `textOnly` alone only requires non-zero length, while any `minTextLength` above `0` already excludes zero-length reviews on its own, so setting both is only useful when you want a length floor above zero), and **volume** (`maxItems`, counted post-filter, per book).

Three real filter combinations:

```json
{ "urls": ["26032825"], "minStars": 1, "maxStars": 2, "minTextLength": 200 }
```

Complaint mining — 1–2★ reviews with at least 200 characters of substance.

```json
{ "urls": ["26032825"], "minStars": 5, "spoilerFilter": "exclude", "minLikeCount": 20, "textOnly": true }
```

Spoiler-free marketing quotes — 5★, well-liked, text reviews only.

```json
{ "urls": ["26032825"], "spoilerFilter": "only", "aiSpoilerDetection": true, "aiModel": "claude-haiku-4-5", "aiApiKey": "<your-key>" }
```

Spoiler audit — every review Goodreads' own flag *or* the AI layer considers a spoiler.

### How to extract Goodreads review data programmatically

Goodreads Review Scraper runs on Apify. Start it from the Apify Console, or call it through the Apify API using `apify_client` and your Apify token.

#### Python example

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("<YOUR_USERNAME>/goodreads-review-scraper-by-star-rating-spoiler-filter").call(
    run_input={
        "urls": ["26032825"],
        "maxItems": 50,
        "minStars": 4,
        "spoilerFilter": "exclude",
    }
)

for review in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(review["rating"], review["likeCount"], review["text"][:80])
```

Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request.

#### Export to spreadsheets or CRM

Export the dataset as CSV directly from the Apify Console or API. Flat fields (`rating`, `text`, `likeCount`, `commentCount`, `createdAtIso`) map straight to spreadsheet columns; nested fields like `creator` and `shelving` are flattened into dotted sub-columns (e.g. `creator/name`, `creator/webUrl`) so a reviewer's name and profile link still land in their own cells.

### Is it legal to scrape Goodreads reviews?

Scraping publicly accessible review data is broadly treated as permissible where no login or paywall is bypassed, and Goodreads Review Scraper reads only what Goodreads already shows any anonymous visitor on a book's reviews page.

Goodreads reviews sit somewhere between two categories: they are attached to a public product catalogue (the book), and each review is authored by a Goodreads username, not a private individual acting outside a public platform context. Because the primary output here is book and review data — not a directory of private people — the obligations that mainly apply are Goodreads' terms of service and database-and-reuse rules around a compiled catalogue, rather than a personal-data regime. If you plan to retain reviewer names or profile links at scale for anything beyond internal research, weigh that against applicable privacy law for the jurisdictions your users are in.

Consult legal counsel for commercial applications involving bulk storage or republication of review data.

### ❓ Frequently asked questions

#### What if a book has zero reviews matching my star and spoiler filters?

The run simply returns zero rows for that book and moves on to the next `url` — no error, no placeholder row. The Actor logs how many reviews it scanned versus how many matched, so you can tell "no reviews exist" apart from "none passed the filter" by loosening the filter and re-running.

#### Can I get likes and comments along with the review text?

Yes. Every row already includes `likeCount` and `commentCount` alongside `text` — there's no separate call or extra field to request; `minLikeCount` and `minCommentCount` let you threshold on them directly.

#### How accurate is the star rating and spoiler data?

The Actor returns the rating and spoiler flag exactly as they exist on Goodreads at request time — it doesn't recompute or estimate either. The optional AI spoiler layer (`aiIsSpoiler`) is a probabilistic second opinion on top of that, not a replacement for it, and ships its own `aiSpoilerConfidence` score so you can judge how much to trust a given call.

#### How many reviews can I get per run?

`maxItems` (default `20`, max `10000`) sets the matching-review target **per book**, and `urls` can hold as many books as you want in one run. The Actor paginates each book's review list until it hits that many matches, the list ends, or its own internal safety cap of 1,500 pages for that book is reached — whichever comes first. No Goodreads-side cap on reviews per book was found in the scraping logic itself.

#### How exactly does the star-rating filter work?

`minStars` and `maxStars` (0–5) form an inclusive range applied to every review's `rating`. The one nuance: `minStars: 0` (the default) is not just "no minimum" — it explicitly also keeps reviews with no star rating at all, since an unrated review is treated as `0` for the comparison. Raise `minStars` above `0` to exclude unrated reviews along with anything below your floor.

#### Does the AI spoiler detection use my own AI account?

Yes, entirely. `aiSpoilerDetection` is off by default, and turning it on requires you to supply `aiApiKey` for whichever provider your chosen `aiModel` belongs to (or have that provider's API key set as an environment variable on the Actor). The Actor does not include or resell access to any AI provider — it only knows how to format the request once you bring the credential.

#### Does Goodreads Review Scraper work with Claude, ChatGPT and AI agent frameworks?

Yes, on two levels: the Actor itself is callable as a standard HTTP endpoint through the Apify API by any agent framework, and separately, its optional `aiSpoilerDetection` feature can call Claude, GPT, Gemini, Grok, DeepSeek, Perplexity or Mistral models directly, using whichever key you provide.

#### How does Goodreads Review Scraper compare to other review scrapers on Apify?

Checked on the Apify Store on 2026-07-26, none of the nearest-ranked review scrapers target Goodreads specifically — `memo23/trustpilot-scraper-ppe`, `zen-studio/trustpilot-review-scraper` and `zen-studio/capterra-reviews-scraper` all cover other review platforms with similar star-rating filter mechanics. Compared on filter design, this Actor's distinguishing point is combining Goodreads' own spoiler flag with an optional, user-supplied AI spoiler-detection layer — a filter axis the Trustpilot and Capterra scrapers above don't document at all, since spoilers aren't a concept on those platforms.

#### Can I use Goodreads Review Scraper without a Goodreads account or API key?

Yes. No Goodreads login, cookie or developer API key is required — the Actor reads only public review data. The one credential you need is your own Apify token to run the Actor; an AI provider key is only needed if you turn on the optional `aiSpoilerDetection` feature.

### Conclusion

Goodreads Review Scraper turns a book's public review list into a filtered, typed dataset — star-rating range, spoiler mode, engagement thresholds and review length applied before a single row reaches you, with an optional AI layer for the spoilers Goodreads' own flag misses. It's built for publishers, marketers, researchers and AI teams who need a precise subset of reviews, not a full history to filter by hand. Start it from the Apify Console with a book URL or ID and let the matching reviews stream into your dataset.

# Actor input Schema

## `urls` (type: `array`):

Paste one or more Goodreads book links. **Tip:** you can paste just the number from the URL (e.g. `26032825`) — we’ll fix the link for you. 🌟

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

How many **matching** reviews to save **for each** book. The actor keeps paginating until it collects this many reviews that pass your filters (or the review list ends). Example: 50 here × 3 books = up to 150 rows. 📊

## `minStars` (type: `integer`):

Keep only reviews rated **at least** this many stars (1–5). `0` = no minimum (also keeps unrated reviews). Example: minStars=1, maxStars=2 → complaint mining. Default 0.

## `maxStars` (type: `integer`):

Keep only reviews rated **at most** this many stars (1–5). Example: minStars=5, maxStars=5 → 5★ only. Default 5.

## `spoilerFilter` (type: `string`):

**All** — keep every review · **Spoiler-free** — drop reviews flagged as spoilers · **Spoilers only** — keep only spoiler reviews. Uses Goodreads' own spoiler flag (plus AI spoiler detection below, when enabled).

## `minLikeCount` (type: `integer`):

Keep only reviews with **at least** this many likes. Example: 50 → only well-liked reviews. `0` = no minimum. Default 0.

## `minCommentCount` (type: `integer`):

Keep only reviews with **at least** this many comments. `0` = no minimum. Default 0.

## `minTextLength` (type: `integer`):

Keep only reviews whose text is **at least** this many characters. Example: 200 → skip one-liners. `0` = no minimum. Default 0.

## `textOnly` (type: `boolean`):

Skip rating-only reviews that have **no written text**. Default off.

## `aiSpoilerDetection` (type: `boolean`):

Off by default. When on, an AI model reads each candidate review and flags plot spoilers Goodreads' own flag missed. The result is added as `aiIsSpoiler` and is combined with the spoiler filter above. Requires an AI model + API key below.

## `aiModel` (type: `string`):

Provider auto-detected from the name: claude-*=Anthropic, gpt-*/o1/o3=OpenAI, gemini-*=Google, grok-*=xAI, deepseek-*=DeepSeek, sonar*=Perplexity, mistral-\*=Mistral. Cheaper mini/flash/haiku/lite models are recommended for classification.

## `aiApiKey` (type: `string`):

API key for the chosen provider. Falls back to the provider env var if left blank: ANTHROPIC\_API\_KEY / OPENAI\_API\_KEY / GEMINI\_API\_KEY / XAI\_API\_KEY / DEEPSEEK\_API\_KEY / PERPLEXITY\_API\_KEY / MISTRAL\_API\_KEY.

## `filtersAndOptions` (type: `object`):

🎛️ **Optional:** change sort order, language, and which edition reviews come from. Leave defaults for a quick start! 👇

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

🌐 **Optional.** Turn on Apify Proxy if you need extra reliability. If something blocks a run, we’ll try smarter routing automatically — you stay focused on the results. ✨

## Actor input object example

```json
{
  "urls": [
    "https://www.goodreads.com/book/show/26032825"
  ],
  "maxItems": 20,
  "minStars": 0,
  "maxStars": 5,
  "spoilerFilter": "all",
  "minLikeCount": 0,
  "minCommentCount": 0,
  "minTextLength": 0,
  "textOnly": false,
  "aiSpoilerDetection": false,
  "aiModel": "claude-haiku-4-5",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All scraped items in the Actor's default dataset.

# 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 = {
    "urls": [
        "https://www.goodreads.com/book/show/26032825"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("simpleapi/goodreads-review-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 = {
    "urls": ["https://www.goodreads.com/book/show/26032825"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("simpleapi/goodreads-review-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 '{
  "urls": [
    "https://www.goodreads.com/book/show/26032825"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call simpleapi/goodreads-review-scraper --silent --output-dataset

```

## MCP server setup

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