# Goodreads Book Scraper (`scrapers-hub/goodreads-book-scraper`) Actor

Goodreads Book Scraper searches books by keyword and returns title, author, cover image, average rating, publish year, edition count and URL. 📖 Great for building book catalogues, reading-list apps and publishing market research.

- **URL**: https://apify.com/scrapers-hub/goodreads-book-scraper.md
- **Developed by:** [Scrapers Hub](https://apify.com/scrapers-hub) (community)
- **Categories:** Developer tools, Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.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 Book Scraper – Extract Book Titles, Authors, Ratings & Editions

The **Goodreads Book Scraper** extracts structured book data from Goodreads search results, turning any list of keywords into a clean dataset of titles, authors, cover images, average ratings, review counts, publication years and edition counts. Give it a search term — an author name, a series, a genre, a subject — and it walks the Goodreads search listings and returns one row per book.

Goodreads holds one of the largest publicly visible catalogues of reader-generated book metadata anywhere: aggregate star ratings, rating volumes, cover artwork and edition histories for millions of titles. That combination makes it a natural reference set for publishing analysts, bookselling teams, library and collection developers, literary researchers and anyone building a book recommendation or discovery product. This Goodreads book scraper gives you that catalogue as machine-readable rows instead of paginated HTML.

The Actor is keyword-driven rather than URL-driven. You supply an array of search terms in `keywords` and a per-keyword cap in `maxItems`, and every result row carries the `keyword` that produced it — so a single run can cover dozens of topics while remaining perfectly attributable when you split the dataset afterwards.

***

### 📊 What Data Can You Extract with This Goodreads Book Scraper?

Each dataset item represents one book from the Goodreads search listings, grouped here into the categories most teams work with.

| Category | Fields | What it gives you |
| --- | --- | --- |
| **Book identity** | `title`, `url` | The book's listing title as shown on Goodreads and its canonical Goodreads URL for deduplication or linking. |
| **Authorship** | `author` | An object describing the book's author, including the author name and their Goodreads profile URL. |
| **Reader reception** | `rating` | An object holding the aggregate score and the number of ratings behind it — `average` and `count`. |
| **Publication data** | `publishYear`, `editions` | The publication year attached to the listing and how many editions Goodreads records for the work. |
| **Visual assets** | `cover` | The Goodreads-hosted cover thumbnail URL for the title. |
| **Run attribution** | `keyword` | The search term that produced this row, so multi-keyword runs stay traceable. |

The `rating` object is the field that carries the most analytical weight. A 4.49 average is meaningless on its own — a 4.49 across 3,467 ratings is a very different signal from a 4.49 across nine. Because `average` and `count` arrive together, you can apply a minimum-volume threshold and rank titles by weighted score rather than raw stars, which is how any credible bestseller or backlist analysis has to work.

***

### 🌟 Key Features of the Goodreads Book Scraper

| Feature | Description |
| --- | --- |
| 🔎 **Multi-keyword search** | Pass an array of search terms and the Actor runs each one in sequence, tagging every row with its source `keyword`. |
| 📈 **Rating and volume together** | The `rating` object returns both `average` and `count`, so you can weight scores by how many readers actually voted. |
| ✍️ **Structured author data** | `author` is returned as an object containing the author's name and their Goodreads profile URL, not just a flat string. |
| 🖼️ **Cover image URLs** | Every row includes a `cover` link to the Goodreads-hosted thumbnail, ready to embed in catalogues or dashboards. |
| 📅 **Publication metadata** | `publishYear` and `editions` support backlist analysis, reissue tracking and edition-history research. |
| 🎯 **Per-keyword result caps** | `maxItems` limits how many books are collected for each keyword, keeping runs predictable and bounded. |
| 🔗 **Canonical URLs** | The `url` field gives a stable Goodreads link per title for deduplication, enrichment or manual verification. |
| 🚦 **Automatic proxy rotation** | Requests are routed through rotating proxies internally, so retrieval stays reliable without any proxy setup on your side. |
| ⚡ **Lightweight HTML parsing** | The scraper parses listing HTML directly rather than driving a browser, which keeps runs fast and resource-light. |

***

### 🚀 Why Choose This Goodreads Book Scraper?

**Keyword-first workflow that matches how research actually starts.** Most book research begins with a subject, an author or a series name — not a list of URLs. Feeding `keywords` directly means you can go from "track everything in this genre" to a dataset in one step, with `keyword` preserved on every row so a fifty-term run splits cleanly afterwards.

**Rating data that supports real ranking.** Returning `average` and `count` as a single `rating` object is what makes the output usable for analysis rather than decoration. Weighted scoring, minimum-volume filters and confidence-adjusted rankings all need both numbers, and both arrive on every row.

**Structured author objects, not flattened strings.** Because `author` includes a Goodreads profile URL alongside the name, you can group a dataset by author reliably even when names collide, and follow the link when you need to enrich a record further.

**Bounded, predictable runs.** `maxItems` caps collection per keyword rather than globally, so adding a keyword adds a known amount of work. That predictability matters when the Goodreads book scraper runs on a schedule and feeds a downstream pipeline.

***

### 📥 Input

```json
{
  "keywords": ["shaker", "cyberpunk", "Ursula K. Le Guin"],
  "maxItems": 30
}
```

#### 🔧 Goodreads Book Scraper Input Fields

| Field | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `keywords` | array of strings | ✅ Yes | — (prefilled with `["shaker"]`) | List of search keywords to look up on Goodreads. Each keyword is searched separately. |
| `maxItems` | integer | No | `30` | Maximum number of books to scrape per keyword. |

#### 💡 Input Examples

**Single subject search**

```json
{
  "keywords": ["climate fiction"],
  "maxItems": 50
}
```

**Author and series tracking**

```json
{
  "keywords": ["Ursula K. Le Guin", "Earthsea", "Hainish Cycle"],
  "maxItems": 40
}
```

**Broad genre sweep with a small cap per term**

```json
{
  "keywords": ["cozy mystery", "space opera", "historical romance", "hard science fiction"],
  "maxItems": 15
}
```

***

### 📤 Output

```json
{
  "keyword": "shaker",
  "title": "Shaker (Blackwings MC, #5)Teagan Brooks",
  "url": "https://www.goodreads.com/book/show/43999679-shaker",
  "author": {
    "name": "Teagan Brooks",
    "url": "https://www.goodreads.com/author/show/1801..."
  },
  "cover": "https://i.gr-assets.com/images/S/compressed.photo.goodreads.com/books/1550046473i/43999679._SY75_.jpg",
  "rating": {
    "average": 4.49,
    "count": 3467
  },
  "publishYear": 2019,
  "editions": 5
}
```

#### 🧾 Goodreads Book Output Fields

| Field | Type | Description |
| --- | --- | --- |
| `keyword` | string | null | The search keyword that produced this item. |
| `title` | string | null | Title of the book as rendered in the Goodreads search listing. |
| `url` | string | null | Canonical Goodreads URL for the book. |
| `author` | object | null | Author details, including the author name and Goodreads author profile URL. |
| `cover` | string | null | URL of the book's cover image thumbnail. |
| `rating` | object | null | Aggregate rating object containing `average` (score) and `count` (number of ratings). |
| `publishYear` | integer | null | Year of publication associated with the listing. |
| `editions` | integer | null | Number of editions Goodreads records for the work. |

Note that the `title` string is taken from the search listing markup and can include the series designation and, in some listings, the author name appended directly after the title — as in the sample above. Normalising titles with a short regex or splitting on the author name from the `author` object is a sensible post-processing step before matching against another catalogue.

***

### 💻 How to Use the Goodreads Book Scraper (Step by Step)

#### Step 1: Choose Your Search Keywords

Everything begins with the `keywords` array. Goodreads search is broad, so the specificity of your terms determines the shape of your dataset. An author name pulls that author's catalogue plus adjacent titles; a series name pulls the entries in that series; a genre or theme pulls a wide, loosely ordered mix. If you are building a comparison set, use several tightly scoped keywords rather than one broad one — the per-row `keyword` field then acts as a ready-made category label.

#### Step 2: Set a Sensible Per-Keyword Cap

`maxItems` controls how many books are collected for each keyword and defaults to 30. Because the cap is per keyword rather than per run, total output scales with the number of terms you supply — ten keywords at 40 items each is a 400-row dataset. Start small when exploring a new subject area, confirm the results match what you expected, then raise the cap for the production run.

#### Step 3: Launch the Run

Start the Goodreads book scraper from the Apify Console or through the API. The Actor works through the keyword list in order, requesting search pages and parsing listing markup as it goes. No browser configuration or proxy setup is required — request routing is handled internally.

#### Step 4: Monitor Progress in the Log

The run log shows which keyword is being processed and how items are accumulating. If a keyword produces far fewer rows than your `maxItems` cap, that usually means Goodreads simply has fewer matching results for that term, not that anything failed. Very generic single-word keywords sometimes return results that drift away from your intent, which the log makes visible early.

#### Step 5: Inspect the Dataset

When the run completes, open the dataset and review a handful of rows. Check that `title` values look right, that `rating.count` is populated for the titles you care about, and that `publishYear` is present. Rows for very obscure or newly listed books may have `null` in some fields — Goodreads listings are not uniformly complete, and the scraper reports what the listing actually contains rather than guessing.

#### Step 6: Clean and Enrich the Data

Two clean-up steps pay for themselves immediately. First, normalise `title` by stripping the trailing author name and the parenthetical series suffix where present. Second, deduplicate on `url`, since a book matching two of your keywords will legitimately appear twice with different `keyword` values. From there, enrichment against ISBN databases or your own catalogue is straightforward using title and author together.

#### Step 7: Export or Automate

Export the dataset as JSON, CSV or Excel, or pull it through the API into your own system. To track a subject over time, schedule the run on a weekly or monthly cadence and compare `rating.count` across snapshots — growth in rating volume is one of the clearest available proxies for a title gaining traction.

***

### 🔌 API Access & Integrations

Trigger the Goodreads book scraper and receive the dataset in a single synchronous call:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapers-hub~goodreads-book-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "keywords": ["cyberpunk", "solarpunk"],
    "maxItems": 25
  }'
