Goodreads Book Scraper With Ratings Analytics avatar

Goodreads Book Scraper With Ratings Analytics

Pricing

from $5.99 / 1,000 results

Go to Apify Store
Goodreads Book Scraper With Ratings Analytics

Goodreads Book Scraper With Ratings Analytics

Goodreads Book Scraper extracts book details, ratings, review counts, author data, genres, publication info, descriptions, and URLs. Analyze rating trends and reader feedback for book research, market analysis, competitor insights, and publishing intelligence.

Pricing

from $5.99 / 1,000 results

Rating

0.0

(0)

Developer

Scrapier

Scrapier

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

0

Monthly active users

3 days ago

Last modified

Share

Goodreads Book Scraper — Search Results With Full Ratings Analytics

Goodreads Book Scraper With Ratings Analytics searches Goodreads by keyword or by pasting a Goodreads search URL, then enriches every matching book with its real 1-star to 5-star ratings breakdown, publication year, and genre list before filtering on rating, ratings volume, publication year, and genre. Each row is typed, normalized JSON — per-star counts and percentages, positive/critical rating share, reviews-to-ratings ratio, and a running popularity percentile — ready to pass directly to an LLM, load into a spreadsheet, or feed a monitoring pipeline. Results stream into the Output table live, one book at a time, as each search line is processed, so long jobs are useful before they finish.

What is Goodreads Book Scraper With Ratings Analytics?

It is a Goodreads book search scraper that looks up each matching book's own published ratings distribution instead of stopping at the average-rating number a plain search page shows. Give it a keyword (like stephen king) or a full Goodreads search URL, and it pages through the results, resolves each book's numeric Goodreads ID, and queries Goodreads' own book-stats data for the real per-star counts, total ratings, written-review count, publication year, and genre tags. From those it derives the star breakdown percentages, a positive-vs-critical rating share, a reviews-to-ratings ratio, and a running popularity percentile — the analytics layer that separates this Actor from a basic title/author/rating scrape. No Goodreads account or login is required; the Actor runs anonymous HTTP requests with a standard browser user agent.

  • 🔍 Search by keyword or paste a full Goodreads search URL (bookQueries, or the legacy urls key)
  • ⭐ Full 1-to-5-star ratings breakdown per book, read from Goodreads' own book data — never estimated
  • 🧮 Derived analytics: star percentage share, positive/critical rating share, reviews-to-ratings ratio, running popularity percentile
  • 🎯 Rating, ratings-volume, publication-year, and genre filters, applied after enrichment, before a row is ever saved
  • 📄 Live incremental output — rows land in the dataset as soon as each book is analyzed, not at the end of the run
  • 🧩 Backward-compatible with input JSON written for the original Goodreads Book Scraper (urls, resultsPerQuery legacy keys)

What data can you get with Goodreads Book Scraper With Ratings Analytics?

Every run returns one row per matching book, built from four groups of fields — the base search-result data, the ratings analytics, the popularity ranking, and publication/genre metadata.

Result TypeExtracted FieldsPrimary Use Case
Book search resultbookId, title, author, rating, ratingsCount, published, editions, url, coverUrl, sourceQuery, searchPage, scrapedAtBuilding a catalog of matching titles from a keyword or search URL
Ratings breakdown & analyticsaverageRating, ratingsTotal, ratingsSum, ratingsInDistribution, ratings1Starratings5Star, ratings1StarPercentratings5StarPercent, positiveRatingsPercent, criticalRatingsPercent, reviewsToRatingsRatio, textReviewsCount, analyticsStatusScoring sentiment and review quality beyond a single average-rating number
Popularity rankingpopularityPercentile, popularityPercentileBasis, popularityPercentileScopeRanking a book's ratings volume against everything else analyzed in the run
Publication & genre metadatapublicationYear, genresFiltering and segmenting results by era or category

Ratings Analytics

