Goodreads Book Review Scraper avatar

Goodreads Book Review Scraper

Pricing

from $0.63 / 1,000 book reviews

Go to Apify Store
Goodreads Book Review Scraper

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

Superslow Sloth

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

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

FieldNotes
idGoodreads' review id.
review_urlCanonical review URL, e.g. https://www.goodreads.com/review/show/1221838816.
textThe full review text, entities decoded and paragraphing kept.
rating1-5, or null when the reader did not rate the book.
rating_labelThe phrase Goodreads prints, e.g. really liked it.
shelf_statusWhat the reader did instead of rating, e.g. is currently reading it.
shelvesThe reader's own shelf tags, e.g. ["dropped", "programming"]. Often empty.
reviewed_atThe date as printed, e.g. Mar 08, 2015.
likesHow many readers marked the review helpful.
comment_countComments on the thread. null when Goodreads shows no comment control at all.
of_another_editionWhether the review is of a different edition of the same work.
user_id, user_name, user_url, user_image_urlThe reviewer.
book_id, book_url, book_titleThe book, repeated on every row.
book_isbn, book_format, book_language, book_page_count, book_image_urlThe edition.
book_average_rating, book_rating_count, book_review_countGoodreads' own aggregate figures.
book_authorsThe 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 as null, because a 0 in 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_count is null, not 0, 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, 2015 with 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_edition marker. The legacy review markup does not carry them.
  • book_review_count is 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 }
}