Goodreads Review Scraper avatar

Goodreads Review Scraper

Pricing

from $5.99 / 1,000 results

Go to Apify Store
Goodreads Review Scraper

Goodreads Review Scraper

πŸ“š Goodreads Review Scraper extracts book reviews at scale β€” ratings, review text, dates, reviewer profiles, helpful votes & shelves. πŸ”Ž Clean, structured data for sentiment analysis & insights. πŸš€ Perfect for authors, publishers, marketers & researchers.

Pricing

from $5.99 / 1,000 results

Rating

0.0

(0)

Developer

Scrapier

Scrapier

Maintained by Community

Actor stats

1

Bookmarked

9

Total users

1

Monthly active users

4 days ago

Last modified

Share

Goodreads Review Scraper β€” Reviews, Ratings and Language Filters

Goodreads Review Scraper pulls every public review from any Goodreads book page β€” star rating, full review text, reviewer profile, likes, comments, shelves and both epoch and ISO-8601 timestamps β€” and streams them to your dataset as clean JSON, one row per review. It is built for review-mining, sentiment analysis, and publishing research, and it is the only Goodreads review Actor in this comparison that lets you filter results by review language and by edition scope before they hit your dataset. Every response is structured JSON, ready to pass to an LLM, load into a spreadsheet, or index for search. Paste a book URL or bare numeric ID and start collecting in seconds.

πŸ“š What is Goodreads Review Scraper?

Goodreads Review Scraper queries the public Goodreads GraphQL API for one or more books and returns every review left on that book's page as a structured record β€” rating, review text, reviewer profile, engagement counts, shelving tags and timestamps. Unlike the general-purpose Goodreads scrapers in this category, it also lets you narrow the review set by language (20 languages) and by edition scope (the whole work vs. one specific edition) before results are collected, so you don't have to filter noise out after the fact. No Goodreads account, login or cookie is required β€” only publicly visible reviews are read.

  • Extracts star rating, full review text, and spoiler status for every review on a book
  • Extracts a reviewer profile object per review β€” name, profile URL, avatar, follower count, total reviews written, author flag
  • Extracts engagement counts β€” like count and comment count per review
  • Extracts shelving and tag data β€” the shelf a reviewer filed the book under, plus any tags attached to that shelving
  • Returns three timestamp fields (created, updated, last revision), each as both raw epoch-milliseconds and a human-readable ISO-8601 UTC string
  • Query controls exposed in the input: sort order (popular / newest / oldest), review language (20 codes or all), and edition scope (whole work or this edition only)

Under the hood, reviews are paged 30 at a time from Goodreads' GraphQL API until maxItems is reached or the book runs out of reviews to return, with a short randomized pause between pages so the request pattern doesn't look scripted. None of this needs configuring β€” it's the Actor's internal pagination, not an input you set.

πŸ“Š What data can you get with Goodreads Review Scraper?

The Actor returns two nested result types inside every dataset row: the review itself, and the reviewer who wrote it, with an optional third β€” shelving and tags β€” attached when Goodreads has that data for the review. As a Goodreads reviews API, a book review data export, or the review layer of a larger review-mining pipeline, the shape below is what every row looks like regardless of which book or language you queried.

Result TypeExtracted FieldsPrimary Use Case
Reviewid, rating, text, spoilerStatus, recommendFor, likeCount, commentCount, createdAt/createdAtIso, updatedAt/updatedAtIso, lastRevisionAt/lastRevisionAtIso, shelving, __typenameSentiment analysis, engagement tracking, review-volume monitoring
Reviewer profile (creator)id, name, webUrl, imageUrlSquare, isAuthor, followersCount, textReviewsCount, contributorIdentifying influential reviewers, distinguishing author reviews from reader reviews
Shelving & tagsshelving.shelf.name, shelving.shelf.webUrl, shelving.taggings[].tag.name, shelving.taggings[].tag.webUrlGenre and shelf-based categorization of reviews