This is the field group a plain Goodreads search page never shows you. For every book the Actor resolves its Goodreads numeric ID and queries Goodreads' own stats data for the exact per-star counts (ratings1Starratings5Star), then derives each star's share of the total (ratings1StarPercentratings5StarPercent, rounded to 2 decimals), a combined positiveRatingsPercent (4–5 star share) and criticalRatingsPercent (1–2 star share), and a reviewsToRatingsRatio (written reviews divided by total ratings, rounded to 4 decimals). When the star distribution can't be resolved for a book, analyticsStatus is set to "unavailable" and the numeric fields are left null rather than filled with a guess.

{
"averageRating": 4.31,
"ratingsTotal": 1842007,
"ratings1Star": 12904,
"ratings5Star": 921003,
"ratings5StarPercent": 50.0,
"positiveRatingsPercent": 76.42,
"criticalRatingsPercent": 3.11,
"reviewsToRatingsRatio": 0.0312,
"analyticsStatus": "ok"
}

Popularity Percentile Ranking

Each book also gets a popularityPercentile — a running rank of its ratingsTotal against every other book already analyzed in the comparison pool at the moment that row is saved. popularityPercentileBasis reports how many books were in that pool, and popularityPercentileScope records whether the pool was the whole run or just that search line. Use it to spot which title in a batch of search results is disproportionately more (or less) read than the rest, without needing a second pass over the data.

How does Goodreads Book Scraper With Ratings Analytics differ from the official Goodreads API?

Goodreads does not currently offer a usable public developer API to build this against. Goodreads stopped issuing new developer API keys on December 8, 2020 and moved to retire the program entirely, disabling existing keys for accounts that had gone inactive — reported by Pixel Envy and Debugger/Medium at the time, and there has been no public re-launch since (checked 2026-08-15).

That leaves scraping — with its own real costs — as the practical option:

  • Query-surface volatility. Goodreads' search-results HTML (table.tableList, .bookTitle, .minirating) can change without notice; a layout tweak breaks a hand-rolled scraper silently.
  • Anti-bot escalation. Goodreads sometimes answers a results page with a small interstitial instead of the listing, and the internal book-stats endpoint can return a WAF challenge (status codes 202/403/429/500/502/503, or an awsWafIntegration response body). This Actor detects both and retries on a fresh connection before falling back to Apify Proxy.
  • Proxy cost and management. Sustained scraping at volume typically needs rotating IPs; running that infrastructure yourself is ongoing overhead this Actor absorbs.

Building and maintaining this yourself means owning the HTML parsing, the retry/escalation logic, and the proxy rotation — this Actor exists so you don't have to.

What is the difference between a Goodreads book search scraper and a Goodreads ratings analytics scraper?

A plain Goodreads book search scraper returns what the results page itself prints: a title, an author, a text-formatted average rating (like "4.28"), and a text-formatted ratings count (like "3,224"). It stops there because that's all the search-results HTML contains. A ratings analytics scraper goes one step further: for every book it found, it separately queries Goodreads' own per-book stats data for the full 1-to-5 star count distribution, the written-review count, and the genre tags, then derives the share metrics and popularity ranking from those real numbers.

The distinction matters when you're deciding between titles with the same average rating — a 4.3 built on 90% five-star reviews reads very differently from a 4.3 with a heavier one- and two-star tail, and a title/author list alone can't tell you which is which.

Goodreads Book Scraper With Ratings Analytics returns both: the raw search-result fields (rating, ratingsCount as Goodreads prints them) sit in the same row as the enriched analytics object (averageRating, ratings1Starratings5StarPercent, positiveRatingsPercent, criticalRatingsPercent), so you never have to choose between the two.

