Apple App Store Review Scraper & App Ratings Summary
Pricing
from $4.99 / 1,000 results
Apple App Store Review Scraper & App Ratings Summary
๐ฑ Apple App Store Review Scraper pulls app reviews at scale โ ratings, text, titles, dates, versions, locales & user names. ๐ Filter by country, date or rating. ๐ Export to CSV/JSON. ๐ Perfect for ASO, sentiment & competitor analysis, UX research, and support triage.
Pricing
from $4.99 / 1,000 results
Rating
0.0
(0)
Developer
Scraper Engine
Maintained by CommunityActor stats
0
Bookmarked
20
Total users
7
Monthly active users
12 days ago
Last modified
Categories
Share
App Store Review Scraper โ Ratings Summary and Review Text
App Store Review Scraper & App Ratings Summary extracts public Apple App Store customer reviews โ star rating, review title, full text, app version, reviewer name and UTC-normalised date โ and adds one summary row per app carrying the real 1โ โ5โ rating counts, the average derived from them, the written-review vs star-rating split and the store listing record. Structured JSON, no HTML parsing. Paste an App Store link and press Start.
What is App Store Review Scraper & App Ratings Summary?
It is an Apify Actor that reads Apple's public customer review feed for any app and, alongside the review rows, emits a single app_summary row per app built from Apple's own keyless rating-distribution surface and the iTunes lookup record. Reviews tell you what users say; the summary gives you the denominator they came from.
No Apple ID, App Store Connect account or API key is required. Every surface it reads is public โ the only credential involved is your Apify token.
It is built for product teams tracking release sentiment, ASO and competitive analysts benchmarking rival apps market by market, and developers piping review text and rating distributions into their own pipelines.
What App Store review and ratings data is publicly available to scrape?
Apple publishes customer reviews, the full star distribution, and the complete listing record on public endpoints that any visitor can reach without signing in. What is gated is developer-side telemetry and reviewer identity beyond the display name.
| Data Category | Any visitor (no login) | Gated |
|---|---|---|
| Star rating, review title, full review text, app version | โ Public review feed | โ |
| Reviewer display name and review permalink | โ Public review feed | โ |
| 1โ โ5โ rating counts and total ratings per storefront | โ Public rating surface | โ |
| Listing record โ price, seller, genres, version, release notes, size, age rating | โ Public lookup endpoint | โ |
| Top-chart position, privacy nutrition labels, similar apps | โ Public store page | โ |
| Reviews beyond the newest 500 per storefront | โ | Apple's feed stops paginating |
| Reviewer's real name, email or Apple ID | โ | Never published by Apple |
| Installs, revenue, retention, crash data | โ | App Store Connect, developer's own app only |
App Store Review Scraper & App Ratings Summary only returns publicly visible data โ what any visitor sees. Nothing behind a login wall.
โ ๏ธ Apple's 500-review ceiling, and what the summary does about it
Apple's public review feed hard-stops at 500 reviews per app per storefront. It serves 50 reviews per page and refuses page 11 with an HTTP 400. That is Apple's limit, not this Actor's, and it applies here exactly as it does to any scraper reading the same feed. reviewsPerApp therefore maxes out at 500 in the schema, and the legacy max_review key โ which accepts up to 10,000 โ is clamped down to 500 with a log line rather than erroring.
The way to exceed 500 review texts for one app is to collect it across several storefronts. Each country code carries its own independent 500-review budget, so /us/, /gb/, /de/ and /au/ give you four separate sets for the same app.
The ratings summary is not subject to that ceiling. The star histogram comes from Apple's aggregate rating surface, not from the paginated feed, so ratingCount, starRatings1โstarRatings5 and everything derived from them describe every rating Apple holds for that app in that storefront โ millions of them, if that is what the app has โ while the review rows in the same run are still capped at 500. That is the point of the summary row: you get the true denominator even though you can only read a sample of the numerators.
Is the ratings summary Apple's number or a calculated one?
Both, and the distinction is worth being precise about because it is the easiest thing in this dataset to misread.
Apple's own published counts, passed through unchanged: ratingCount (total star ratings in that storefront), starRatings1 through starRatings5 (the five bucket counts), writtenReviewCount (how many of those ratings carry written text). From the lookup record, averageUserRating, averageUserRatingCurrentVersion and userRatingCount are also Apple's own values.
Calculated by the Actor from Apple's counts: ratingAverageDerived is the arithmetic mean over the five bucket counts. Apple's own average field on that surface is rounded to a half star, which makes it useless for tracking movement, so the average is recomputed from the raw counts instead. negativeSharePct (1โ
+2โ
share), positiveSharePct (4โ
+5โ
share), writeRatePct (written reviews รท total ratings) and histogramSampleSize (the sum of the five buckets) are calculated the same way.
What none of these are: an aggregate of the reviews this run collected. The summary is computed over Apple's full storefront rating population, not over your 20 or 500 sampled review rows. histogramSampleSize is the sum of Apple's buckets โ not your row count. If you want a sample-based average, compute it yourself from ratingNumber on the review rows, and expect it to differ from ratingAverageDerived, because the newest 500 reviews are not a random draw from a lifetime of ratings.
What data can I extract with App Store Review Scraper & App Ratings Summary?
Runs produce two kinds of row โ one app_summary row per app, then up to 500 review rows per app per storefront โ plus uncharged accounting rows for links that failed. Every row carries type, isSummary, appId, country, appStoreUrl and scrapedAt, so the shapes are trivially separable.
| Field Name | Description |
|---|---|
type | Row kind: app_summary, review or error |
isSummary | true only on the summary row |
appId | Numeric App Store ID parsed from the URL |
country | Storefront the row describes โ it differs between row types, see the input notes |
appStoreUrl | The listing URL this row came from |
scrapedAt | UTC ISO collection timestamp |
ratingCount | Apple's total star ratings for the app in that storefront |
starRatings1 | Number of 1โ ratings |
starRatings2 | Number of 2โ ratings |
starRatings3 | Number of 3โ ratings |
starRatings4 | Number of 4โ ratings |
starRatings5 | Number of 5โ ratings |
histogramSampleSize | Sum of the five bucket counts โ null if the sum is zero |
ratingAverageDerived | Mean rating computed from the five buckets, rounded to 3 decimals |
negativeSharePct | Percentage of ratings that are 1โ or 2โ , 2 decimals |
positiveSharePct | Percentage of ratings that are 4โ or 5โ , 2 decimals |
writtenReviewCount | Ratings that carry written text, as Apple reports it |
writeRatePct | writtenReviewCount รท ratingCount as a percentage, 3 decimals |
appName | App title from the lookup record |
sellerName | Publisher name |
sellerUrl | Publisher website |
price | Numeric price in the storefront currency |
formattedPrice | Price as Apple formats it, e.g. "Free" |
currency | ISO currency code for price |
primaryGenre | Main category |
genres | Array of all category names |
currentVersion | Latest published version string |
releaseDate | First release timestamp |
currentVersionReleaseDate | Release timestamp of the current version |
releaseNotes | "What's New" text for the current version |
fileSizeMb | Download size converted from bytes to MB, 1 decimal |
minimumOsVersion | Minimum iOS version required |
contentAdvisoryRating | Age rating, e.g. "12+" |
artworkUrl512 | 512px app icon URL |
averageUserRating | Apple's own lifetime average for that storefront |
averageUserRatingCurrentVersion | Apple's own average for the current version |
userRatingCount | Apple's own rating count from the lookup record |
artistId | Numeric developer ID |
bundleId | Reverse-DNS bundle identifier |
chartRank | Top-chart position, or null if the app is not charting |
chartCategory | Chart the rank belongs to, e.g. "Music" |
chartType | Chart variant parsed from the chart link, e.g. "top-free" |
privacyTypes | Array of Apple privacy nutrition-label group titles |
similarApps | Array of objects: appId, name, developer, rating, ratingCount |
moreByDeveloperCount | How many other apps the developer shelf listed |
reviewId | Apple's review identifier โ the deduplication key |
author | Reviewer display name |
rating | Star rating exactly as Apple's feed returns it, a string |
ratingNumber | The same rating parsed to an integer |
version | App version the reviewer was running |
title | Review headline |
content | Full review body |
date | Apple's publish timestamp, in the storefront's local offset |
dateIso | The same instant normalised to UTC, or null if unparseable |
reviewUrl | Permalink to the review |
reviewerProfileUrl | Link to the reviewer's Apple review history |
errorReason | On accounting rows only: no_data, invalid_input or fetch_failed |
errorDetail | On accounting rows only: the failure message, truncated to 300 characters |
Rating distribution fields
The histogram block is the reason this variant exists. Instead of a single rounded star score you get the five raw bucket counts and four measures derived from them. negativeSharePct and positiveSharePct are the two that move first when a release goes wrong โ a lifetime average built on millions of ratings barely twitches, while the 1โ
โ2โ
share is visible within days. writeRatePct tells you what fraction of raters bothered to type anything, which is the correction factor you need before treating review text as representative of the rating population.
All twelve histogram fields come back as null rather than a guessed number when Apple's rating surface cannot be reached for that app and storefront. Nothing is faked, and the run logs a warning naming the app and country.
Review fields
rating is Apple's string and ratingNumber is the same value as an integer, so you can aggregate without a cast while keeping the raw form. version is the build the reviewer had installed โ group by it to get release-over-release sentiment rather than a lifetime score that never moves. date preserves Apple's storefront-local offset unchanged and dateIso is the same instant in UTC, so a multi-market dataset lands on one timeline without you writing timezone logic.
Reviews are de-duplicated on reviewId within each app, so a review appearing on two feed pages is written once. Non-review entries in the feed are filtered out on the presence of Apple's rating field rather than by array position โ which means the newest review of every app survives, where the naive index-slice approach silently deletes it.
Store context fields
Set includeStoreContext to add chartRank, chartCategory, chartType, privacyTypes, similarApps and moreByDeveloperCount, read from the app's store page. An app that is not currently charting returns null for rank โ never a fabricated 0. This toggle costs a full store-page fetch per app and is off by default.
๐ค Add-on: Need additional app or review data?
Google Play Store Reviews Scraper covers the Android half of the same question, which is what you need before claiming a sentiment shift is real rather than platform-specific. Google Maps Scraper with Reviews Sentiment Analysis and Goodreads Review Scraper with Reviewer Lead Enrichment apply the same review-plus-aggregate shape to other review corpora.
Why not build this yourself?
Apple's review feed is public and needs no key, which makes a first-pass scraper look like an afternoon's work. It is the second pass that costs.
The feed mixes non-review entries into the same array as reviews, so they have to be filtered on a field rather than sliced off by index โ get that wrong in either direction and you either keep junk or delete the newest review of every app. It repeats reviews across page boundaries, so a naive collector over-reports. It stops at page 11 with an HTTP 400 rather than an empty page, which reads as a crash unless you know it is the ceiling. Timestamps arrive with storefront-local offsets that must be normalised before any cross-market comparison is valid. App IDs have to be parsed out of several URL shapes.
The rating surface adds its own problems: it soft-blocks with an HTTP 200 carrying an HTML shell instead of JSON, so success cannot be keyed on the status code, and it needs an Apple storefront header that has to be tried in several forms before it answers. The store page replies with a 301 to its slug URL, which a client that does not follow redirects turns into an empty body.
None of these are individually hard. Together they are why a hand-rolled version returns a review count that does not match the App Store's own.
How to use App Store Review Scraper & App Ratings Summary
The Actor runs on Apify. Start it from the Apify Console or call it through the Apify API โ no other account, signup or key is involved.
- Open the Actor on Apify and click Try for free
- Paste one or more App Store links into App Store listing links โ full URLs containing
/id<number>, one per entry - Set Reviews to collect per app: 20 for a quick sample, 500 for everything Apple exposes per storefront
- Leave Star histogram + derived average and Store listing record on; tick Chart rank, privacy labels & similar apps if you want store context
- Optionally set Storefront for the summary to hold the rating basis constant across a mixed list
- Click Start, then export as JSON, CSV or Excel, or read the dataset through the Apify API
The two-letter country in each URL selects the storefront the reviews come from. https://apps.apple.com/gb/app/.../id324684580 returns UK reviews; swap /gb/ for /de/ for the German store's separate set.
How to scale to bulk app extraction
appStoreUrls is a list โ add as many App Store links as you want and they are processed one after another in a single run, with the connection tier the run has already settled on carried across apps rather than rediscovered per app. The legacy url and urls lists are merged in ahead of it and duplicate URL strings are dropped. To build a market-segmented dataset, list the same app under several country codes; each entry produces its own summary row and its own independent 500-review budget, and the country field tells you which is which.
What can you do with App Store review and ratings data?
- ๐ A product manager watching a release lists the app under
/us/and/gb/, then comparesnegativeSharePctday over day againstratingAverageDerivedโ the 1โ โ2โ share moves within days of a bad build, while the average barely shifts. - ๐ An ASO analyst benchmarking rivals enables
includeStoreContextacross a competitor list and readschartRank,chartCategoryandpositiveSharePctinto one table to see who is charting on what sentiment. - ๐ A support lead triaging a regression filters review rows on
ratingNumberof 1 or 2 and groupscontentbyversionto find the exact build that introduced the complaint. - ๐ A localisation manager runs the same app across ten storefronts and sorts by
ratingAverageDerivedandwriteRatePctto find the market where satisfaction is lowest and feedback volume is highest. - ๐ค An AI engineer building a product-feedback agent indexes
titleandcontentinto a vector store keyed byappId,versionanddateIso, then attaches the summary row so the agent can answer "is this complaint representative?" with the real rating distribution rather than a guess.
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 App Store Review Scraper & App Ratings Summary handle rate limits and blocking?
The Apple endpoints it reads are public, so the run starts on a direct connection with no proxy at all and only escalates if a request is refused: direct โ Apify datacenter โ Apify residential, with each switch logged. If the datacenter tier is unavailable on your account the run skips straight to residential and locks there. The tier is remembered for the rest of the run rather than being rediscovered app by app, and one sticky session identifier is used throughout.
Retries differ by surface, because the failure modes differ. The review feed retries up to five times per tier before escalating, then backs off up to three rounds on residential before giving up on that page. The rating surface retries across four rounds, trying three storefront-header variants each round, and treats an HTTP 200 carrying Apple's HTML shell as a block rather than a success โ bodies are validated as JSON, not trusted on status code. The store page retries four times and follows redirects. The lookup record retries three times.
There is no CAPTCHA solving here, because none of these endpoints presents one. When a surface stays unavailable, its fields come back null with a warning in the log; when a whole link fails, it becomes an uncharged accounting row rather than a silent gap.
โฌ๏ธ Input
Nothing is marked required. Twelve parameters are accepted, and the six under "Compatibility & advanced" exist so any configuration written for the base Actor still runs here unchanged.
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
appStoreUrls | No | array | One App Store URL per entry, e.g. https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580. The two-letter country in the URL selects the storefront the reviews come from. | ["https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580"] |
reviewsPerApp | No | integer | Apple's public review feed hard-stops at 500 reviews per app per storefront (page 11 returns HTTP 400), so 500 is the real ceiling โ not a soft limit. Minimum 1, maximum 500. Default 20. | 500 |
includeRatingHistogram | No | boolean | Adds the real 1โ
/2โ
/3โ
/4โ
/5โ
rating counts, the average derived from those counts (Apple's own field here is rounded to a half star), plus negativeSharePct, positiveSharePct, the written-review count and the write rate. Default true. | true |
includeAppDetails | No | boolean | Adds the app's listing data: name, seller, price + currency, genres, version, release date, release notes, download size, minimum iOS version, age rating, icon URL and Apple's own lifetime/current-version rating averages. Default true. | true |
includeStoreContext | No | boolean | Adds a second request to the app's store page for its top-chart rank + category, Apple privacy nutrition-label groups, and up to 10 similar apps with their own ratings. Unranked apps return null for rank โ never a fake 0. Slower, so it is off by default. Default false. | true |
summaryCountry | No | string | Two-letter country code deciding which storefront's rating numbers the summary reports โ ratings differ a lot by market. Leave empty to use each app URL's own country. Default "". | "gb" |
url | No | array | Original url key. Values supplied here are merged ahead of appStoreUrls. | ["https://apps.apple.com/gb/app/spotify-music-and-podcasts/id324684580"] |
urls | No | array | Original undocumented urls key, now declared. Merged after url. | [] |
max_review | No | integer | Original max_review key; when set it overrides reviewsPerApp. Minimum 1, maximum 10000 โ values above 500 are accepted and clamped to 500, and the clamp is logged. | 500 |
max_pages | No | integer | Original max_pages key. Each page holds up to 50 reviews; minimum 1, maximum 10, which is Apple's hard maximum. Default 10. | 10 |
delay_between_pages | No | integer | Original delay_between_pages key. Seconds to wait between review pages; minimum 0, maximum 60. Effective default 1.5, plus a small random jitter. | 2 |
proxyConfiguration | No | object | Optional Apify Proxy settings. The Apple feeds used here are public and normally work with no proxy at all; the Actor escalates automatically only if a request is refused. | {"useApifyProxy": false} |
The empty-input trap. No parameter is required, but a run with no app URL has nothing to do. If url, urls and appStoreUrls are all empty, the Actor logs No app URLs resolved and exits without pushing a single row and without charging anything โ it does not fall back to a default app. The Console hides this: appStoreUrls is prefilled with one example App Store link, so pressing Start in the UI scrapes that example app. A prefill is not a schema default, so an API caller sending {} gets an empty dataset while the Console user sees results. Always send at least one URL explicitly.
Legacy keys win. max_review is read before reviewsPerApp, max_pages before its camelCase forms, delay_between_pages before its, and url / urls are merged ahead of appStoreUrls. Set both a legacy key and its modern equivalent and the legacy one takes effect. The code also accepts three aliases the schema does not declare โ maxReviews, maxPages / pagesPerApp, and delayBetweenPagesSeconds / pageDelaySeconds โ and the URL lists additionally accept a newline- or comma-delimited string, or objects of the form {"url": "..."}.
Two silent fallbacks worth knowing. A URL that does not match apps.apple.com/<cc>/ is treated as the us storefront. And a country code outside the 60 storefronts the Actor holds an Apple storefront identifier for falls back to the US identifier on the rating-histogram request only โ the review feed still uses the country you gave.
summaryCountry applies to the summary row only. Review rows keep the country from their own URL. Set summaryCountry: "gb" on a /us/ URL and you get UK rating numbers on a summary row with country: "gb", while the review rows below it carry US reviews with country: "us". That is deliberate โ it lets you hold the rating basis constant across a mixed list โ but do not join the two on country without checking.
Bare numeric app IDs are not accepted. The app ID is parsed from /id<number> in the URL, so a plain 324684580 produces an invalid_input accounting row rather than a run.
Example input
{"appStoreUrls": ["https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580","https://apps.apple.com/gb/app/spotify-music-and-podcasts/id324684580","https://apps.apple.com/de/app/spotify-music-and-podcasts/id324684580"],"reviewsPerApp": 200,"includeRatingHistogram": true,"includeAppDetails": true,"includeStoreContext": true,"summaryCountry": "","url": [],"urls": [],"max_pages": 10,"delay_between_pages": 2,"proxyConfiguration": { "useApifyProxy": false }}
โฌ๏ธ Output
Typed, normalised JSON with a stable shape per row type across runs. Rows are pushed live as they are collected, so the dataset fills while the run is still going. Export as JSON, CSV or Excel, or read the dataset through the Apify API.
The dataset is deliberately heterogeneous โ one app_summary row per app followed by its review rows โ so branch on type rather than assuming one shape. Fields from a disabled toggle are absent from the row, not null. Turn off includeStoreContext and its six keys never appear; turn it on and they appear with null values if the store page could not be read. The same holds for the twelve histogram keys and the twenty-one listing keys. The default dataset view lists 56 columns; the Actor writes 58 keys in total, with price and currency present in the data but not in that view.
Uncharged accounting rows. A link that cannot be parsed, cannot be fetched, or yields neither reviews nor a summary is written as a row with type: "error", isSummary: false, errorReason (invalid_input, fetch_failed or no_data) and errorDetail truncated to 300 characters. These rows are pushed without a charged event โ they never bill. Only app_summary and review rows charge, one row_result event each. Filter the charged rows with item["type"] != "error", or take reviews alone with item["type"] == "review".
Example output
The app_summary row, with all three toggles on:
{"type": "app_summary","isSummary": true,"appStoreUrl": "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580","appId": "324684580","country": "us","scrapedAt": "2026-07-25T09:14:02Z","ratingCount": 3622778,"writtenReviewCount": 71504,"writeRatePct": 1.974,"starRatings1": 118432,"starRatings2": 41207,"starRatings3": 62915,"starRatings4": 189340,"starRatings5": 3210884,"ratingAverageDerived": 4.748,"negativeSharePct": 4.41,"positiveSharePct": 93.86,"histogramSampleSize": 3622778,"appName": "Spotify: Music and Podcasts","sellerName": "Spotify AB","sellerUrl": "https://www.spotify.com","price": 0.0,"formattedPrice": "Free","currency": "USD","primaryGenre": "Music","genres": ["Music", "Entertainment"],"currentVersion": "9.0.34","releaseDate": "2011-07-14T18:52:36Z","currentVersionReleaseDate": "2026-07-14T09:31:08Z","releaseNotes": "We're always making changes and improvements to Spotify. To make sure you don't miss a thing, just keep your updates turned on.","fileSizeMb": 152.3,"minimumOsVersion": "15.0","contentAdvisoryRating": "12+","artworkUrl512": "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/spotify/512x512bb.jpg","averageUserRating": 4.74781,"averageUserRatingCurrentVersion": 4.7503,"userRatingCount": 3622778,"artistId": 324684581,"bundleId": "com.spotify.client","chartRank": 3,"chartCategory": "Music","chartType": "top-free","privacyTypes": ["Data Used to Track You", "Data Linked to You", "Data Not Linked to You"],"similarApps": [{"appId": "510855668","name": "SoundCloud: Discover New Music","developer": "SoundCloud Global Limited & Co KG","rating": 4.6,"ratingCount": 218430},{"appId": "1108187390","name": "Amazon Music: Songs & Podcasts","developer": "AMZN Mobile LLC","rating": 4.7,"ratingCount": 1204877}],"moreByDeveloperCount": 4}
A review row from the same app:
{"author": "mkrentz88","rating": "5","version": "9.0.34","title": "Finally fixed the sync bug","content": "Been using this for two years and offline sync would drop tracks on every long flight. The 9.0.34 update seems to have sorted it โ three flights, nothing lost. Search is faster too.","date": "2026-07-16T05:52:31-07:00","reviewId": "11846203991","ratingNumber": 5,"dateIso": "2026-07-16T12:52:31Z","reviewerProfileUrl": "https://itunes.apple.com/us/reviews/id438291047","reviewUrl": "https://itunes.apple.com/us/review?id=324684580&type=Purple%20Software","appStoreUrl": "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580","appId": "324684580","country": "us","type": "review","isSummary": false,"scrapedAt": "2026-07-25T09:14:05Z"}
And an uncharged accounting row:
{"type": "error","isSummary": false,"appStoreUrl": "324684580","appId": null,"country": null,"errorReason": "invalid_input","errorDetail": "Could not extract app ID from URL: 324684580","scrapedAt": "2026-07-25T09:14:01Z"}
How does it work?
For each app link the Actor parses the numeric ID and the two-letter storefront out of the URL, then reads up to four public Apple surfaces: the customer review feed, the keyless rating-distribution endpoint, the iTunes lookup record, and โ if you enable it โ the app's store page.
Requests go out on a direct connection first and only climb to an Apify datacenter and then a residential proxy if Apple refuses one, so most runs never use a proxy at all. Where a browser-like TLS fingerprint helps, requests are made through an impersonating HTTP client, falling back to a plain async client otherwise. Responses are validated by content rather than status code: the rating surface soft-blocks with an HTTP 200 carrying an HTML shell, so a body that is not JSON counts as a failure and triggers an escalation.
Only publicly visible data is returned โ no Apple ID, cookie or session is used anywhere. Because three of the four surfaces are structured JSON endpoints rather than rendered markup, a store redesign generally leaves the output untouched, and the field names you map against stay put.
Integrations
The Actor is a standard Apify Actor, so it works with anything that can trigger an Apify run or read an Apify dataset.
Calling the Actor from Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("<YOUR_USERNAME>/apple-app-store-review-scraper-app-ratings-summary").call(run_input={"appStoreUrls": ["https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580","https://apps.apple.com/gb/app/spotify-music-and-podcasts/id324684580",],"reviewsPerApp": 200,"includeRatingHistogram": True,"includeStoreContext": True,})for item in client.dataset(run["defaultDatasetId"]).iterate_items():if item["type"] == "app_summary":print(item["country"], item["appName"], item["ratingAverageDerived"],"neg%", item["negativeSharePct"], "n", item["ratingCount"])elif item["type"] == "review":print(" ", item["country"], item["version"], item["ratingNumber"], item["title"])
Works in Go, Ruby, Node.js, cURL โ any language that can make an HTTP request. Branching on type is what separates summary rows, review rows and uncharged accounting rows.
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 Switch node on type splits summaries from reviews into separate branches. In Make, the Apify module supports run-and-wait, so a weekly rating sweep can feed a Google Sheets or Airtable step directly. In LangChain, wrap the run call as a tool and hand the agent the summary row as context alongside the review text, so it answers with the distribution rather than the sample.
Is it legal to scrape App Store reviews?
Collecting publicly published reviews is broadly treated as permissible where no authentication is bypassed, and this Actor reads only what any visitor sees โ no Apple ID, no developer account, no session of any kind.
The caveat that matters: review rows contain personal data. author is a reviewer's chosen display name, reviewerProfileUrl links to that person's review history, and title and content are text the reviewer wrote themselves, which may include details about their life, health, location or purchases. Under the GDPR and UK GDPR you need a lawful basis before storing or processing that for EU or UK data subjects โ legitimate interests is the usual route for product-feedback analysis, and it needs a documented balancing assessment. Under the CCPA/CPRA the same rows are personal information about California residents, with the disclosure and deletion duties that follow.
Data minimisation is straightforward here because the fields separate cleanly. If your analysis needs sentiment and release attribution but not identity, drop author and reviewerProfileUrl at ingestion and keep ratingNumber, version, dateIso and content. If you only need the aggregate picture, the app_summary row carries no personal data at all โ it is counts and listing metadata. Note that review collection cannot be switched off entirely: reviewsPerApp has a minimum of 1, so the smallest possible personal-data footprint is one review row per app.
Set a retention period rather than keeping review text indefinitely, and keep reviewId so an erasure or objection request can be honoured by deleting the exact records instead of searching text. Consult legal counsel if your use case involves bulk storage of reviewer identity data.
โ Frequently asked questions
What App Store review and ratings fields does the Actor return?
Review rows are led by ratingNumber, title, content, version and dateIso; summary rows by ratingAverageDerived, starRatings1โstarRatings5, ratingCount, positiveSharePct and writtenReviewCount. Fifty-eight keys in total across the row types โ see the data fields table above for every one.
Does it require an Apple ID, developer account or API key?
No. Every surface it reads is public: the customer review feed, the rating-distribution endpoint, the iTunes lookup record and the store page. No Apple ID, no App Store Connect account, no key. Your Apify token is the only credential involved.
How many reviews can I collect per app?
Up to 500 per app per storefront. That is Apple's ceiling โ the feed serves 50 reviews per page and returns HTTP 400 on page 11 โ and it applies to this Actor exactly as it does to any other reader of that feed. reviewsPerApp is capped at 500 in the schema; the legacy max_review accepts up to 10,000 and clamps to 500 with a log line rather than failing. To go past 500 for one app, list it under several country codes. The ratings summary is unaffected by this limit, because it comes from an aggregate surface rather than the paginated feed.
Is the ratings summary Apple's own number or calculated from the reviews collected?
Neither entirely, and this is the one thing to get right. The star counts (ratingCount, starRatings1โstarRatings5, writtenReviewCount) are Apple's published totals for the whole storefront. ratingAverageDerived, negativeSharePct, positiveSharePct, writeRatePct and histogramSampleSize are computed by the Actor from those counts, because Apple's own average on that surface is rounded to a half star. None of them are aggregates of the review rows in your run. An average computed from ratingNumber across your collected reviews will differ, and should โ the newest 500 reviews are not a random sample of a lifetime of ratings.
What happens if an app has no reviews in that storefront, or the URL is wrong?
Both cases are visible in the dataset rather than silent. A URL with no parsable /id<number> produces an uncharged row with type: "error" and errorReason: "invalid_input". A fetch that fails outright gives errorReason: "fetch_failed". An app that returns neither reviews nor a summary gives errorReason: "no_data". An app that genuinely has no reviews in that storefront still gets its summary row โ the histogram and listing surfaces are independent of the feed โ and simply produces no review rows; the Actor stops after two consecutive empty pages and logs that it reached the end. A 404 from the feed stops that app immediately with an informational log line rather than an error.
Why does an empty input return nothing when the Console shows a prefilled link?
Because that link is a Console prefill, not a schema default. Nothing in this Actor is required, and a run where appStoreUrls, url and urls are all empty logs No app URLs resolved and exits with zero rows and zero charge. In the Console the prefill fills the field for you, so Start works; an API caller sending {} gets an empty dataset. Send at least one URL explicitly.
Can I scrape multiple apps at once?
Yes. appStoreUrls is a list and every entry is processed in the same run, each producing its own summary row plus its own review rows. The legacy url and urls lists are merged ahead of it and exact duplicate URL strings are dropped. Listing the same app under several country codes is the standard way to build a market-segmented dataset in one run.
Does the Actor 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 hand-written tool definition can invoke it and receive typed JSON with no parsing step. The summary row is small enough to pass to a model whole as grounding context for the review text.
How does it compare to other App Store review scrapers?
Checked on the Apify Store on 25 July 2026: scrapers_lat/app-store-reviews-scraper accepts app IDs, URLs or a search term, offers a sortBy of most recent or most helpful, and attaches the full app profile to every review row โ its README states the feed returns "up to around 500 reviews per app per country" and recommends adding countries to go deeper. riadh_chebbi/apple-app-store-reviews-scraper resolves apps by name, ID or URL, takes a countries list, and documents voteSum / voteCount per review, noting the same roughly-500-per-country limit. A third listing, eccentric_layout/appstore-scraper, is published as "Apple App Store Scraper - Apps, Reviews & Ratings"; its README was not retrieved, so nothing is claimed about it here. All of these read the same public Apple surfaces and are therefore bound by the same 500-per-storefront feed ceiling. Neither of the two READMEs read documents a per-star rating distribution or a derived average; conversely, helpful-vote counts, search-term input and sort order are documented by them and are not offered here.
What happens when Apple changes its layout or anti-bot system?
The Actor is maintained. Three of the four surfaces it reads are structured JSON endpoints rather than rendered HTML, so a store redesign generally does not touch them; the store-page context read is the one exposed to markup change, and when it fails its six fields come back null with a warning rather than breaking the run. Your field names and types stay stable either way.
Can I use it without managing proxies or browser infrastructure?
Yes. There is no browser to run and no proxy account to create. Runs start on a direct connection and the Actor escalates to Apify datacenter and then residential proxies by itself if Apple refuses a request, logging each switch. Setting proxyConfiguration is optional and only changes which groups the escalation uses.
Which fields work best for AI training data and RAG indexing?
For RAG: content and title carry the descriptive text and chunk cleanly per review, with appId, version, country and dateIso as metadata filters. Attaching the app's ratingAverageDerived, positiveSharePct and ratingCount to each chunk lets a retrieval step weight a complaint against how common it actually is. For training data: ratingNumber, starRatings1โstarRatings5, writeRatePct and dateIso are the most structurally consistent fields across records. Everything returns as typed primitives, arrays or plain nested objects โ no normalisation pass needed before indexing.
๐ Related scrapers
| Scraper Name | What it extracts |
|---|---|
| Google Play Store Reviews Scraper | Android review text, star ratings and app versions |
| Google Maps Scraper with Reviews Sentiment Analysis | Place reviews with sentiment scoring and rating breakdowns |
| Goodreads Review Scraper with Reviewer Lead Enrichment | Book reviews, ratings and enriched reviewer records |
| Reddit Posts Scraper by Keyword & Score Filter | Discussion threads about the same product, filtered by score |
| Amazon Today's Deals Scraper with Star Ratings | Deal listings with star ratings and badge data |
| Twitter (X) Posts Search | Public posts mentioning an app around a release |
๐ฌ Your feedback
Found a bug, or need a field this Actor does not yet return? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON and the App Store link you ran are the fastest to reproduce and fix.