Language & Edition-Filtered Reviews

Every run accepts a filtersAndOptions object with three real query controls read straight from the input schema: sortBy (which order reviews are paged in), languageCode (restrict results to one of 20 languages, or leave unfiltered), and reviewEdition (collect reviews for the whole work, or only the specific edition the URL points to). None of the three competing Goodreads review scrapers checked for this README document a language or edition filter β€” they return whatever the book page returns, unfiltered. Here, the filter is applied server-side by Goodreads' own GraphQL API before a single row reaches your dataset, so you pay for the reviews you actually wanted:

{
"sortBy": "newest",
"languageCode": "es",
"reviewEdition": "only_this_book"
}

Reviewer Profiles

Every review row carries a nested creator object with the reviewer's Goodreads ID, display name, profile URL, avatar image, follower count, and total review count, plus an isAuthor flag that Goodreads sets when the reviewer is a published author. There's also a contributor object, populated only when Goodreads links that reviewer to a bibliography β€” it carries a contributor id and a works.totalCount figure for reviewers who are themselves published writers. This is enough to separate casual reader reviews from author-to-author reviews, or to rank reviewers by followersCount when you're looking for reviews with outsized reach. If Goodreads returns no creator for a review, the field is null rather than an empty object, so downstream code can check for that case instead of guessing at a placeholder name.

Shelving & Tags

Reviews are frequently attached to a shelf β€” Goodreads' term for the list a reader filed the book under, like read, currently-reading, or a custom shelf name β€” plus any tags the reviewer added to that shelving. The shelving object carries the shelf's name and webUrl, and a taggings array of {tag: {name, webUrl}} entries for each tag attached. Not every review has shelving data attached; when it's absent, shelving is null rather than an object with empty fields. This is the field set to use for genre or theme analysis across a book's review base β€” grouping reviews by shelf name surfaces how readers are actually categorizing a title, independent of Goodreads' own genre metadata.

Why not build this yourself?

Goodreads does not publish a supported endpoint for pulling review data at scale, and the book pages themselves are not a simple HTML scrape. The source shows why: Goodreads serves its book pages behind an AWS WAF JavaScript challenge β€” a plain HTTP request gets back a 202 status with an x-amzn-waf-action: challenge header instead of a page. Reviews actually live behind a separate GraphQL API (AWS AppSync) that isn't WAF-protected, but reaching it means resolving a per-book internal resource ID first, holding a valid x-api-key, and handling the case where that key rotates. This Actor resolves the resource ID with a lightweight getBookByLegacyId call, carries a stable public API key, and falls back to re-extracting a fresh key from the page's JS bundles if the stable one is ever rejected β€” then retries and escalates through direct, Apify datacenter, and residential proxy tiers if a request comes back blocked. Each request gets up to 3 attempts with a growing pause between them (roughly 1.2s, then 2.4s, then 3.6s) before the Actor gives up on that request entirely, and proxy escalation only kicks in before the first review of a book has been saved β€” so a block mid-page never causes a duplicate row. Reproducing that logic, and keeping it working as Goodreads' WAF rules and JS bundle structure change over time, is the ongoing maintenance cost of building it yourself instead of running a maintained Actor.

What is the difference between book metadata scraping and review scraping?

Book metadata scraping returns facts about the book itself β€” title, ISBN, cover image, average rating, page count β€” pulled once per book. Review scraping returns the individual opinions readers left about that book β€” one row per review, with its own rating, text, and reviewer. The two are easy to conflate because some Goodreads scrapers bundle both into one Actor and one output shape.

Goodreads Review Scraper does the second job only, and does it in depth: it does not return book title, ISBN, cover image, or aggregate rating counts. Every row in the dataset is a Review object β€” nested under a creator (the reviewer) and, optionally, a shelving object (the shelf and tags that reviewer filed the book under). If your workflow needs book-level facts (ISBN, average rating, page count) alongside reviews, you'll need a second, metadata-focused Actor for that half of the job β€” this one is built specifically to go deep on the review layer: sort order, language, edition scope, and full reviewer context per review.