How to scrape Goodreads with Goodreads Book Scraper With Ratings Analytics?

  1. Open Goodreads Book Scraper With Ratings Analytics on the Apify Store and click Try for free (or Run, if you've already added it to your account).
  2. Enter one or more lines in Book searches to analyze (bookQueries) — a search phrase like historical fiction, or a full Goodreads search URL.
  3. Set the real query controls you need: Books to keep per search (booksPerQuery), the rating/ratings-volume/publication-year/genre filters, and Popularity percentile is measured against (percentileScope).
  4. Click Start to launch the run.
  5. Watch rows appear in the Output tab as they're saved, or wait for the run to finish and export the dataset as JSON, CSV, Excel, or the other formats the Apify platform supports.

How to run multiple searches in one job

Add one line per search to bookQueries — a mix of keywords and full Goodreads search URLs is fine in the same run. Every line is analyzed in turn (search lines are processed one after another, not in parallel) and every book that line produces is tagged with the exact query string in sourceQuery, so a multi-line run's Output table can be split back apart by search afterward. There is no separate concurrency setting exposed for this Actor.

⬇️ Input

Configure the run with the Book searches to analyze field and, optionally, the ratings-analytics and filter controls below. bookQueries accepts either a plain keyword or a full Goodreads search URL — mix both freely, one per line.

ParameterRequiredTypeDescriptionExample Value
bookQueriesNoarray (stringList)One search phrase (for example historical fiction or stephen king) or one full Goodreads search URL per line. Every line is analyzed separately and its books are tagged with the query that produced them.["historical fiction", "stephen king"]
booksPerQueryNointeger, min 1, max 10000, default 10How many books that PASS your filters should be kept for each search line. Paging continues until this many books match or the search runs out of results.25
percentileScopeNostring, enum run | query, default runSets the comparison pool for popularityPercentile. run compares against all books analyzed so far in the run; query compares only against books from the same search line. It is a RUNNING percentile — popularityPercentileBasis reports how many books that pool held at save time."query"
ratingsBreakdownStyleNostring, enum counts_and_percent | counts_only | percent_only, default counts_and_percentChooses which star columns are written. counts_only keeps ratings1Starratings5Star; percent_only keeps ratings1StarPercentratings5StarPercent; the default writes both."counts_and_percent"
minAverageRatingNonumber, min 0, max 5Keep only books whose average rating is at least this value. Leave empty to keep every rating.4.2
minRatingsTotalNointeger, min 0Keep only books with at least this many ratings in total. Leave empty to keep every book.5000
publishedFromYearNointeger, min 0, max 2100Keep only editions published in this year or later, read from the book's own publication date. Leave empty for no lower bound.2015
publishedToYearNointeger, min 0, max 2100Keep only editions published in this year or earlier. Leave empty for no upper bound.2023
genresIncludeNoarray (stringList)One genre or shelf name per line. A book is kept when at least one of its own genres matches any line (case-insensitive, whole-word match, so Fiction matches Historical Fiction but not Nonfiction). Leave empty to accept every genre.["Fantasy"]
genresExcludeNoarray (stringList)One genre or shelf name per line. A book is dropped as soon as any of its own genres matches a line. Applied after the keep-list.["Romance"]
urlsNoarray (stringList) — legacyAccepted so an input written for the original Goodreads Book Scraper still runs unchanged. Used only when bookQueries is empty.[]
resultsPerQueryNointeger, min 1, max 10000 — legacyAccepted so an existing input JSON still runs. Used only when booksPerQuery is empty.25
delayMsNointeger, min 0, max 60000, default 1000Milliseconds to wait between two pages of the same search.1000
proxyConfigurationNoobject (Apify Proxy configuration)Optional. Leave off and the run goes out on the platform's own connection; turn it on to route requests through Apify Proxy with the groups and country you choose.{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}

Example input JSON:

{
"bookQueries": ["historical fiction", "stephen king"],
"booksPerQuery": 25,
"percentileScope": "run",
"ratingsBreakdownStyle": "counts_and_percent",
"minAverageRating": 4,
"minRatingsTotal": 5000,
"publishedFromYear": 2015,
"publishedToYear": 2023,
"genresInclude": ["Fantasy"],
"genresExclude": ["Romance"],
"delayMs": 1000,
"proxyConfiguration": { "useApifyProxy": false }
}

⚠️ Common pitfall: leaving proxyConfiguration off does not guarantee the run stays off Apify Proxy. If a direct request comes back blocked or with a retryable status, the Actor automatically escalates to an Apify Proxy URL for the rest of the run — this is by design (it's how the Actor keeps runs going through Goodreads' interstitials), but it means proxy usage can occur even when the toggle is left false. Also note booksPerQuery and urls/resultsPerQuery only take effect when their non-legacy counterpart is empty, and delayMs only pauses between pages of the same search line, not between different lines.

⬆️ Output

Every matching book is pushed to the Output dataset as one flat JSON row the moment it passes your filters — not batched at the end of the run. Export the dataset as JSON, CSV, Excel, XML, RSS, or HTML directly from the Apify Console or via the API. A run also writes a SUMMARY.json record (total items, filters applied, books filtered out and why) to the run's key-value store, separate from the dataset.

Scraped results

[
{
"bookId": "21853621",
"title": "The Nightingale",
"author": "Kristin Hannah",
"rating": "4.55",
"ratingsCount": "1,842,007 ratings",
"published": "2015",
"editions": "312 editions",
"url": "https://www.goodreads.com/book/show/21853621-the-nightingale",
"coverUrl": "https://images-na.ssl-images-amazon.com/images/S/nightingale.jpg",
"averageRating": 4.55,
"ratingsTotal": 1842007,
"ratingsSum": 8386560,
"textReviewsCount": 97442,
"ratings1Star": 9210,
"ratings2Star": 18420,
"ratings3Star": 165780,
"ratings4Star": 626282,
"ratings5Star": 1022315,
"ratings1StarPercent": 0.5,
"ratings2StarPercent": 1.0,
"ratings3StarPercent": 9.0,
"ratings4StarPercent": 34.0,
"ratings5StarPercent": 55.5,
"positiveRatingsPercent": 89.5,
"criticalRatingsPercent": 1.5,
"ratingsInDistribution": 1842007,
"reviewsToRatingsRatio": 0.0529,
"popularityPercentile": 100.0,
"popularityPercentileBasis": 1,
"popularityPercentileScope": "run",
"publicationYear": 2015,
"genres": ["Historical Fiction", "Fiction", "World War II"],
"analyticsStatus": "ok",
"sourceQuery": "historical fiction",
"searchPage": 1,
"scrapedAt": "2026-08-15T09:12:04Z"
},
{
"bookId": "18143977",
"title": "All the Light We Cannot See",
"author": "Anthony Doerr",
"rating": "4.32",
"ratingsCount": "1,203,884 ratings",
"published": "2014",
"editions": "289 editions",
"url": "https://www.goodreads.com/book/show/18143977-all-the-light-we-cannot-see",
"coverUrl": "https://images-na.ssl-images-amazon.com/images/S/light.jpg",
"averageRating": 4.32,
"ratingsTotal": 1203884,
"ratingsSum": 5200784,
"textReviewsCount": 108211,
"ratings1Star": 12038,
"ratings2Star": 30097,
"ratings3Star": 174562,
"ratings4Star": 469515,
"ratings5Star": 517672,
"ratings1StarPercent": 1.0,
"ratings2StarPercent": 2.5,
"ratings3StarPercent": 14.5,
"ratings4StarPercent": 39.0,
"ratings5StarPercent": 43.0,
"positiveRatingsPercent": 82.0,
"criticalRatingsPercent": 3.5,
"ratingsInDistribution": 1203884,
"reviewsToRatingsRatio": 0.0899,
"popularityPercentile": 50.0,
"popularityPercentileBasis": 2,
"popularityPercentileScope": "run",
"publicationYear": 2014,
"genres": ["Historical Fiction", "Fiction", "War"],
"analyticsStatus": "ok",
"sourceQuery": "historical fiction",
"searchPage": 1,
"scrapedAt": "2026-08-15T09:12:11Z"
},
{
"bookId": "19063",
"title": "The Book Thief",
"author": "Markus Zusak",
"rating": "4.39",
"ratingsCount": "2,671,209 ratings",
"published": "2005",
"editions": "471 editions",
"url": "https://www.goodreads.com/book/show/19063.The_Book_Thief",
"coverUrl": "https://images-na.ssl-images-amazon.com/images/S/bookthief.jpg",
"averageRating": 4.39,
"ratingsTotal": 2671209,
"ratingsSum": 11730672,
"textReviewsCount": 145980,
"ratings1Star": 21370,
"ratings2Star": 53424,
"ratings3Star": 320545,
"ratings4Star": 856787,
"ratings5Star": 1419083,
"ratings1StarPercent": 0.8,
"ratings2StarPercent": 2.0,
"ratings3StarPercent": 12.0,
"ratings4StarPercent": 32.1,
"ratings5StarPercent": 53.1,
"positiveRatingsPercent": 85.2,
"criticalRatingsPercent": 2.8,
"ratingsInDistribution": 2671209,
"reviewsToRatingsRatio": 0.0546,
"popularityPercentile": 33.3,
"popularityPercentileBasis": 3,
"popularityPercentileScope": "run",
"publicationYear": 2005,
"genres": ["Historical Fiction", "Fiction", "Young Adult"],
"analyticsStatus": "ok",
"sourceQuery": "historical fiction",
"searchPage": 1,
"scrapedAt": "2026-08-15T09:12:19Z"
}
]

Two field pairs are worth telling apart when you consume this data: rating/ratingsCount are the raw, comma-formatted text Goodreads prints on the search-results page, while averageRating/ratingsTotal are the enriched numeric values looked up from Goodreads' own book stats — use the latter for any sorting, filtering, or math. If ratingsBreakdownStyle is set to counts_only or percent_only, the fields for the other style are omitted from the row entirely, not just left null.

How can I use the data extracted with Goodreads Book Scraper With Ratings Analytics?

  • 📚 Book bloggers and content curators: build "best of" or "hidden gems" lists filtered by minAverageRating and genresInclude, then sort candidates by positiveRatingsPercent instead of the raw average alone.
  • 🤖 AI engineers and LLM developers: the agent issues a search, receives structured JSON with per-star breakdowns and genre tags, and passes it to the model as grounded context for a reading-recommendation or book-QA agent.
  • 📊 Publishing and market researchers: track ratings1StarPercentratings5StarPercent and reviewsToRatingsRatio across an author's catalog or a genre to compare sentiment quality, not just popularity.
  • 🛒 Catalog and affiliate-site operators: populate a book catalog page with coverUrl, editions, published, and rating data pulled straight from genresInclude-filtered search results.

How do you monitor Ratings Analytics over time?

Ratings analytics change as a book collects more ratings and reviews — the star distribution shifts, averageRating drifts, and a title's popularityPercentile moves relative to whatever else is in the comparison pool. Monitoring that discipline means running the same bookQueries set on a recurring schedule and diffing the new rows against the previous run's, keyed by the stable bookId.

The fields worth diffing are averageRating, ratingsTotal, positiveRatingsPercent, criticalRatingsPercent, and popularityPercentile — a jump in ratingsTotal combined with a drop in positiveRatingsPercent flags a title whose newer reviews are running colder than its older ones, which a single-snapshot average rating would hide.

A concrete workflow: schedule a run across your tracked bookQueries (for example, an author's name or a genre keyword), extract averageRating, ratingsTotal, and the star-share fields for each bookId, compare against the same book's row from the previous run's dataset, and alert when a field moves past a threshold you set. Set up the recurring run with Apify Schedules in the Console (or via the API), and pull each run's dataset with apify_client once it finishes to do the comparison.

Integrate Goodreads Book Scraper With Ratings Analytics and automate your workflow

Goodreads Book Scraper With Ratings Analytics works with any language or tool that can send an HTTP request through the Apify API.

REST API with Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_API_TOKEN>")
run = client.actor("your-username/goodreads-book-scraper-with-ratings-analytics").call(
run_input={
"bookQueries": ["historical fiction"],
"booksPerQuery": 25,
"minAverageRating": 4.0,
}
)
for book in client.dataset(run["defaultDatasetId"]).iterate_items():
print(book["title"], book["averageRating"], book["positiveRatingsPercent"])

Replace your-username with the Apify account this Actor is published under, and use your own Apify API token.

Scheduled monitoring and delivery

The Actor has no built-in webhook or polling feature of its own; use the Apify platform's real mechanisms instead. Set up a recurring Schedule in the Apify Console (or via the Schedules API) to re-run the same input on a cadence, and attach a webhook to the run's ACTOR.RUN.SUCCEEDED event to push a notification or trigger a downstream job as soon as each run's dataset is ready.

Scraping publicly accessible Goodreads pages for book titles, ratings, and review counts is generally lawful — this Actor returns only what any visitor to Goodreads.com can already see. What it collects is business and product-catalog data (book listings and their published ratings), not personal data about identifiable individuals, so this falls under Goodreads' Terms of Service and database-rights considerations rather than personal-data regimes like GDPR or CCPA, which govern data about identified or identifiable people. Scraping the same public data for AI-training purposes and for operational monitoring carry different risk profiles worth weighing separately. Consult your legal team for commercial use cases involving bulk data storage or redistribution.

❓ Frequently asked questions

Can I paste a Goodreads search URL instead of typing a keyword?

Yes. Each line in bookQueries (or the legacy urls field) can be either a plain search phrase or a full Goodreads search URL (any URL containing goodreads.com/search). The Actor detects which type each line is and pages through it accordingly, tagging every result with the exact query string in sourceQuery.

How many books does the Actor return per search, and what happens when a search runs out of results?

booksPerQuery (default 10, max 10000) sets how many books that pass your filters are kept per search line; paging continues until that many are found or the search runs out of pages. As a hard safety cap in the code, paging for any single search line stops after 500 pages regardless of booksPerQuery — if a search line can't produce enough matching books within that many pages, the run moves on with whatever it found.

How does the Actor handle Goodreads' anti-bot measures?

It opens Goodreads' landing page once on a fresh connection before requesting results (a real page carries the results table and is tens of kilobytes; a short response is treated as an interstitial, not "no more results"), retries on the retryable HTTP statuses (202, 403, 429, 500, 502, 503), and escalates to Apify Proxy after repeated failures on the same connection. The ratings-lookup endpoint separately detects WAF-challenge responses and, if its stored API key stops working, re-reads a fresh one from Goodreads' own JavaScript bundles before retrying once.

Does Goodreads Book Scraper With Ratings Analytics extract the full ratings breakdown?

Yes — every book carries its 1-to-5 star breakdown in ratings1Starratings5Star and ratings1StarPercentratings5StarPercent (controlled by ratingsBreakdownStyle), plus positiveRatingsPercent, criticalRatingsPercent, and reviewsToRatingsRatio. When the lookup fails for a particular book, analyticsStatus is set to "unavailable" and these fields are left null rather than estimated.

What happens when a book's ratings breakdown can't be looked up?

analyticsStatus on that row is set to "unavailable" and the analytics fields are null. If any rating/ratings-volume/publication-year/genre filter is active, a book whose data couldn't be looked up is dropped rather than let through — the run log reports it under a no_data filter reason.

Can I reuse an input JSON written for the original Goodreads Book Scraper?

Yes. urls and resultsPerQuery are accepted as legacy keys and are used automatically whenever bookQueries and booksPerQuery are left empty, so an existing input JSON keeps working unchanged. The new field names always take priority when both are supplied.

How do I monitor a book's ratings and popularity over time?

Schedule the same bookQueries to re-run on a cadence using Apify Schedules, extract averageRating, ratingsTotal, the star-share fields, and popularityPercentile for each bookId, and diff them against the previous run's dataset to flag meaningful movement.

Does Goodreads Book Scraper With Ratings Analytics work with Claude, ChatGPT, and AI agent frameworks?

It's callable as a standard HTTP endpoint through the Apify API by any agent framework that can make a tool call — an agent issues a search via apify_client (or a raw API request), receives the structured JSON rows back, and uses them as grounded context for a book-recommendation or research answer.

Can I use this without managing proxies or a Goodreads account?

Yes. No Goodreads account or login is required, and proxyConfiguration is entirely optional — leave it off and the Actor runs on the platform's own connection by default, automatically escalating to Apify Proxy on its own if a request comes back blocked.

Do the star percentage columns always add up to exactly 100%?

Not always exactly. Each of ratings1StarPercentratings5StarPercent is computed independently as that star's share of the total, rounded to 2 decimals, so the five values can sum to a hair above or below 100% due to rounding — treat each percentage as accurate on its own rather than expecting the five to sum perfectly.

💬 Your feedback

Found a bug, or a field that's missing something you need? Open an issue from this Actor's page on the Apify Console (the Issues tab) or reach out to Scrapier directly — reports like this are what keep the ratings-lookup logic working as Goodreads changes its site.