# Goodreads Scraper (`dami_studio/goodreads-books-scraper`) Actor

Goodreads scraper: search by title, author or keyword and get one row per book with title and series, author, average rating, how many people rated it, the listed edition's year, full-size cover and link. Up to 20 terms a run. No account. $1.14 per 1,000 books.

- **URL**: https://apify.com/dami_studio/goodreads-books-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** E-commerce, Other
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.11 / 1,000 books

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 for a title, an author or a plain keyword, search results back as rows**: title,
author, average rating, how many people rated it, the year of the edition Goodreads lists, the
full-size cover and the book link.

What it gives you is the search listing, not the book page. That means ratings and counts, and it
means no blurb, no ISBN, no genres and no reviews. The listing also does not show a book's first
publication year or its edition count, so those two columns are always empty.

| | |
|---|---|
| **Input** | Titles, authors or keywords, up to 20 per run |
| **Output** | One row per book: title with its series, author, average rating, ratings count, listed edition's year, cover, link |
| **Ceiling** | 2,000 books per run |
| **Account needed** | None, no API key, no browser |
| **Price** | $1.14 per 1,000 books on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 4,300 |

### 🔍 What Goodreads Scraper does

It runs each of your search terms and pages through the results until it has your share of the row
budget. The budget is split evenly between terms, so four terms and 40 rows gives you ten of each
rather than 40 of the first one.

`searchField` changes the answer more than it looks. Searching `christie` as an author gives you
Agatha Christie's novels. The same word as a title gives you books with Christie in the name. Those
are genuinely different result sets, not the same set reordered.

Two things it refuses to bill you for. A book Goodreads lists without a rating is skipped, because a
row with no rating is the thing you came for missing. And "Goodreads looked and found nothing" is a
real answer, kept separate from "the request never got through", so a term that genuinely has no
matches is not charged as rows.

Paging stops at 50 pages per term.

### 📋 What data you get from each Goodreads book

| What you get | Field |
|---|---|
| Goodreads' id for the book, and its link | `bookId`, `url` |
| The title as listed, series marker included | `title` |
| The author | `author` |
| Average rating and how many ratings it rests on | `avgRating`, `ratingsCount` |
| The year of the edition the listing shows | `editionPublishedYear` |
| The full-size cover | `coverUrl` |
| Which term found it, how, and at what position | `query`, `searchField`, `rank` |
| When it was read | `scrapedAt` |

### ▶️ How to scrape Goodreads books