In practice, this matters most when you're deciding which Actor to run first. If the question is "what books exist matching this title/ISBN/author," that's a metadata or search question. If the question is "what are readers actually saying about a book I've already identified," that's this Actor's job β€” point it at the book URL you already have and it starts paging reviews immediately, without a discovery step in between.

How to scrape Goodreads reviews with Goodreads Review Scraper?

  1. Open Goodreads Review Scraper on the Apify Store and click Try for free (or Run, if you already have it saved to your account).
  2. Enter one or more book links in urls β€” the only required input field. A bare numeric ID (e.g. 26032825) works too; it's rewritten to a full goodreads.com/book/show/<id> URL automatically before the run starts.
  3. Set the real query controls: maxItems for how many reviews to pull per book (up to 10,000), and, inside filtersAndOptions, sortBy, languageCode, and reviewEdition for which reviews you get back and in what order.
  4. Click Start. Reviews populate the Output tab in real time β€” you don't have to wait for the run to finish to start reading rows.
  5. Export the finished dataset as JSON, JSONL, CSV, Excel, HTML or XML from the Storage tab, or pull it programmatically with the Apify API and apify_client once the run status turns SUCCEEDED.
{
"urls": ["https://www.goodreads.com/book/show/26032825"],
"maxItems": 50,
"filtersAndOptions": { "sortBy": "newest", "languageCode": "en", "reviewEdition": "ALL" }
}

How to run multiple queries in one job

urls accepts an array, so a single run can collect reviews for as many books as you list β€” maxItems applies per book, not to the run as a whole (50 in maxItems Γ— 3 books in urls yields up to 150 rows). Books are processed one after another within the run; there is no separate concurrency setting to configure.

⬇️ Input

Every field below is read directly from .actor/actor.json's input schema β€” names, types, defaults and constraints are exact. urls is the only required field; everything else has a working default, so the fastest way to a first run is pasting a book link and clicking Start. filtersAndOptions is a collapsed, optional section in the Console UI β€” open it only when you need to change sort order, language, or edition scope away from the defaults.

ParameterRequiredTypeDescriptionExample Value
urlsYesarray (string list)One or more Goodreads book links. A bare numeric ID (e.g. 26032825) is accepted and rewritten to https://www.goodreads.com/book/show/<id> automatically.["https://www.goodreads.com/book/show/26032825"]
maxItemsNointegerHow many reviews to collect for each book in urls. Minimum 1, maximum 10000, default 20.50
filtersAndOptionsNoobjectOptional filter block β€” leave it out for the defaults below.see nested rows
filtersAndOptions.sortByNostring (enum)popular (most helpful first, default), newest, or oldest."newest"
filtersAndOptions.languageCodeNostring (enum)all (no filter, default) or one of 19 language codes: en, bn, fr, de, es, it, pt, ru, ja, ko, zh, ar, hi, nl, pl, tr, vi, id, th."es"
filtersAndOptions.reviewEditionNostring (enum)ALL β€” reviews for the whole work, across every edition (default). only_this_book β€” reviews for the exact edition the URL points to."only_this_book"
proxyConfigurationNoobject (proxy editor)Standard Apify Proxy configuration block. Prefilled {"useApifyProxy": false}.{"useApifyProxy": false}

Example JSON input:

{
"urls": [
"https://www.goodreads.com/book/show/26032825",
"18143977"
],
"maxItems": 100,
"filtersAndOptions": {
"sortBy": "popular",
"languageCode": "all",
"reviewEdition": "ALL"
},
"proxyConfiguration": { "useApifyProxy": false }
}