```

The equivalent using the official Python client:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")

run_input = {
    "keywords": ["Ursula K. Le Guin", "Earthsea"],
    "maxItems": 40,
}

run = client.actor("scrapers-hub/goodreads-book-scraper").call(run_input=run_input)

for book in client.dataset(run["defaultDatasetId"]).iterate_items():
    rating = book.get("rating") or {}
    print(book["title"], rating.get("average"), rating.get("count"), book["publishYear"])
```

The dataset can also be routed into Zapier, Make, Google Sheets or Slack, or delivered to your own service through Apify webhooks the moment a run finishes.

***

### 💡 Best Use Cases for Goodreads Book Data

#### 📖 Publishing and Backlist Analysis

Combining `publishYear` with `rating.count` shows which older titles are still accumulating readers and which have gone quiet. Publishers use exactly this shape of data to decide reissue candidates, and the `editions` count adds a second signal — a work with many editions has typically had sustained commercial life.

#### 🛒 Bookselling and Catalogue Curation

Retailers and independent bookshops can pull a genre's top titles by keyword, rank them using `rating.average` filtered by a minimum `rating.count`, and use `cover` plus `url` to build a shelf-talker list or an online curated collection with artwork already attached.

#### 🎯 Competitive Title Research for Authors

Authors and agents researching a comp-title list can search their subgenre, then sort by `rating.count` to find the titles readers actually engage with. The `author` object's profile URL makes it easy to follow up on prolific names in the same space.