1. Open [Goodreads Scraper](https://apify.com/dami_studio/goodreads-books-scraper) and click
   **Try for free**.
2. Put one term per line into **Search terms**.
3. Pick **Search in** if you want author-only or title-only matching.
4. Set **Maximum rows**. Start at 20 to see the shape of the output.
5. Click **Start**, then download the dataset as JSON, CSV or Excel, or read it from the Apify API.

Start it with no search terms and you get one labelled sample row instead, which is not charged.

### 💰 How much does it cost to scrape Goodreads?

**$1.14 per 1,000 books** on the Free plan. Paid plans pay a little less: $1.13 on Starter, $1.12 on Scale and $1.11 on Business. On the free plan, the $5 Apify
gives you each month covers about 4,300 books.

You pay per book row delivered. Duplicates across terms, books skipped for having no rating, the
sample row, diagnostic rows and a term that finds nothing are not charged.

### 📥 What you give it

```json
{
  "queries": ["dune", "project hail mary"],
  "searchField": "all",
  "maxItems": 20
}
```

| Field | Default | What it is |
|---|---|---|
| `queries` | none, the form starts with `dune` and `project hail mary` | Titles, author names or keywords, one per line, up to 20. Each is searched in turn. |
| `searchField` | `all` | `all` is Goodreads' own default and mixes titles, authors and series. `title` and `author` return different sets. |
| `maxItems` | `20` | Total books across every term, up to 2,000, shared evenly between them. This is also your spending cap. |
| `proxyUrls` | none | Leave it empty unless you have your own servers you want the traffic to leave through, one URL per line as `http://user:pass@host:port`. |

### 📤 What you get back

A real row from a run on 2 October 2026:

```json
{
  "ok": true,
  "charged": true,
  "recordType": "book",
  "query": "dune",
  "searchField": "all",
  "rank": 1,
  "bookId": "44767458",
  "title": "Dune (Dune, #1)",
  "author": "Frank Herbert",
  "avgRating": 4.29,
  "ratingsCount": 1714417,
  "publishedYear": null,
  "editionPublishedYear": 2019,
  "editionsCount": null,
  "url": "https://www.goodreads.com/book/show/44767458",
  "coverUrl": "https://m.media-amazon.com/images/S/compressed.photo.goodreads.com/books/1555447414i/44767458.jpg",
  "scrapedAt": "2026-10-02T23:23:22.478Z"
}
```

| Field | How to read it |
|---|---|
| `bookId` | Goodreads' own id for the work. Stable, so use it to dedupe across runs. |
| `avgRating`, `ratingsCount` | The average out of five and how many ratings it is built on. Every delivered row has both. |
| `editionPublishedYear` | The year the edition Goodreads lists was published. For a reissue that is the reissue's year (2019 for the Dune above), not the year the book first came out. |
| `publishedYear`, `editionsCount` | Always `null`. The search listing does not show the first publication year or the edition count. The columns stay so anything built on them keeps working. |
| `title` | As listed, series marker and all, like `Dune (Dune, #1)`. |
| `coverUrl` | The full-size cover image on Goodreads' own servers. |

### 🧾 Reading the output

Three kinds of row can land in your dataset, and `recordType` tells them apart.

| Row | How to spot it | Billed |
|---|---|---|
| A book | `recordType: "book"` | yes |
| The sample row | `_sample: true`, `recordType: "sample"` | no |
| A diagnostic | `_diagnostic: true` and an `errorCode` | no |

**The `charged` field is a label, not a receipt.** It is stamped when the row is built, before
billing happens, so read it the way you read `recordType`: it separates real books from sample and
diagnostic rows, nothing more.

| Code | What it means |
|---|---|
| `NO_RESULTS` | Goodreads has nothing matching that term. A real answer, and not charged. |
| `END_OF_RESULTS` | Goodreads ran out of books for that term before its share of `maxItems`, so fewer came back. The row says how many. Not charged. |
| `PAGE_LIMIT` | The term reached 50 pages, the most the run reads for one term, before its share of `maxItems`. Not charged. |
| `ROW_INCOMPLETE` | A book was listed without a rating, so it was skipped rather than delivered. Not charged. |
| `TIME_BUDGET` | The run ran out of time before reaching a term, or partway through one. Everything already delivered is complete. |
| `NOT_SERVED` | This search was not served this run. Re-run it later. Books the term delivered before that page are in the results. Runs before 13 October 2026 wrote `BLOCKED` here. |
| `NETWORK` | Goodreads was unreachable. Re-run it. |
| `PROXY_INPUT_ADJUSTED` | A connection setting in your input was not usable, so the run used its own. |
| `CHARGE_ERROR` | Billing could not be recorded for some rows. Rare, and worth telling us about. |

One bad term never stops the others, and a diagnostic row never fails the run. The one exception is an unexpected error on our side before anything came back: that run ends as failed, with an `UNEXPECTED_ERROR` row.

### 💡 What people use it for

- Checking how an author's back catalogue is rated, by searching them with `searchField` set to
  `author` and sorting on `ratingsCount`.
- Building a shortlist for a reading list or a shop buy, where `avgRating` alone is misleading and
  `ratingsCount` is the number that matters.
- Watching how a new release gathers ratings week by week, scheduled and deduped on `bookId`.
- Resolving a messy list of titles to real Goodreads links and ids before enriching them elsewhere.

### 🚧 What it does not do

- **Search results only.** No blurb, no ISBN, no genres, no page count, no publisher.
- **No reviews and no reviewer data.** Only the rating average and the count.
- **No user shelves, no lists, no author pages**, and nothing that needs a Goodreads login.
- **Books with no rating are skipped**, so a very obscure or just-announced title may not appear.
- **50 pages per term.** A single term will not yield thousands of
  distinct books however high you set `maxItems`.
- **No first publication year and no edition count.** `editionPublishedYear` is the year of the
  edition the listing shows, which for a classic can be decades after it first came out.
- **Relevance order is Goodreads'**, applied before paging, so asking for 20 gives you the 20 it
  serves first.
- It is not an official Goodreads tool. Dami's Studio is independent and is not affiliated with or endorsed by Goodreads or Amazon, or by any other company named on this page.

### 🧭 Which reference scraper do you need?

| If you want | Use |
|---|---|
| Wikipedia search results or article text | [Wikipedia Scraper](https://apify.com/dami_studio/wikipedia-scraper) |
| Wikidata Q-ids, aliases and claims, by name or by ID | [Wikidata Scraper](https://apify.com/dami_studio/wikidata-scraper) |
| Book metadata by keyword, title or ISBN | [Books Scraper](https://apify.com/dami_studio/books-scraper) |
| Goodreads ratings and rating counts from a book search | This one |
| Books, audio, film and software held on archive.org | [Internet Archive Scraper](https://apify.com/dami_studio/internet-archive-scraper) |
| Papers with readable abstracts and citation counts | [OpenAlex Scraper](https://apify.com/dami_studio/openalex-scraper) |
| Wikipedia, arXiv and OpenAlex searches as tools for an AI agent | [Research MCP Server](https://apify.com/dami_studio/research-mcp-server) |

### ❓ Questions people ask

#### Do I need a Goodreads account or an API key?

Neither. The search listing needs no login.

#### Can I get the book description or the ISBN?

Not from here. [Books Scraper](https://apify.com/dami_studio/books-scraper) covers ISBN, publisher and
page count.

#### Why do author and title searches give different books?

Because they are different searches. `author` matches the writer, `title` matches words in the name
of the book.

#### Why did I get fewer rows than I asked for?

Either the terms ran out of distinct results, or some books had no rating and were skipped. The
diagnostic rows say which.

#### Why does the rating count differ from the Goodreads page?

Ratings keep arriving every day, so a row is a snapshot. Compare it with the page at the time in
`scrapedAt`, not with what the page says an hour later.

#### Can I call it from code or connect it to an AI assistant?

The [API tab](https://apify.com/dami_studio/goodreads-books-scraper/api/python) has code for Python, JavaScript and
the CLI. MCP clients like Claude and ChatGPT use
`https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/goodreads-books-scraper`; coding assistants can load
[our skill
file](https://api.apify.com/v2/key-value-stores/BTBHU3DdiuoF4JOsK/records/apify-finance-sports-entertainment-SKILL.md).
All three bill your Apify account at the same price.

#### Is scraping Goodreads legal?

These are public catalogue pages. Author names are personal data under GDPR, so have a reason for
collecting them. Apify's write-up on
[the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) is a good starting
point, and we are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Send the run ID and the search term you used. The
`errorCode` on the diagnostic row usually names the problem on its own.

# Actor input Schema

## `queries` (type: `array`):

Book titles, author names or plain keywords to search Goodreads for. Up to 20 per run. Each term is searched in turn until the run reaches the row limit below.

## `searchField` (type: `string`):

Which part of a book to match on. 'Everything' is Goodreads' own default and mixes titles, authors and series. 'Title only' and 'Author only' return genuinely different sets - searching 'christie' as an author gives you Agatha Christie's novels, as a title it gives books with Christie in the name.

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

Total number of books to return across all search terms. The budget is shared evenly between the terms, so four terms and 40 rows gives you ten of each. Keep it low while you are testing - you pay per row.

## `proxyUrls` (type: `array`):

Leave this empty for a normal run. Fill it in only if you want the traffic to leave through proxy servers you already pay for, one URL per line, in the form http://user:pass@host:port.

## Actor input object example

```json
{
  "queries": [
    "dune",
    "project hail mary"
  ],
  "searchField": "all",
  "maxItems": 20
}
```

# Actor output Schema

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

Every row in the default dataset: query, title, author, avgRating, ratingsCount, publishedYear, url, coverUrl, editionsCount, bookId, searchField, rank. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# 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 = {
    "queries": [
        "dune",
        "project hail mary"
    ],
    "searchField": "all",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/goodreads-books-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 = {
    "queries": [
        "dune",
        "project hail mary",
    ],
    "searchField": "all",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/goodreads-books-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 '{
  "queries": [
    "dune",
    "project hail mary"
  ],
  "searchField": "all",
  "maxItems": 20
}' |
apify call dami_studio/goodreads-books-scraper --silent --output-dataset

```

## MCP server setup

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