Common pitfalls:

  • Setting maxItems to 0 does not stop the run or raise a validation error β€” the Actor treats 0 as "not set" and silently falls back to the default of 20 reviews per book, so a run you expected to fetch nothing from will still return up to 20 rows per book.
  • The proxyConfiguration field is present in the input form, but this Actor manages its own proxy strategy internally β€” it always starts with a direct connection and only escalates to Apify datacenter, then residential, proxies if Goodreads blocks a request. Whatever you select in proxyConfiguration does not change that behavior on the current build.
  • A narrow filtersAndOptions.languageCode combined with a book that has few or no reviews in that language will legitimately return fewer rows than maxItems, or none at all β€” that's Goodreads' filtered result, not a failed run.
  • A bare numeric ID only works when it's the Goodreads book ID from a /book/show/<id> URL β€” an ISBN or a work ID from a different URL shape won't resolve.

⬆️ Output

Results are typed, normalized JSON β€” the same field set on every row, run after run β€” streamed to the default Apify dataset as each review is found, so you can inspect or export partial results before a run finishes. Download the dataset as JSON, JSONL, CSV, Excel, HTML, XML or RSS from the Storage tab, or read it with the Apify API. The dataset's default Live results view only surfaces 8 of the fields below (bookUrl, id, rating, text, likeCount, commentCount, createdAtIso, creator) as table columns β€” every row actually carries the full set documented here; switch to View: OVERVIEW β†’ Full JSON or hit the API to see the rest.

Every dataset row is a review, and every review row is billed under the row_result pay-per-event β€” there are no separate error, warning, or accounting rows mixed into the dataset that you'd need to filter out before counting or billing reconciliation. If a run's usage-based charging can't be applied for any reason, the Actor falls back to saving the row without charging it rather than dropping it, so a billing hiccup never costs you data.

Scraped results

[
{
"bookUrl": "https://www.goodreads.com/book/show/26032825",
"__typename": "Review",
"id": "kca://review:goodreads/amzn1.gr.review:goodreads.v1.sdrC-ZRjzY6QMCANZHeBhQ",
"creator": {
"id": 5477612,
"imageUrlSquare": "https://i.gr-assets.com/images/S/compressed.photo.goodreads.com/users/1583425448i/5477612._UY200_CR0,0,200,200_.jpg",
"isAuthor": false,
"followersCount": 323630,
"__typename": "User",
"textReviewsCount": 2305,
"name": "Emily May",
"webUrl": "https://www.goodreads.com/user/show/5477612-emily-may",
"contributor": null
},
"recommendFor": null,
"updatedAt": 1452890265000,
"updatedAtIso": "2016-01-15T20:37:45Z",
"createdAt": 1452890265000,
"createdAtIso": "2016-01-15T20:37:45Z",
"spoilerStatus": false,
"lastRevisionAt": 1452890265000,
"lastRevisionAtIso": "2016-01-15T20:37:45Z",
"text": "A gorgeous, cruel fairy tale that earns every bit of its darkness.",
"rating": 5,
"shelving": {
"shelf": { "name": "read", "webUrl": "https://www.goodreads.com/review/list/5477612?shelf=read", "__typename": "Shelf" },
"taggings": [
{ "tag": { "name": "fantasy", "webUrl": "https://www.goodreads.com/review/list/5477612?shelf=fantasy", "__typename": "Tag" }, "__typename": "Tagging" }
],
"webUrl": "https://www.goodreads.com/review/show/1234567890",
"__typename": "Shelving"
},
"likeCount": 3120,
"commentCount": 84
},
{
"bookUrl": "https://www.goodreads.com/book/show/26032825",
"__typename": "Review",
"id": "kca://review:goodreads/amzn1.gr.review:goodreads.v1.a1b2c3d4e5f6g7h8",
"creator": { "id": 763271, "imageUrlSquare": "https://i.gr-assets.com/images/S/compressed.photo.goodreads.com/users/763271.jpg", "isAuthor": false, "followersCount": 412, "__typename": "User", "textReviewsCount": 88, "name": "Nancy", "webUrl": "https://www.goodreads.com/user/show/763271-nancy", "contributor": null },
"recommendFor": null,
"updatedAt": 1449100000000,
"updatedAtIso": "2015-12-02T21:26:40Z",
"createdAt": 1449100000000,
"createdAtIso": "2015-12-02T21:26:40Z",
"spoilerStatus": false,
"lastRevisionAt": 1449100000000,
"lastRevisionAtIso": "2015-12-02T21:26:40Z",
"text": "Dazzling inventions, air pirates and a princess. The setting was vividly described and rich in detail.",
"rating": 4,
"shelving": null,
"likeCount": 41,
"commentCount": 3
},
{
"bookUrl": "https://www.goodreads.com/book/show/26032825",
"__typename": "Review",
"id": "kca://review:goodreads/amzn1.gr.review:goodreads.v1.z9y8x7w6v5u4t3s2",
"creator": null,
"recommendFor": null,
"updatedAt": 1446000000000,
"updatedAtIso": "2015-10-28T00:00:00Z",
"createdAt": 1446000000000,
"createdAtIso": "2015-10-28T00:00:00Z",
"spoilerStatus": true,
"lastRevisionAt": null,
"lastRevisionAtIso": null,
"text": "Loved it but be warned β€” the ending is brutal (spoilers ahead).",
"rating": 4,
"shelving": { "shelf": { "name": "favorites", "webUrl": "https://www.goodreads.com/review/list/000?shelf=favorites", "__typename": "Shelf" }, "taggings": [], "webUrl": "https://www.goodreads.com/review/show/999", "__typename": "Shelving" },
"likeCount": 9,
"commentCount": 0
}
]