#### 🤖 Building Book Recommendation Datasets

Recommendation engines need a seed corpus with identity, popularity and metadata. `title`, `author`, `publishYear`, `rating` and `url` cover the essentials, and running many `keywords` in one job builds a genre-labelled corpus where the `keyword` field doubles as a weak category label.

#### 📚 Library Collection Development

Acquisition librarians evaluating a subject area can scan `rating.average` and `rating.count` across a keyword sweep to gauge reader demand, then check `editions` and `publishYear` to judge availability and edition stability before ordering.

#### 📊 Literary and Cultural Research

Researchers studying genre trends, reception patterns or publication cycles can build longitudinal datasets by running the same keyword set on a schedule. Tracking how `rating.count` and `rating.average` move over months gives a quantitative view of reception that is otherwise hard to obtain.

#### 🖼️ Cover and Metadata Enrichment

If you already hold a book list without artwork or publication years, a keyword run using titles or author names returns `cover`, `publishYear` and `url`, filling the gaps in an existing catalogue without manual lookup.

***

### ⚙️ Tips for Better Goodreads Scraping Results

- **Use specific keywords.** "Le Guin Earthsea" returns a tighter, more relevant set than "fantasy". Broad single-word terms drift quickly on Goodreads search.
- **Keep `maxItems` proportionate.** Search relevance drops the deeper you go into results. For most keywords, the first 20–40 items carry the signal; beyond that you collect noise.
- **Deduplicate on `url`.** Overlapping keywords will surface the same book more than once, which is expected. The canonical URL is the reliable join key, not the title.
- **Normalise `title` before matching.** Listing titles can carry series suffixes and a trailing author name, so strip those before comparing against ISBN data or an internal catalogue.
- **Filter by `rating.count` before ranking.** Apply a minimum rating volume — a few hundred is a reasonable floor for mainstream genres — so tiny-sample averages do not dominate your top results.
- **Run on a schedule for trend work.** A single snapshot tells you position; repeated snapshots tell you direction. Rating volume growth is the most informative delta to track.

