Goodreads Review Scraper With Reviewer Lead Enrichment
Pricing
from $4.99 / 1,000 results
Goodreads Review Scraper With Reviewer Lead Enrichment
π Goodreads Review Scraper pulls reviews from book & author pages β ratings, review text, dates, shelves, likes & reviewer info. β‘ Export CSV/JSON/API for sentiment, market research & book marketing. π Perfect for publishers, authors & data teams.
Pricing
from $4.99 / 1,000 results
Rating
0.0
(0)
Developer
Scraper Engine
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
0
Monthly active users
4 days ago
Last modified
Categories
Share
Goodreads Review Scraper β Ratings, Review Text and Reviewer Reach
Goodreads Review Scraper extracts public book reviews as structured JSON: star rating, full review text, reviewer display name and profile URL, follower count, lifetime review count, like and comment counts, plus absolute UTC timestamps. Every review row also carries a computed reviewer influence score and tier, and each run closes with a deduplicated, ranked list of the most influential reviewers across all the books you submitted. Paste book links or bare numeric book IDs and rows stream into the output table as they are collected.
What is Goodreads Review Scraper With Reviewer Lead Enrichment?
Goodreads Review Scraper With Reviewer Lead Enrichment is an Apify Actor that reads Goodreads' public review feed for any book and returns 32 keys per review, including a flattened reviewer profile and a deterministic 0β100 reach score derived from the reviewer's public follower and review counts. At the end of the run it aggregates those reviewers across every book you supplied, deduplicates them, and pushes a ranked top_reviewer row for each.
No Goodreads account, login, cookie or API key is required β the Actor reads the same public review data any visitor sees. It is built for authors and publishers looking for book influencers to approach, marketers running review-sentiment analysis, and developers piping review text into an analysis or RAG pipeline.
What Goodreads review data is publicly available to scrape?
Goodreads publishes review text, star ratings, reviewer display names, profile links, avatars and public follower counts on every book's review page with no sign-in. What sits behind an account is the social layer β who is friends with whom, friends-only content, and anything relative to a signed-in viewer.
| Data Category | Publicly Available | Requires a signed-in Goodreads account |
|---|---|---|
| Review text and star rating | β | β |
| Reviewer display name, profile URL, avatar | β | β |
| Reviewer follower count and lifetime text-review count | β | β |
| Shelf name and reader-applied tags on a review | β | β |
| Like count and comment count on a review | β | β |
| Published-author flag and works count for author reviewers | β | β |
| Whether a reviewer follows you or is your friend | β | β |
| Friends-only reviews and private shelves | β | β |
Goodreads does not publish reviewer email addresses, phone numbers, websites or social handles anywhere on a public review or profile page, so no scraper can return them β including this one. Goodreads Review Scraper only returns publicly visible data β what any visitor sees. Nothing behind a login wall.
What data can I extract with Goodreads Review Scraper?
Every run produces two row shapes, distinguished by the type key: review rows for each collected review, and top_reviewer rows for the ranked reviewer leads emitted at the end.
A review row carries 32 keys β the review itself, the raw Goodreads reviewer object, the flattened reviewer lead columns, and run accounting.
| Field Name | Description |
|---|---|
type | Row discriminator β "review" on collected reviews |
isChild | false on review rows; true on top-reviewer lead rows |
bookUrl | The book link this row came from, exactly as resolved |
bookLegacyId | Numeric Goodreads book ID parsed out of bookUrl |
id | Goodreads review identifier (a kca://review/... string) |
__typename | GraphQL type name as Goodreads returns it |
text | Full review body, plain text |
rating | Star rating the reviewer gave, 1β5 |
spoilerStatus | Whether the reviewer flagged the review as containing spoilers |
recommendFor | Free-text "recommended for" note, when the reviewer wrote one |
likeCount | Number of likes the review has received |
commentCount | Number of comments on the review |
viewerHasLiked | Viewer-relative like flag β always unauthenticated here |
shelving | Nested object: the shelf the reviewer filed the book under plus their tags |
createdAt | Review creation time as Goodreads epoch milliseconds |
updatedAt | Last update time as epoch milliseconds |
lastRevisionAt | Last text-revision time as epoch milliseconds |
createdAtIso | createdAt normalized to a UTC ISO-8601 string |
updatedAtIso | updatedAt normalized to a UTC ISO-8601 string |
lastRevisionAtIso | lastRevisionAt normalized to a UTC ISO-8601 string |
creator | The raw nested reviewer object exactly as Goodreads returns it |
reviewerId | Reviewer's numeric Goodreads user ID |
reviewerName | Reviewer's public display name |
reviewerProfileUrl | Reviewer's public Goodreads profile URL |
reviewerImageUrl | Reviewer's public square avatar URL |
reviewerFollowers | Public follower count on the reviewer's profile |
reviewerReviewCount | Reviewer's lifetime public text-review count |
isAuthorReviewer | true when the reviewer is a published Goodreads author |
reviewerWorksCount | Number of works credited to the reviewer, for author reviewers |
reviewerInfluenceScore | Computed 0β100 reach score (see below) |
reviewerTier | Bucketed label derived from that score |
scrapedAt | UTC ISO-8601 timestamp of collection |
Review and book fields
text is the full review body with no HTML to strip, which makes it the field to point sentiment analysis or a vector index at. rating is the reviewer's own 1β5 star value. shelving is a nested object β shelving.shelf.name is the shelf the reader filed the book under (read, currently-reading, a custom shelf), and shelving.taggings is an array of the reader's own tags, each with tag.name and tag.webUrl. It comes back null when the reviewer never shelved the book.
Goodreads returns its three timestamps as epoch milliseconds. Those are preserved verbatim in createdAt, updatedAt and lastRevisionAt, and each is also written as a UTC ISO-8601 string in createdAtIso, updatedAtIso and lastRevisionAtIso. An ISO field comes back null rather than guessed when the source value is missing or unparseable.
viewerHasLiked and the viewerRelationshipStatus object nested inside creator are viewer-relative fields β they describe a signed-in reader's relationship to the review. Because the Actor runs unauthenticated, they carry no meaningful signal; they are passed through rather than dropped so the row stays a faithful superset of Goodreads' own payload.
Reviewer lead fields
reviewerId, reviewerName, reviewerProfileUrl, reviewerImageUrl, reviewerFollowers, reviewerReviewCount, isAuthorReviewer and reviewerWorksCount are flattened copies of values that already sit inside the creator object on the same review β hoisted to the top level so a CSV export or a spreadsheet pivot needs no nested-JSON handling.
reviewerInfluenceScore is computed, not scraped. It is a deterministic 0β100 number: follower count contributes 70%, lifetime text-review count contributes 30%, both log-scaled so a reviewer with a million followers and a reviewer with one land on a readable spread rather than at opposite ends of a straight line. A published-author reviewer gets a fixed 10-point boost, and the result is capped at 100. When Goodreads returns neither a follower count nor a review count, the score is null.
reviewerTier buckets that score into mega-influencer (75 and above), influencer (50β74.9), active-reviewer (25β49.9), casual-reader (above 0), or unknown when the score is null.
Top-reviewer lead fields
Ranked top_reviewer rows are pushed after every book has been processed. They carry 21 keys and a different shape β the review-specific fields are omitted, not nulled:
| Field Name | Description |
|---|---|
type | "top_reviewer" on every ranked lead row |
isChild | true |
leadRank | 1-based rank within this run, by descending leadScore |
leadScore | reviewerInfluenceScore plus a like-based engagement term (log-scaled, capped at 40) plus 5 points per extra book the reviewer appears on |
reviewerId, reviewerName, reviewerProfileUrl, reviewerImageUrl | Reviewer identity, carried over from the review rows |
reviewerFollowers, reviewerReviewCount, reviewerWorksCount, reviewerInfluenceScore | Highest value seen for this reviewer across the run |
isAuthorReviewer, reviewerTier | Published-author flag and influence tier |
booksReviewedCount | How many of your submitted books this reviewer reviewed |
booksReviewed | Array of those book URLs, sorted |
reviewsCollectedCount | How many of this reviewer's reviews the run actually collected |
totalLikesOnCapturedReviews | Sum of likeCount across those collected reviews |
avgRatingGiven | Mean star rating this reviewer gave across them, to 2 decimals, or null |
runId | The Apify run ID that produced the lead |
scrapedAt | UTC ISO-8601 timestamp |
The same rows are also mirrored into a per-run named dataset called top-reviewers-<runId>, so you can pull the lead list on its own without filtering the main dataset. If that mirror cannot be opened the run logs a warning and continues β the rows are still in the default dataset either way.
π€ Add-on: Need additional review or reviewer data?
If you are benchmarking a title across platforms, Scraper Engine also publishes Google Play Store Reviews Scraper and Apple App Store Review Scraper for app reviews, and Google Maps Scraper With Reviews Sentiment Analysis for local business reviews. For creator-side reach on other platforms, YouTube Shorts Scraper With Creator Lead Enrichment and Pinterest Comment Scraper With Commenter Profile Enrichment follow the same enrich-the-commenter pattern used here.
What "reviewer lead enrichment" actually returns
Read this before you plan an outreach workflow around the Actor's name.
No contact detail of any kind is extracted. There is no email address, no phone number, no personal website, no social handle and no messaging address anywhere in the output. Goodreads does not publish those on a public review or profile page, and the Actor makes no attempt to find them elsewhere. The only route to a reviewer from this data is the public Goodreads profile at reviewerProfileUrl, opened by hand.
Enrichment costs no extra request. The reviewer fields are not fetched from each reviewer's profile page. They arrive inside the creator object on the same review payload, in the same request that returns the review text β Goodreads ships the reviewer's name, avatar, profile URL, follower count, lifetime review count and author flag alongside every review. The Actor flattens them, scores them and aggregates them. There is no per-reviewer lookup, and therefore no lookup cap to configure and no second request to pay for.
"Lead" here means reach, not contactability. The genuinely new information the enrichment produces is reviewerInfluenceScore and reviewerTier on review rows, and leadScore, leadRank, booksReviewedCount, booksReviewed, reviewsCollectedCount, totalLikesOnCapturedReviews and avgRatingGiven on the ranked rows. Everything else labelled "reviewer" is a flattened restatement of what was already on the review.
Fields the Actor does not return, because Goodreads does not put them on the review payload: reviewer location, join date, reading-challenge stats, followed authors, shelf inventory, friend list, and any of the reviewer's other reviews beyond the ones your run collected.
Why not build this yourself?
There is no openly available Goodreads public API to build against today, so a do-it-yourself version means working against the same undocumented surface this Actor uses β and the failure modes are not obvious until you hit them.
Goodreads serves its book HTML pages behind an AWS WAF JavaScript challenge that answers server-side requests with an HTTP 202 and a challenge body rather than an error. A naive scraper reads that as a success and parses an empty page. The review data itself lives on a separate AWS AppSync GraphQL endpoint that is not behind the WAF, but reaching it requires a public API credential that Goodreads' own web app ships inside a JavaScript bundle β and that credential can rotate. This Actor keeps a known-good pair and, if it stops working, re-extracts a fresh one from the page's JS bundles automatically.
Then there is the resolution step. A Goodreads review list is not keyed by the numeric ID in the URL; it is keyed by an opaque work or book resource ID, and which of the two you need depends on whether you want reviews for the whole work or for one specific edition. Resolving that costs a separate GraphQL call per book before you can request a single review.
Add cursor pagination, block detection across five different HTTP statuses, a proxy tier that escalates only while it is still safe to do so, and epoch-millisecond timestamps that need normalizing before any cross-book time series is valid β and a hand-rolled version becomes a maintenance job rather than a script.
How to use Goodreads Review Scraper With Reviewer Lead Enrichment
The Actor runs on Apify. Start it from the Apify Console or call it through the Apify API β there is no separate signup and no other platform account to create.
- Open Goodreads Review Scraper With Reviewer Lead Enrichment on Apify and click Try for free
- Paste one or more links into Book URLs β
https://www.goodreads.com/book/show/26032825works, and so does the bare number26032825 - Set Max reviews per book. Try 20 for a quick sample; the field accepts up to 10,000
- Optionally open Reviewer lead enrichment to set a minimum follower threshold or change how many ranked leads you want, and Filters & sorting to change sort order, language or edition scope
- Click Start β review rows appear in the output table as they are collected, and the ranked lead rows are appended once the last book finishes
- Export as JSON, CSV or Excel, or read the dataset through the Apify API
How to scale to bulk review extraction
urls is an array, so one run handles as many books as you list. maxItems applies per book, not per run β five books at 200 reviews each is a 1,000-row target, plus the ranked lead rows on top. Books are processed one at a time in the order you list them, and the reviewer aggregation spans all of them, so a single multi-book run is what produces a cross-book lead list. Separate runs each produce their own isolated ranking with no shared deduplication.
What can you do with Goodreads review and reviewer data?
- π£ A publicist planning a launch campaign runs the author's backlist in one job with
minFollowersset to 1000, then works thetop_reviewerrows byleadRank, usingbooksReviewedCountto find the readers who already engaged with more than one title. - π A publisher's marketing analyst groups review rows by
ratingandcreatedAtIsoto plot how sentiment for a title moved month by month, usinglikeCountto weight the reviews other readers actually found useful. - π A competitive researcher collects reviews for a rival's title and their own, then intersects on
reviewerIdto find readers reviewing both, readingavgRatingGivento see which book each one preferred. - βοΈ An author deciding who to send advance copies to filters
reviewerTiertoinfluencerand above, checksreviewerReviewCountfor readers who actually finish and write, and opensreviewerProfileUrlto read their history before deciding. - π€ An AI engineer building a book-recommendation agent indexes
textwithrating,reviewerTierandshelving.taggingsas metadata filters, so the agent can answer "what do high-reach reviewers say about the pacing of this book" against real review text rather than a summary.
Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run.
How does Goodreads Review Scraper handle rate limits and blocking?
The Actor starts with a direct connection and escalates only when it has to. Every request β the book resolution call and each page of reviews β gets up to three attempts, with a linear backoff of 1.2 seconds multiplied by the attempt number between them.
A response counts as blocked on HTTP 202, 403, 429, 502 or 503, on any other non-200 status, or when the body contains AWS WAF challenge markup. Goodreads' WAF answers server-side requests with a 202 rather than an error, so the 202 check is the one that matters most.
When a book fails in a way worth retrying, the connection tier escalates: direct β Apify datacenter proxy β Apify residential proxy. Once a run falls back to residential it stays there for the rest of the run, since a route that got through once is the safest one to keep. Escalation only happens before the first row of that book has been saved β once rows are streaming, a mid-book failure is raised rather than restarted, so you never get duplicate rows from a partially collected book.
There is no CAPTCHA solving in this Actor, and none is claimed β blocking is handled by changing route, not by defeating a challenge. Between review pages the Actor sleeps a random 0.2β0.8 seconds. If a book still cannot be loaded after all retries and all escalation options, the run fails: rows already pushed stay in the dataset, but the remaining books are not processed and no ranked lead rows are emitted.
β¬οΈ Input
One parameter is required: urls. Everything else has a default, and a run with nothing else set collects 20 popular-first reviews per book, in all languages, across all editions, with the ranked lead list turned on.
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
urls | Yes | array | Goodreads book links, one entry per book. A bare numeric book ID also works β anything not starting with http is turned into https://www.goodreads.com/book/show/<value>. Prefilled with one link. | ["https://www.goodreads.com/book/show/26032825", "2767052"] |
maxItems | No | integer | How many reviews to collect per book. Minimum 1, maximum 10000. Default 20. | 50 |
reviewerLeadOptions | No | object | Reviewer scoring, filtering and ranking options β see the sub-table below. Every sub-default applies when omitted. | {"minFollowers": 500, "topReviewersCount": 25} |
filtersAndOptions | No | object | Sort order, review language and edition scope β see the sub-table below. | {"sortBy": "newest", "languageCode": "en"} |
proxyConfiguration | No | object | Apify Proxy settings. Prefilled with {"useApifyProxy": false}. | {"useApifyProxy": false} |
reviewerLeadOptions sub-parameters:
| Sub-parameter | Type | Description | Default |
|---|---|---|---|
minFollowers | integer | Keep only reviews written by reviewers with at least this many followers. 0 keeps everyone. Minimum 0. | 0 |
includeTopReviewers | boolean | Append the ranked, deduplicated top-reviewer rows at the end of the run. | true |
topReviewersCount | integer | How many ranked lead rows to emit. 0 emits every unique reviewer. Minimum 0. | 25 |
dedupeReviewers | boolean | Collapse a reviewer appearing on several of your books into one lead row. Turn off to rank each reviewer-and-book pair separately. | true |
filtersAndOptions sub-parameters:
| Sub-parameter | Type | Description | Default |
|---|---|---|---|
sortBy | string | popular, newest or oldest. | "popular" |
languageCode | string | all, or one of en, bn, fr, de, es, it, pt, ru, ja, ko, zh, ar, hi, nl, pl, tr, vi, id, th. Filtering is applied by Goodreads, not locally. | "all" |
reviewEdition | string | ALL for reviews of the whole work, only_this_book for the specific edition you linked. | "ALL" |
Six honest notes on how these behave:
- A run with no
urlsfails immediately. The Actor raisesInput "urls" must contain at least one Goodreads book URL.before any request is made, so nothing is collected and nothing is charged. proxyConfigurationis accepted but never read. The Actor manages its own connection tier, described in the blocking section above: it always begins direct and only reaches for Apify Proxy when a request is refused. Turning the toggle on or off, or selecting a group or country, changes nothing about the run.minFollowersabove zero caps how far a book is scanned. With a follower filter active most reviews may be skipped, so the Actor scans at mostmaxItems Γ 20reviews per book β or 400, whichever is larger β then stops that book even if it has not reachedmaxItems. With no follower filter there is no scan cap, because every scanned review is kept.minFollowersalso drops reviewers with no follower count at all. A missing follower value is treated as zero, so any threshold above zero excludes deleted, anonymised or otherwise incomplete reviewer records along with the low-reach ones.topReviewersCounttreats zero and negative values identically. Both skip truncation entirely and emit a ranked row for every unique reviewer in the run. On a large multi-book job that can be a lot of extra rows, and each one is charged.maxItems: 0silently becomes 20 when passed through the API, because a falsy value falls back to the default. A negative value fails the run withmaxItems must be positive.The Console enforces the 1β10000 range for you.
Undocumented aliases: every sub-parameter of reviewerLeadOptions and filtersAndOptions is also accepted at the top level of the input β {"minFollowers": 500} behaves exactly like {"reviewerLeadOptions": {"minFollowers": 500}}. If a key is set in both places, the nested one wins. This exists for backwards compatibility; prefer the nested form.
Example input
{"urls": ["https://www.goodreads.com/book/show/26032825","https://www.goodreads.com/book/show/2767052","13496"],"maxItems": 50,"reviewerLeadOptions": {"minFollowers": 250,"includeTopReviewers": true,"topReviewersCount": 25,"dedupeReviewers": true},"filtersAndOptions": {"sortBy": "popular","languageCode": "en","reviewEdition": "ALL"},"proxyConfiguration": {"useApifyProxy": false}}
β¬οΈ Output
Typed, normalized JSON with a stable schema across runs. Review rows are pushed live as each page is parsed, so the dataset fills while the run is still going; ranked lead rows are appended once every book is done. Export as JSON, CSV or Excel, or read the dataset through the Apify API.
Every row in the dataset is a result row, and every result row is charged as one row_result event β including the top_reviewer lead rows, which restate reviewers already present in the review rows. This Actor writes no error rows, no diagnostic rows and no accounting rows: there is no errorReason, status, isError or equivalent marker anywhere in the output, because no such row is ever pushed. Failures live in the run log, not in the data, and a book with no reviews contributes no rows at all.
The type key is a row-shape discriminator, not an error marker. Split the two shapes with:
reviews = [r for r in items if r["type"] == "review"]leads = [r for r in items if r["type"] == "top_reviewer"]
isChild is the boolean equivalent β false on reviews, true on leads.
Fields are omitted, not nulled, across row shapes. A top_reviewer row has no text, rating, bookUrl, id, likeCount, commentCount or ISO timestamp keys; a review row has no leadRank, leadScore, booksReviewedCount, booksReviewed or avgRatingGiven. Use .get() rather than direct indexing when iterating a mixed dataset, and expect empty cells in a CSV export wherever a column belongs to the other shape.
Deduplication applies to reviewer leads only, never to reviews. Review rows are pushed exactly as Goodreads paginates them, with no review-ID dedupe β so listing the same book twice in urls collects and charges for its reviews twice. Reviewer deduplication happens only inside the ranking step, keyed on reviewerId and falling back to reviewerProfileUrl then reviewerName, and only while dedupeReviewers is on. With it off, the key becomes reviewer-plus-book, so a reviewer on three of your books produces three separate lead rows, each showing booksReviewedCount: 1.
Example output
A review row β all 32 keys:
{"bookUrl": "https://www.goodreads.com/book/show/26032825","bookLegacyId": 26032825,"__typename": "Review","id": "kca://review/amzn1.gr.review.v1.rTKrPWFMr7vNe0kOaXQZ3g","creator": {"id": 4267369,"imageUrlSquare": "https://images.gr-assets.com/users/1436977339p2/4267369.jpg","isAuthor": false,"viewerRelationshipStatus": {"isFollowing": false,"isFriend": false,"isBlockedByViewer": false,"__typename": "ViewerRelationshipStatus"},"followersCount": 8412,"__typename": "User","textReviewsCount": 1276,"name": "Elena Marchetti","webUrl": "https://www.goodreads.com/user/show/4267369-elena-marchetti","contributor": null},"recommendFor": null,"updatedAt": 1747310400000,"createdAt": 1747126380000,"spoilerStatus": false,"lastRevisionAt": 1747310400000,"text": "I put this down twice in the first hundred pages and I am glad I went back. The structure is doing something deliberate β the three timelines only stop feeling like a trick once the second one catches up, and after that the book is relentless. The geology is not set dressing, it is the argument.","rating": 5,"shelving": {"shelf": {"name": "read","webUrl": "https://www.goodreads.com/review/list/4267369?shelf=read","__typename": "Shelf"},"taggings": [{"tag": {"name": "science-fiction","webUrl": "https://www.goodreads.com/review/list/4267369?shelf=science-fiction","__typename": "Tag"},"__typename": "Tagging"}],"webUrl": "https://www.goodreads.com/review/show/1893472018","__typename": "Shelving"},"likeCount": 214,"viewerHasLiked": false,"commentCount": 18,"reviewerId": 4267369,"reviewerName": "Elena Marchetti","reviewerProfileUrl": "https://www.goodreads.com/user/show/4267369-elena-marchetti","reviewerImageUrl": "https://images.gr-assets.com/users/1436977339p2/4267369.jpg","reviewerFollowers": 8412,"reviewerReviewCount": 1276,"isAuthorReviewer": false,"reviewerWorksCount": null,"reviewerInfluenceScore": 69.1,"reviewerTier": "influencer","createdAtIso": "2026-05-13T09:33:00Z","updatedAtIso": "2026-05-15T12:00:00Z","lastRevisionAtIso": "2026-05-15T12:00:00Z","type": "review","isChild": false,"scrapedAt": "2026-07-25T09:14:02Z"}
A top_reviewer lead row for the same reviewer, from a run covering two of her books β all 21 keys:
{"reviewerId": 4267369,"reviewerName": "Elena Marchetti","reviewerProfileUrl": "https://www.goodreads.com/user/show/4267369-elena-marchetti","reviewerImageUrl": "https://images.gr-assets.com/users/1436977339p2/4267369.jpg","reviewerFollowers": 8412,"reviewerReviewCount": 1276,"isAuthorReviewer": false,"reviewerWorksCount": null,"reviewerInfluenceScore": 69.1,"reviewerTier": "influencer","reviewsCollectedCount": 2,"totalLikesOnCapturedReviews": 331,"type": "top_reviewer","isChild": true,"booksReviewedCount": 2,"booksReviewed": ["https://www.goodreads.com/book/show/2767052","https://www.goodreads.com/book/show/26032825"],"avgRatingGiven": 4.5,"leadScore": 94.3,"runId": "8kPq2mVxRn4TbLwZ","leadRank": 1,"scrapedAt": "2026-07-25T09:18:47Z"}
How does it work?
For each book, the Actor first pulls the numeric ID out of your URL and resolves it into Goodreads' internal work and book resource IDs with a single GraphQL call. Which one it uses depends on reviewEdition β the work ID gathers reviews across all editions, the book ID restricts them to the edition you linked.
It then pages through Goodreads' public review endpoint 30 reviews at a time, following the cursor Goodreads returns, until it has collected maxItems for that book or the list runs out. Sorting and language filtering are sent with the request, so Goodreads does that work rather than the Actor discarding rows locally.
Requests go out directly by default. If Goodreads refuses one β a WAF challenge, a rate-limit status, a timeout β the Actor retries, and if that fails it escalates through Apify's datacenter proxy to residential, with nothing for you to configure. Each review is flattened, scored and pushed the moment it is parsed, and the reviewer is added to an in-memory tally that becomes the ranked lead list at the end.
Only publicly visible data is collected β no Goodreads account, cookie or session is involved anywhere. Because the output keys are defined by the Actor rather than by Goodreads' page markup, a site redesign can affect coverage but never your field names or their types.
Integrations
Goodreads Review Scraper is an Apify Actor, so it works with anything that can call the Apify API or consume a dataset β scripts, schedules, webhooks, no-code builders and agent frameworks.
Calling Goodreads Review Scraper from Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("<YOUR_USERNAME>/goodreads-review-scraper-with-reviewer-lead-enrichment").call(run_input={"urls": ["26032825", "2767052"],"maxItems": 50,"reviewerLeadOptions": {"minFollowers": 250, "topReviewersCount": 25},"filtersAndOptions": {"sortBy": "popular", "languageCode": "en"},})items = list(client.dataset(run["defaultDatasetId"]).iterate_items())leads = [r for r in items if r["type"] == "top_reviewer"]for lead in sorted(leads, key=lambda r: r["leadRank"]):print(lead["leadRank"], lead["reviewerName"], lead["leadScore"],lead["reviewerFollowers"], lead["booksReviewedCount"])
Works in Go, Ruby, Node.js, cURL β any language that can make an HTTP request. Filter on type before iterating, since the two row shapes carry different keys.
No-code tools (n8n, Make, LangChain)
In n8n, use the Apify node β or an HTTP Request node pointed at the Actor's run endpoint with your token β and pass the same JSON input shown above; a Filter node on type splits reviews from leads before they reach a Google Sheets or Airtable step. In Make, the Apify module supports run-and-wait, so a monthly sweep of a backlist can feed a spreadsheet or CRM step directly. In LangChain, wrap the run endpoint as a tool and pass text, rating and reviewerTier straight to the model β the review body is already plain text with no markup to strip. Apify schedules and webhooks cover recurrence and completion triggers.
β οΈ Is it legal to scrape Goodreads reviews and reviewer profiles?
Collecting reviews published on a public page, with no login bypassed, is generally treated as permissible, and this Actor reads only what any visitor sees. But the output is personal data about identifiable private individuals, and that shapes your obligations rather than removing them.
Reviewers are readers writing in a personal capacity, not businesses. A display name, profile URL, avatar and review history identify a person, and what someone reads can reveal beliefs, politics, health interests and sexuality. That makes a reviewer dataset more revealing than a typical business contact list, and the "lead" framing raises the stakes rather than lowering them. The moment you store a row, you are a data controller.
Under GDPR and UK GDPR you need a lawful basis before storing or reusing these rows. If you rely on legitimate interest, that requires a documented balancing test β and it is materially harder to satisfy for private individuals in a personal context than for business contacts. Under the CCPA, reviewers are consumers with access and deletion rights. Because the data is collected indirectly rather than from the person, GDPR Article 14 transparency duties apply, alongside retention limits and the duty to answer subject requests.
Minimise concretely: if your analysis needs sentiment or ratings, drop reviewer identity at ingestion and keep rating, text, createdAtIso and bookUrl only. If you need reach, keep the scores and drop the names.
No email, phone, website or social handle is returned by this Actor, so it supplies no contact channel. Approaching anyone you find here is regulated separately from collecting the data, and "it was public" is not a lawful basis for contacting someone. Consult legal counsel if your use case involves bulk storage of personal data.
β Frequently asked questions
What Goodreads review fields does the scraper return?
Thirty-two keys per review. The five you will use most are text, rating, reviewerName, reviewerFollowers and reviewerInfluenceScore, with likeCount close behind as a proxy for how useful other readers found the review. See the data fields table above for the full list, including the nested creator and shelving objects and the separate 21-key top_reviewer shape.
Does the scraper require a Goodreads account or login?
No. There is no Goodreads account, cookie, session or API key involved, and none can be supplied. The Actor reads Goodreads' public review data the same way an anonymous visitor's browser does, and the only credential you need is your Apify token. One side effect: the viewer-relative fields Goodreads includes in its payload β viewerHasLiked and the viewerRelationshipStatus object inside creator β are always unauthenticated and carry no signal.
How many reviews can I extract in one run?
maxItems accepts 1 to 10,000 and applies per book, so ten books at 500 each is a 5,000-row target in one run, plus up to topReviewersCount ranked lead rows on top. Whether you reach the target depends on how many reviews the book actually has. One caveat: if minFollowers is above zero, each book stops after scanning maxItems Γ 20 reviews (or 400, whichever is larger), so a high threshold on a book with few high-reach reviewers returns fewer rows than you asked for.
What happens if a book has no reviews, or a reviewer's profile is private or deleted?
A book with no reviews returns no rows and no error. The Actor logs that there is nothing more to show, moves to the next book, and the run finishes normally β you are not charged for a book that produced nothing.
A reviewer whose profile is unavailable is handled differently: the review row is still pushed, with reviewerId, reviewerName, reviewerProfileUrl, reviewerFollowers and reviewerReviewCount all null, reviewerInfluenceScore null, and reviewerTier set to "unknown". Those fields are nulled, not dropped, and the row is still charged. That reviewer is excluded from the ranked lead list, since there is no stable key to aggregate on. If minFollowers is above zero the row is skipped entirely, because a missing follower count counts as zero.
A book URL that cannot be resolved at all is a third case: after three retries and every proxy escalation option, the run fails. Rows already collected stay in the dataset, but the remaining books are not processed and no lead rows are emitted. Validate your book IDs before a long run.
Can I scrape multiple Goodreads books at once?
Yes β that is the intended shape. urls is an array, and the reviewer aggregation deliberately spans every book in the run, which is the only way booksReviewedCount and the cross-book component of leadScore mean anything. Books are processed sequentially. Running each book separately gives you the same review rows but a separate, non-comparable lead ranking per run.
Does the scraper work with Claude, ChatGPT and other AI agent tools?
Yes. It is callable as a standard HTTP endpoint through the Apify API, so LangChain, CrewAI, n8n or a custom tool definition can invoke it and receive typed JSON. Review text arrives as clean plain text, so text goes straight into an LLM context window or a vector store with no HTML stripping.
How does this compare to other lead-enrichment scrapers on the Apify Store?
Checked on the Apify Store on 25 July 2026, the most-used Actors carrying "lead enrichment" in their titles all work on business data: anchor/linkedin-profile-enrichment documents live LinkedIn person and company fields including company_website; automation-lab/linkedin-company-scraper documents 18 company fields and points users at a separate contact-finder Actor for emails; zen-studio/yandex-maps-scraper documents phone numbers directly and an optional paid enrichment event for email. None of them is a Goodreads scraper, and no directly comparable Goodreads reviewer-enrichment Actor was found on the Store on that date. The difference in kind matters more than any difference in field counts: those three return business contact details, while this Actor returns no contact detail at all β its enrichment is a reach score computed from public reviewer stats.
Does the scraper return data in a format LLMs can use directly?
Yes. Typed, normalized JSON with consistent field names across runs, no HTML and no selectors. Pass the rows directly to an LLM, index them into a vector store, or feed them to an agent tool. The only shape you need to handle is the two row types β filter on type first.
What happens when Goodreads changes its layout or anti-bot system?
The Actor does not parse page HTML for review content, so a visual redesign does not affect it. It reads Goodreads' own structured review endpoint, and it already handles the WAF challenge guarding the HTML pages by escalating its connection tier. If the public API credential Goodreads ships rotates, the Actor re-extracts a fresh one from the site's JavaScript bundles automatically. The Actor is maintained, and the output field names and types stay stable regardless.
Can I use it without managing proxies or browser infrastructure?
Yes, and there is nothing to configure. No browser is launched at any point β the Actor makes plain HTTP requests. Proxying is handled internally: it starts direct, escalates to Apify's datacenter proxy if a request is refused, then to residential, and stays on residential for the rest of the run. You never create a proxy account or rotate an IP, and the proxyConfiguration input has no effect on any of it.
Which fields work best for AI training data and RAG indexing?
For RAG, text carries by far the most information per record β full natural-language review bodies with no markup β with shelving.taggings supplying reader-applied topic tags as ready-made metadata filters and bookUrl as a partition key. For training data and feature extraction, the most structurally consistent fields are rating, likeCount, commentCount, reviewerFollowers, reviewerReviewCount, reviewerInfluenceScore and createdAtIso β all typed primitives, all normalized rather than passed through, with createdAtIso already in UTC so a cross-book time series needs no timezone handling. Keep the personal-data obligations in the legal section in mind before pushing reviewer identity into a training set or a shared vector store.
π Related scrapers
| Scraper Name | What it extracts |
|---|---|
| Google Play Store Reviews Scraper | Android app review text, ratings and app versions |
| Apple App Store Review Scraper | App Store review text, ratings and the version reviewed |
| Google Maps Scraper With Reviews Sentiment Analysis | Local business reviews with sentiment scoring |
| Pinterest Comment Scraper With Commenter Profile Enrichment | Pin comments with the commenter's public profile attached |
| YouTube Shorts Scraper With Creator Lead Enrichment | Shorts metadata with creator reach data |
| Reddit User Profile Posts And Comments Scraper | A Reddit user's posts, comments and account age |
π¬ Your feedback
Found a bug, or a book whose reviews come back short of what the page shows? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON β the book URL and your reviewerLeadOptions β are the fastest to reproduce and fix.