Fields Goodreads does not return for a given review β€” a missing creator, an un-set lastRevisionAt, an empty shelving β€” come back as null, never a guessed or fabricated value.

How can I use the data extracted with Goodreads Review Scraper?

  • πŸ“– Publishers and authors: track how a title's reviews trend after a launch, a cover redesign, or a marketing push, using rating, createdAtIso, and likeCount per review β€” pull the review set before and after the event and compare the two without opening the book page by hand.
  • πŸ€– AI engineers and LLM developers: feed text and rating straight into a sentiment or summarization model as grounded context β€” the agent issues the run, receives structured JSON back, and passes it to the model with no HTML to strip out or clean up first. The creator.name and shelving.shelf.name fields give the model attribution and category context for free.
  • πŸ“Š Market and competitive researchers: compare reader reception across competing or comparable titles by pulling reviews for each into the same run β€” a single urls array can hold every title in a comparison set β€” and aggregating rating and engagement counts per book.
  • πŸŽ“ Academic and data science teams: build reproducible, versioned review datasets β€” every row carries an ISO-8601 timestamp and a Goodreads review id, so re-running the same query set later is directly comparable to the last run, and duplicate review IDs across runs are trivial to detect and drop during analysis.

How do you monitor Goodreads reviews over time?

Review monitoring means re-running the same book list on a schedule and diffing the results against the previous run, rather than scraping once and treating the numbers as fixed. Between runs, the fields that move are likeCount and commentCount on existing reviews, and the appearance of brand-new review id values as readers post fresh reviews.

The practical loop: keep a saved Task with your book urls and filtersAndOptions.sortBy set to newest, run it on a schedule, and diff the new run's id values against the last run's β€” any id you haven't seen before is a new review; any id you have seen with a changed likeCount or commentCount is a review gaining or losing traction. Set filtersAndOptions.languageCode if you only care about review activity in one market. This turns a one-off scrape into a standing feed of "what changed since last time" for a title or a competitive set of titles.

A minimal diff loop looks like this once you have two runs' datasets to compare:

prev_ids = {r["id"]: r for r in previous_run_items}
for r in latest_run_items:
old = prev_ids.get(r["id"])
if old is None:
alert(f"new review on {r['bookUrl']}: {r['rating']}β˜… by {r['creator']['name'] if r['creator'] else 'unknown'}")
elif old["likeCount"] != r["likeCount"] or old["commentCount"] != r["commentCount"]:
alert(f"review {r['id']} engagement changed: {old['likeCount']}β†’{r['likeCount']} likes")

This Actor has no built-in scheduler of its own β€” set the recurrence up as an Apify Schedule against a saved Task in the Apify Console (or via the Apify API), and either poll the dataset after each scheduled run or wire the run's completion to a webhook/integration in the Console.

Integrate Goodreads Review Scraper and automate your workflow

Goodreads Review Scraper works with any language or tool that can send an HTTP request β€” it's an Apify Actor, callable through the Apify Console, the Apify API, or any of Apify's official API clients.

REST API with Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_API_TOKEN>")
run_input = {
"urls": ["https://www.goodreads.com/book/show/26032825"],
"maxItems": 50,
"filtersAndOptions": {"sortBy": "newest", "languageCode": "en", "reviewEdition": "ALL"},
}
run = client.actor("<YOUR_USERNAME>/goodreads-review-scraper-pay-per-events").call(run_input=run_input)
for review in client.dataset(run["defaultDatasetId"]).iterate_items():
print(review["rating"], review["text"][:80])