***

### 🛠️ Troubleshooting

**Why did a keyword return fewer books than my `maxItems` setting?**
`maxItems` is an upper bound, not a target. If Goodreads search has fewer matching listings for that term, the Actor returns everything it found. Broaden the keyword or check its spelling if the count looks unexpectedly low.

**Why does the `title` field include the author name?**
The title is taken from the Goodreads search listing markup, where the author name can be rendered adjacent to the title without a separator. Use the `name` value inside the `author` object to strip it during post-processing.

**Why are `publishYear` or `editions` null on some rows?**
Not every Goodreads listing exposes those values, particularly for very new, self-published or sparsely catalogued titles. The scraper reports what the listing contains rather than inferring missing values.

**Why do the same books appear multiple times?**
Each keyword is searched independently, so a title matching two of your terms produces two rows with different `keyword` values. Deduplicate on `url` if you need a unique title list.

**Why did a run return no results at all?**
Check that `keywords` is a non-empty array of strings. If the input is valid and the run still returns nothing, Goodreads may have been temporarily unresponsive — rerun the job, since proxy rotation and retries are handled automatically.

***

### ❓ Frequently Asked Questions About Goodreads Book Scraping

**What is the Goodreads Book Scraper?**
It is an Apify Actor that searches Goodreads by keyword and returns structured book data — title, author, cover, rating, publication year, edition count and canonical URL — as dataset rows.

**Do I need a Goodreads account or API key?**
No. The scraper reads publicly visible Goodreads search listings. No login credentials or Goodreads API keys are required.

**Can I scrape by book URL instead of keyword?**
The input schema is keyword-based: `keywords` and `maxItems` are the only fields. To target a specific book, search for its exact title, optionally combined with the author name.

**How many books can I collect in one run?**
`maxItems` caps results per keyword and defaults to 30. Total output is roughly the number of keywords multiplied by that cap, subject to how many results Goodreads actually returns.

**What exactly is in the `rating` field?**
It is an object with `average` — the aggregate star score — and `count`, the number of ratings that produced it. Both are needed for any credible ranking.

