Apple App Store Review Scraper & App Ratings Summary avatar

Apple App Store Review Scraper & App Ratings Summary

Pricing

from $4.99 / 1,000 results

Go to Apify Store
Apple App Store Review Scraper & App Ratings Summary

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

Scraper Engine

Maintained by Community

Actor stats

0

Bookmarked

20

Total users

7

Monthly active users

12 days ago

Last modified

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 CategoryAny 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 NameDescription
typeRow kind: app_summary, review or error
isSummarytrue only on the summary row
appIdNumeric App Store ID parsed from the URL
countryStorefront the row describes โ€” it differs between row types, see the input notes
appStoreUrlThe listing URL this row came from
scrapedAtUTC ISO collection timestamp
ratingCountApple's total star ratings for the app in that storefront
starRatings1Number of 1โ˜… ratings
starRatings2Number of 2โ˜… ratings
starRatings3Number of 3โ˜… ratings
starRatings4Number of 4โ˜… ratings
starRatings5Number of 5โ˜… ratings
histogramSampleSizeSum of the five bucket counts โ€” null if the sum is zero
ratingAverageDerivedMean rating computed from the five buckets, rounded to 3 decimals
negativeSharePctPercentage of ratings that are 1โ˜… or 2โ˜…, 2 decimals
positiveSharePctPercentage of ratings that are 4โ˜… or 5โ˜…, 2 decimals
writtenReviewCountRatings that carry written text, as Apple reports it
writeRatePctwrittenReviewCount รท ratingCount as a percentage, 3 decimals
appNameApp title from the lookup record
sellerNamePublisher name
sellerUrlPublisher website
priceNumeric price in the storefront currency
formattedPricePrice as Apple formats it, e.g. "Free"
currencyISO currency code for price
primaryGenreMain category
genresArray of all category names
currentVersionLatest published version string
releaseDateFirst release timestamp
currentVersionReleaseDateRelease timestamp of the current version
releaseNotes"What's New" text for the current version
fileSizeMbDownload size converted from bytes to MB, 1 decimal
minimumOsVersionMinimum iOS version required
contentAdvisoryRatingAge rating, e.g. "12+"
artworkUrl512512px app icon URL
averageUserRatingApple's own lifetime average for that storefront
averageUserRatingCurrentVersionApple's own average for the current version
userRatingCountApple's own rating count from the lookup record
artistIdNumeric developer ID
bundleIdReverse-DNS bundle identifier
chartRankTop-chart position, or null if the app is not charting
chartCategoryChart the rank belongs to, e.g. "Music"
chartTypeChart variant parsed from the chart link, e.g. "top-free"
privacyTypesArray of Apple privacy nutrition-label group titles
similarAppsArray of objects: appId, name, developer, rating, ratingCount
moreByDeveloperCountHow many other apps the developer shelf listed
reviewIdApple's review identifier โ€” the deduplication key
authorReviewer display name
ratingStar rating exactly as Apple's feed returns it, a string
ratingNumberThe same rating parsed to an integer
versionApp version the reviewer was running
titleReview headline
contentFull review body
dateApple's publish timestamp, in the storefront's local offset
dateIsoThe same instant normalised to UTC, or null if unparseable
reviewUrlPermalink to the review
reviewerProfileUrlLink to the reviewer's Apple review history
errorReasonOn accounting rows only: no_data, invalid_input or fetch_failed
errorDetailOn 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.

  1. Open the Actor on Apify and click Try for free
  2. Paste one or more App Store links into App Store listing links โ€” full URLs containing /id<number>, one per entry
  3. Set Reviews to collect per app: 20 for a quick sample, 500 for everything Apple exposes per storefront
  4. Leave Star histogram + derived average and Store listing record on; tick Chart rank, privacy labels & similar apps if you want store context
  5. Optionally set Storefront for the summary to hold the rating basis constant across a mixed list
  6. 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 compares negativeSharePct day over day against ratingAverageDerived โ€” the 1โ˜…โ€“2โ˜… share moves within days of a bad build, while the average barely shifts.
  • ๐Ÿ” An ASO analyst benchmarking rivals enables includeStoreContext across a competitor list and reads chartRank, chartCategory and positiveSharePct into one table to see who is charting on what sentiment.
  • ๐Ÿ› A support lead triaging a regression filters review rows on ratingNumber of 1 or 2 and groups content by version to find the exact build that introduced the complaint.
  • ๐ŸŒ A localisation manager runs the same app across ten storefronts and sorts by ratingAverageDerived and writeRatePct to find the market where satisfaction is lowest and feedback volume is highest.
  • ๐Ÿค– An AI engineer building a product-feedback agent indexes title and content into a vector store keyed by appId, version and dateIso, 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.

ParameterRequiredTypeDescriptionExample Value
appStoreUrlsNoarrayOne 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"]
reviewsPerAppNointegerApple'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
includeRatingHistogramNobooleanAdds 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
includeAppDetailsNobooleanAdds 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
includeStoreContextNobooleanAdds 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
summaryCountryNostringTwo-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"
urlNoarrayOriginal url key. Values supplied here are merged ahead of appStoreUrls.["https://apps.apple.com/gb/app/spotify-music-and-podcasts/id324684580"]
urlsNoarrayOriginal undocumented urls key, now declared. Merged after url.[]
max_reviewNointegerOriginal 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_pagesNointegerOriginal 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_pagesNointegerOriginal delay_between_pages key. Seconds to wait between review pages; minimum 0, maximum 60. Effective default 1.5, plus a small random jitter.2
proxyConfigurationNoobjectOptional 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 ApifyClient
client = 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.

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.

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.

Scraper NameWhat it extracts
Google Play Store Reviews ScraperAndroid review text, star ratings and app versions
Google Maps Scraper with Reviews Sentiment AnalysisPlace reviews with sentiment scoring and rating breakdowns
Goodreads Review Scraper with Reviewer Lead EnrichmentBook reviews, ratings and enriched reviewer records
Reddit Posts Scraper by Keyword & Score FilterDiscussion threads about the same product, filtered by score
Amazon Today's Deals Scraper with Star RatingsDeal listings with star ratings and badge data
Twitter (X) Posts SearchPublic 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.