The same call works from any Apify API client β€” the JavaScript equivalent uses apify-client with the same actor slug and input shape:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('<YOUR_USERNAME>/goodreads-review-scraper-pay-per-events').call({
urls: ['https://www.goodreads.com/book/show/26032825'],
maxItems: 50,
filtersAndOptions: { sortBy: 'newest', languageCode: 'en', reviewEdition: 'ALL' },
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, 'reviews collected');

Scheduled monitoring and delivery

There is no webhook or push-delivery feature specific to this Actor, but the underlying Apify platform provides both: attach an Apify Schedule to a saved Task to run this Actor on a recurring cron interval, and configure an Apify webhook or a Zapier/Make integration on run completion to forward the finished dataset wherever your pipeline needs it.

Scraping publicly accessible Goodreads review pages is generally lawful β€” courts in the U.S. have found that scraping data a website makes available without a login is not a CFAA violation (hiQ Labs, Inc. v. LinkedIn Corp., 9th Cir., 2019). Goodreads Review Scraper returns only what any visitor can see on a book's reviews page while logged out; it does not access private accounts, private shelves, gated content, or anything behind Goodreads' own login wall. Because each review row includes reviewer personal data β€” a name, a profile URL, an avatar image, and a follower count β€” processing it at scale may still bring it within GDPR or CCPA if you handle EU or California residents' data, particularly for bulk storage, profiling, or re-publishing use cases. Scraping a handful of reviews for one-off research and continuously scraping reviewer data into a standing database carry meaningfully different risk profiles. You're also bound by Goodreads' own Terms of Service regardless of what the law separately permits. Consult your legal team before building a production pipeline around bulk reviewer data.

❓ Frequently asked questions

Can I filter Goodreads reviews by language?

Yes. Set filtersAndOptions.languageCode to one of 19 language codes (en, bn, fr, de, es, it, pt, ru, ja, ko, zh, ar, hi, nl, pl, tr, vi, id, th), or leave it at the default all to collect reviews in every language Goodreads has for that book. The filter is applied by Goodreads' API before pagination, so a language filter also affects how many pages the Actor needs to walk to reach maxItems β€” a popular English-language book filtered to a smaller language community will simply return fewer rows once that community's reviews run out.

Can I control the sort order of reviews?

Yes. filtersAndOptions.sortBy accepts popular (Goodreads' "most helpful first" ordering, the default), newest, or oldest. Combine sortBy: "newest" with a scheduled run to catch new reviews as they're posted.

What does reviewEdition actually change?

It controls whether you get reviews for the whole work or just one specific edition. ALL (default) returns reviews left across every edition of the book, aggregated at the work level; only_this_book restricts results to reviews left specifically on the edition the URL points to.

How does Goodreads Review Scraper handle Goodreads' anti-bot measures?

Goodreads serves book pages behind an AWS WAF JavaScript challenge (an HTTP 202 with an x-amzn-waf-action: challenge header). The Actor avoids that page entirely by calling Goodreads' own GraphQL API directly, and if a request is still blocked (HTTP 202, 403, 429, 502, or 503, or a response body that carries an AWS WAF integration marker), it retries up to 3 times with a short backoff, then escalates the connection from direct, to Apify datacenter proxy, to residential proxy β€” locking onto residential for the rest of that book once it's needed, so it doesn't keep flipping tiers mid-run. If every tier is exhausted and the book still can't be reached, that book's run step fails with an error rather than returning partial or guessed data.

Does Goodreads Review Scraper extract language- and edition-filtered reviews?

Yes β€” set sortBy, languageCode, and reviewEdition inside filtersAndOptions, and the filtering is applied by Goodreads' own API before rows are returned, not after. There's no separate "filtered" object; the filters simply change which Review rows come back.

How many reviews does Goodreads Review Scraper return per book?

Up to maxItems per book (default 20, maximum 10000), or fewer if the book has fewer reviews matching your filters β€” the Actor stops paging for a book as soon as Goodreads returns no further review pages. With multiple urls, the cap applies separately to each book, not to the run as a whole.

How do I use Goodreads Review Scraper to monitor a book's reviews over time?

Schedule the same urls list on a recurring Apify Schedule, sort by newest, and compare each run's review id values against the previous run's β€” new IDs are new reviews, and a changed likeCount or commentCount on a known ID means that review gained or lost traction.

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

It's callable as a standard Apify Actor via the Apify API and apify_client, so any agent framework that can send an HTTP request or call a Python/JS SDK method can invoke it, wait for the run, and read the dataset back as grounded context for the model.

How does Goodreads Review Scraper compare to other Goodreads scrapers?

As observed on their Apify Store listings on 2026-07-26: easyapi/goodreads-review-scraper takes bookUrls + maxItems with no sort, language, or edition control, and its own sample output shows no ISO-8601 date fields, only raw epoch timestamps. thescrapelab/Apify-Goodreads-Scraper covers a broader surface (book metadata, ISBN lookup, author and series data, plus reviews via a searchMode), but its review path exposes no language or edition filter either. scrapestorm/goodreads-review-scraper---faster-cheaper takes only a book URL and maxItems, has no documented filter parameters, and is billed as a flat $18.99/month subscription rather than pay-per-result. Goodreads Review Scraper is the only one of the four with a documented languageCode and reviewEdition filter, and it returns both epoch-ms and ISO-8601 timestamps on every date field.

What happens if a book can't be reached after every retry and proxy tier is exhausted?

The run stops with an error rather than silently skipping that book. Any reviews already saved for books processed earlier in the same run stay in the dataset β€” nothing already written is rolled back β€” but books later in the urls list that hadn't started yet are not processed. If you're running a large urls list, splitting it across a few smaller runs limits how much of a batch a single unreachable book can affect.

Can I use Goodreads Review Scraper without managing proxies or Goodreads credentials?

Yes. No Goodreads account, login, or API key is required β€” the Actor authenticates to Goodreads' own public GraphQL API with a stable key embedded in the Actor itself. Proxy handling is automatic: the run starts with a direct connection and only reaches for Apify datacenter, then residential, proxies if Goodreads blocks it, so you don't need to select or configure a proxy group for a normal run to succeed.

πŸ’¬ Your feedback

Found a bug, or a Goodreads field this Actor should be pulling but isn't? Open an issue through the Actor's page on the Apify Store, or reach out via the Scrapier publisher profile on Apify β€” reports like this go straight into the next update.