**What is inside the `author` object?**
The author's name and their Goodreads author profile URL, which lets you group by author reliably and follow up for further detail.

**Does the scraper return full book descriptions or reviews?**
No. The output fields are limited to the listing-level data documented above: keyword, title, url, author, cover, rating, publishYear and editions.

**Can I get ISBNs from this Goodreads book scraper?**
ISBN is not among the output fields. Use `title` and the `author` name to match against an ISBN database if you need that identifier.

**Does it handle proxies?**
Yes. Requests are routed through rotating proxies automatically. Proxy configuration is not exposed as an input field, so there is nothing to set up.

**Can I schedule recurring runs?**
Yes. Apify's scheduler supports any cron expression, which makes it straightforward to snapshot the same keyword set weekly or monthly for trend analysis.

**What export formats are available?**
Datasets export as JSON, JSONL, CSV, Excel, XML or HTML from the Apify Console, and can be read directly through the API.

**Is scraping Goodreads legal?**
This Actor accesses publicly available listing pages. How you use the resulting data is your responsibility, including compliance with Goodreads' terms of service and any copyright applying to titles, descriptions and cover artwork.

**Why is the cover image a small thumbnail?**
The `cover` URL is the image Goodreads serves in its search listings, which is a compressed thumbnail. That is what the listing markup exposes.

**Can I filter results by rating or publication year in the input?**
Not at input time — the schema offers only `keywords` and `maxItems`. Filter on `rating.average`, `rating.count` or `publishYear` after export; the fields are all present in the dataset.

**How do I track whether a book is gaining popularity?**
Run the same keywords on a schedule and compare `rating.count` between snapshots. Growth in rating volume is the clearest signal of a title picking up readers.

***

### 🆘 Support & Feedback

If you hit a bug, an unexpected empty result, or a parsing issue, open a ticket in the **Issues** tab of this Actor on Apify. Reports filed there are tracked and answered directly, and they are the fastest route to a fix.

Need something custom — additional Goodreads fields, a different input mode, or a pipeline that feeds this data straight into your own systems? Write to **scraperhubapi@gmail.com** with a description of the outcome you need.

If the Goodreads Book Scraper is useful to you, a review on its Apify page is genuinely appreciated. Ratings and written feedback directly influence which improvements are prioritised.

***

### ⚖️ Disclaimer

This Goodreads book scraper collects only publicly accessible information from Goodreads search listings. It does not log in, does not access private shelves, reading activity or account settings, and does not attempt to circumvent authentication or access controls.

You are responsible for how you use the extracted data. Book titles, cover artwork, descriptions and author information may be protected by copyright or other rights held by publishers, authors and Goodreads itself, and redistributing that material can carry obligations that this Actor does not resolve for you. Review Goodreads' terms of service before deploying the scraper at scale or in a commercial product.

Where the data you collect includes personal information — author names and profile URLs, for example — you act as the data controller under the GDPR, the UK GDPR, the CCPA and other applicable privacy laws. That includes having a lawful basis for processing, honouring data-subject requests, and applying sensible retention limits.

To ask a question about data handling, or to request removal of data associated with your runs, contact **scraperhubapi@gmail.com**.

# Actor input Schema

## `keywords` (type: `array`):

List of search keywords to look up on Goodreads.

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

Maximum number of books to scrape per keyword.

## Actor input object example

```json
{
  "keywords": [
    "shaker"
  ],
  "maxItems": 30
}
```

# Actor output Schema

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

Records scraped by Goodreads Book Scraper, stored in the run'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 = {
    "keywords": [
        "shaker"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapers-hub/goodreads-book-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 = { "keywords": ["shaker"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapers-hub/goodreads-book-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 '{
  "keywords": [
    "shaker"
  ]
}' |
apify call scrapers-hub/goodreads-book-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapers-hub/goodreads-book-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/3bdaSQug7JzX8ttjn/builds/q0cjZOcHQFTkP5SHo/openapi.json
