Goodreads Book Review Scraper
Pricing
from $0.63 / 1,000 book reviews
Goodreads Book Review Scraper
Scrape reader reviews from Goodreads - full review text, star rating, reviewer, likes, comment count and the reader's own shelves, with the book's own figures on every row.
Pricing
from $0.63 / 1,000 book reviews
Rating
0.0
(0)
Developer
Superslow Sloth
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Returns reader reviews from Goodreads: the full review text, the star rating, who wrote it, how many readers found it helpful, how many comments the thread carries, and the reader's own shelf tags. Every row also carries the book's own figures - title, author, ISBN, page count and Goodreads' aggregate rating - so a dataset of reviews across several books is usable without a second lookup.
Give it a book URL, a bare numeric id, or id-slug. All three work.
The full review text, not the truncated one
Goodreads renders a long review twice on the page: a visible copy cut off after a few hundred characters with a "...more" link, and a hidden copy holding all of it. This actor reads the hidden one. On the page measured, 22 of 30 reviews were truncated in the visible copy, so a scraper reading the obvious element would have silently delivered three quarters of its rows with the ends missing.
300 reviews per book is Goodreads' ceiling, not this actor's
Measured 2026-09-11: Goodreads paginates ten pages of thirty reviews and stops, whatever the book's review count says. A book showing 1,495 reviews serves 300 of them. Page 11 comes back empty rather than failing, and this actor stops there rather than paying for pages that repeat nothing.
If you need more than 300 rows, ask for more books - not a larger maxReviews.
Output fields
| Field | Notes |
|---|---|
id | Goodreads' review id. |
review_url | Canonical review URL, e.g. https://www.goodreads.com/review/show/1221838816. |
text | The full review text, entities decoded and paragraphing kept. |
rating | 1-5, or null when the reader did not rate the book. |
rating_label | The phrase Goodreads prints, e.g. really liked it. |
shelf_status | What the reader did instead of rating, e.g. is currently reading it. |
shelves | The reader's own shelf tags, e.g. ["dropped", "programming"]. Often empty. |
reviewed_at | The date as printed, e.g. Mar 08, 2015. |
likes | How many readers marked the review helpful. |
comment_count | Comments on the thread. null when Goodreads shows no comment control at all. |
of_another_edition | Whether the review is of a different edition of the same work. |
user_id, user_name, user_url, user_image_url | The reviewer. |
book_id, book_url, book_title | The book, repeated on every row. |
book_isbn, book_format, book_language, book_page_count, book_image_url | The edition. |
book_average_rating, book_rating_count, book_review_count | Goodreads' own aggregate figures. |
book_authors | The book's authors. |
What this source does not give
Stated rather than guessed. A missing value is always null, never 0 and
never an empty string, because a zero reads as a measurement that was never
taken.
- A review can have no rating. A reader who shelves a book without rating
it still writes a review, and Goodreads' own API reports that as
0. It is emitted here asnull, because a0in a rating column reads as one star to anything that averages it. Those reviews are excluded from any minimum-rating filter for the same reason. comment_countisnull, not0, when there is no comment control. No control at all is not the same as a thread with nothing in it.- No review timestamp, only a date. Goodreads prints
Mar 08, 2015with no time and no timezone, so no ISO timestamp is invented from it. - No spoiler flag, no "read dates", no edition of the reader's copy beyond
the
of_another_editionmarker. The legacy review markup does not carry them. book_review_countis Goodreads' own published figure and does not agree exactly with the number of reviews it will serve you. For the book measured it said 1,500 while the site's own connection reported 1,495 and it served 300.
How the reviews are actually fetched
Worth knowing, because it explains the field list. Goodreads' modern book page
carries the first thirty reviews and ignores ?page=2 - the second page
comes back with the same thirty review ids and the identical page token, so a
scraper paging that way delivers thirty duplicates per page and charges for
every one. Real pagination there needs a GraphQL key dug out of the page and a
query id dug out of a JavaScript bundle.
This actor uses Goodreads' older review endpoint instead, which paginates properly, needs no key, and returns server-rendered markup. Its pages do not overlap - pages 1, 4 and 10 of the same book shared no review ids at all.
Anti-bot behaviour
Goodreads answered a plain datacenter address with no proxy on 2026-09-11, so a proxy is optional. One trap is handled explicitly: a book that does not exist comes back with HTTP 200, carrying Goodreads' own not-found page. The status line cannot tell it from a real book. This actor tells them apart by the schema.org block - a real book page has one, the not-found page does not, and a refusal has neither the block nor the site's own payload. A missing book is reported and never retried; a refusal is retried on a fresh exit address.
Input
{"startUrls": [{ "url": "https://www.goodreads.com/book/show/3735293-clean-code" }],"maxReviews": 100,"minRating": "any","proxyConfiguration": { "useApifyProxy": true }}