# App Store Reviews Scraper | No Personal Data (`datamill.top/app-store-reviews-scraper`) Actor

Scrape Apple App Store reviews, ratings and app metadata. Review text, titles, scores, dates and app versions. No author names or profile links — GDPR-safe output. Apple caps its public feed at 500 reviews per app per country; collect across storefronts for more.

- **URL**: https://apify.com/datamill.top/app-store-reviews-scraper.md
- **Developed by:** [Alexandr Gobozov](https://apify.com/datamill.top) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## App Store Reviews Scraper — App Data & Ratings, No Personal Data

Scrape Apple App Store reviews, ratings, and full app metadata from any iOS app.
This App Store scraper extracts review titles, review text, star scores, dates, app
versions, and helpful votes, plus complete app listing data: price, category, release
date, current version, and rating count.

**Reviewer names and profile links are never collected.** Every review is returned with
an irreversible pseudonymous hash instead of a nickname, so you get the analysis you
need without holding personal data. See [Privacy and GDPR](#privacy-and-gdpr) below.

Output uses the same field names as our
[Google Play Reviews Scraper](https://apify.com/datamill.top/google-play-reviews-scraper),
so both stores merge into one table without renaming a single column.

> **Unofficial tool.** This Actor is not affiliated with, sponsored by, or endorsed by
> Apple Inc. "App Store" and "Apple" are trademarks of Apple Inc. This Actor reads only
> Apple's own publicly available endpoints.

### Pricing

Currently **free** — you only pay Apify platform usage for the compute a run consumes.
No per-result fee on top.

### The one limit you need to know first

Apple's public review feed returns **at most 500 reviews per app per country
storefront** — 10 pages of 50. There is no way around it, and any scraper claiming
otherwise for the public feed is either paginating the same 500 rows or reading a
private endpoint.

Two things actually work, and this actor is built for both:

- **Collect from several storefronts.** The cap is per country. Ten countries means up
  to 5,000 reviews for the same app, and you get a useful per-market breakdown for free.
  Put the codes in `countries`.
- **Run it on a schedule.** Set `reviewsSince` to yesterday and run daily. The 500-review
  window keeps moving, so your own dataset grows past the cap over time.

If you own the app, Apple's App Store Connect API returns your complete review history
with no cap. That needs your own API key and is a different tool.

### What this App Store scraper extracts

**App data** — title, developer, developer website, bundle ID, price, currency, average
rating, rating count, current-version rating and count, content rating, genre and genre
list, version, release date, last update date, minimum iOS version, download size, icon,
store URL, supported languages, description, release notes, iPhone and iPad screenshots.

**Review data** — review title, review text, star rating (1–5), review date, app version
the review was written on, helpful votes, total votes, storefront, pseudonymous author
hash with duplicate-nickname indicators.

### Why choose this iOS app scraper

- **No personal data.** No nicknames, no profile links, no developer contact details.
  Nothing in the output identifies a person.
- **Merges with Google Play.** Field names, date format, and the author-hash scheme are
  identical to our Google Play actor. Concatenate the two datasets and every column
  lines up. Rows carry a `source` field so you can always tell them apart.
- **Reliable dates.** Apple returns review timestamps in the storefront's own offset —
  `-07:00` for the US, `+02:00` for Germany. This scraper converts everything to UTC in
  ISO 8601, so a column sorted across markets is actually sorted. The original release
  date string is kept in `releasedText`.
- **Handles messy input.** Paste a full App Store URL, an `id310633997` fragment, a bare
  numeric ID, or an iOS bundle ID like `net.whatsapp.WhatsApp`. Bundle IDs are resolved
  automatically. Duplicates are removed, so you never pay twice for the same app.
- **Retries built in.** Four attempts with exponential backoff on every request. One
  unavailable app never kills the whole run — you get the rest of your data plus a clear
  warning in the log.
- **Incremental scraping.** Set `reviewsSince` to a date and the scraper stops paging as
  soon as it reaches older reviews.
- **Efficient metadata.** App cards are fetched in batches of 20 through Apple's official
  lookup endpoint — one request per 20 apps instead of one per app.

### Use cases

**App Store Optimization (ASO)** — track rating trends and metadata changes for your app
and competitor apps across markets.

**Review analysis and sentiment analysis** — export review titles and text to your own
pipeline, LLM, or spreadsheet to find recurring complaints, feature requests, and bugs.
App Store reviews carry a title, which is often a cleaner sentiment signal than the body.

**Cross-platform comparison** — run this actor and the Google Play one on the same
product, stack the two datasets, and compare complaint mix between iOS and Android
without any column mapping work.

**Release monitoring** — filter reviews by `appVersion` to see exactly how sentiment
changed after a release.

**Market comparison** — the same app scraped across `us`, `gb`, `de`, `br`, and `jp`
gives you five separate review pools plus five separate price and rating snapshots.

**Fake review detection** — `authorHashCount` and `authorHashApps` reveal nicknames
appearing across multiple apps, without exposing who wrote what.

### Input

Give the scraper app IDs, a search query, or both.

```json
{
  "appIds": [
    "310633997",
    "https://apps.apple.com/us/app/spotify/id324684580",
    "net.whatsapp.WhatsApp"
  ],
  "searchQuery": "habit tracker",
  "maxAppsFromSearch": 10,
  "scrapeAppDetails": true,
  "scrapeReviews": true,
  "maxReviewsPerApp": 500,
  "reviewsSort": "newest",
  "reviewsSince": "2026-01-01",
  "countries": ["us", "gb", "de"]
}
```

| Field | Description |
|---|---|
| `appIds` | Numeric App Store IDs, bundle IDs, or app page URLs. Duplicates removed automatically. |
| `searchQuery` | Search the App Store instead of listing IDs. |
| `maxAppsFromSearch` | How many search results to scrape. |
| `scrapeAppDetails` | Return the app listing data. |
| `scrapeReviews` | Return reviews. Set `maxReviewsPerApp` to 0 to skip. |
| `maxReviewsPerApp` | Reviews per app **per country**, up to Apple's cap of 500. |
| `reviewsSort` | `newest` or `helpfulness`. Apple's feed has no sort by rating. |
| `filterByScore` | Only reviews with this star rating (1–5). Applied after fetching — see below. |
| `reviewsSince` | `YYYY-MM-DD`. Stops early once older reviews are reached. |
| `countries` | Two-letter storefront codes. Each one has its own reviews, prices, and its own 500-review cap. |
| `country` | Shortcut for a single storefront. Ignored when `countries` is set. |
| `includeDescription` | Turn off for a lighter dataset without description and screenshots. |
| `annotateAuthorCollisions` | Adds duplicate-nickname indicators to reviews. |
| `maxConcurrency` | Parallel requests, 1–10. |

**About `filterByScore`.** Apple has no server-side score filter, so filtering happens
after the feed is fetched. Setting it to 1 gives you the one-star reviews *out of the 500
reachable ones* — not 500 one-star reviews. Most scrapers do the same thing without
telling you.

### Output

Results go to a single dataset. App rows have `"type": "app"`, review rows have
`"type": "review"`, and every row carries `"source": "app_store"`, so you can filter,
split, or merge with Google Play data after export to JSON, CSV, Excel, or Google Sheets.

App record, shortened:

```json
{
  "type": "app",
  "source": "app_store",
  "appId": "310633997",
  "bundleId": "net.whatsapp.WhatsApp",
  "country": "us",
  "title": "WhatsApp Messenger",
  "developer": "WhatsApp Inc.",
  "installs": null,
  "realInstalls": null,
  "score": 4.7,
  "ratings": 12345678,
  "histogram": null,
  "free": true,
  "price": 0,
  "currency": "USD",
  "genre": "Social Networking",
  "contentRating": "12+",
  "released": "2009-05-03T07:00:00+00:00",
  "releasedText": "2009-05-03T07:00:00Z",
  "updated": "2026-09-01T18:00:00+00:00",
  "version": "25.1.0",
  "minimumOsVersion": "15.1",
  "fileSizeBytes": 243167232,
  "scrapedAt": "2026-09-07T12:04:11+00:00"
}
```

Review record:

```json
{
  "type": "review",
  "source": "app_store",
  "appId": "324684580",
  "country": "us",
  "reviewId": "12345678901",
  "reviewTitle": "Sync finally works",
  "content": "Solid update, playlists sync across devices now.",
  "score": 5,
  "thumbsUpCount": 7,
  "voteCount": 9,
  "appVersion": "9.1.78",
  "at": "2026-09-04T18:23:07+00:00",
  "replyContent": null,
  "repliedAt": null,
  "authorHash": "667312a54c6ae870",
  "authorHashCount": 1,
  "authorHashApps": 1,
  "authorHashCountries": 1,
  "scrapedAt": "2026-09-07T12:04:11+00:00"
}
```

#### Fields that are always null, and why

These columns exist so the App Store dataset stacks cleanly on the Google Play one. An
empty column is more honest than a missing one: you can see at a glance that the data
does not exist rather than wondering whether it was skipped.

| Field | Why it is empty |
|---|---|
| `installs`, `realInstalls` | Apple does not publish download counts for any app. |
| `histogram` | Apple does not publish the 1–5 star breakdown in a public endpoint. |
| `replyContent`, `repliedAt` | Developer replies are not included in Apple's public review feed. Google Play does include them, so the columns stay for cross-store alignment. |

Fields that exist here but not on Google Play: `reviewTitle`, `voteCount`, `bundleId`,
`minimumOsVersion`, `fileSizeBytes`, `authorHashCountries`, `country`.

### Privacy and GDPR

App Store reviewer nicknames are personal data under the GDPR. This scraper does not
return them.

Each review carries `authorHash` — a salted SHA-256 hash of the display nickname,
truncated to 16 characters. The same nickname always produces the same hash, so you can
still group reviews by author, but the nickname cannot be recovered from the hash.

**Apple's feed also contains a link to each reviewer's profile — a stable user ID. We do
not read it and do not hash it.** Hashing a real user ID would let anyone track one
individual reliably across every app they have ever reviewed. That is profiling, and it
is not what this tool is for. The hash is derived from the nickname instead, which is
weaker on purpose. Three fields tell you exactly how much to trust it:

- `authorHashCount` — how many reviews in this run share the hash. A large number means
  a very common nickname that should not be treated as one person.
- `authorHashApps` — how many different apps the hash appeared in.
- `authorHashCountries` — how many different storefronts. A repeat across storefronts
  almost always means different people, since markets do not share an audience.

One Apple ID can leave only one review per app. A repeated hash **within a single app and
storefront** therefore always means different people with the same nickname. A repeat
**across different apps in the same storefront** may be the same reviewer and is worth a
closer look.

The salt is configurable through the `AUTHOR_HASH_SALT` environment variable. Set the
same salt here and in the Google Play actor and the hashes stay comparable across both
stores.

### Integrations

Run this App Store scraper from the Apify API, schedule it to monitor app reviews daily,
or connect it to Make, Zapier, Google Sheets, Slack, or an MCP client. Results export as
JSON, CSV, Excel, XML, or RSS.

### FAQ

**How many reviews can I scrape from an App Store app?**
500 per app per country storefront, which is Apple's own limit on the public feed. Add
countries to `countries` to multiply that, or schedule daily runs to accumulate history.

**Can I get more than 500 reviews for one app in one country?**
Not from any public source. If it is your own app, App Store Connect gives you all of
them with an API key you own.

**Why are `replyContent` and `repliedAt` always empty?**
Apple's public review feed does not carry developer responses. The columns exist so the
output lines up with the Google Play dataset, where they are populated.

**Can I scrape only negative reviews?**
Set `filterByScore` to 1 or 2. Note that filtering runs after fetching, so you get the
low-rated share of the 500 reachable reviews.

**Does it work with Google Play apps?**
No — use our Google Play Reviews Scraper for Android. The two produce the same output
schema on purpose, so you can run both and merge the results.

**Why is the reviewer name missing?**
By design — see [Privacy and GDPR](#privacy-and-gdpr). Use `authorHash` to group reviews
by author without storing personal data.

**Is scraping the App Store legal?**
This actor reads Apple's official public RSS review feed, lookup endpoint, and search
endpoint. It does not authenticate, does not bypass any technical protection, and does
not access private data. You remain responsible for how you use the data under the laws
that apply to you. This is not legal advice.

**Is this an official Apple tool?**
No. It is an independent, unofficial Actor with no affiliation to Apple Inc. It reads
only endpoints Apple publishes openly, and it holds no special access or partnership.

# Actor input Schema

## `appIds` (type: `array`):

Numeric App Store IDs, bundle IDs, or full app page URLs. Find the ID in the App Store URL after /id, e.g. 310633997 in apps.apple.com/us/app/whatsapp-messenger/id310633997

## `searchQuery` (type: `string`):

Instead of listing app IDs, search the store. Results are added to App IDs above.

## `maxAppsFromSearch` (type: `integer`):

How many apps to take from the search results.

## `scrapeAppDetails` (type: `boolean`):

Title, developer, price, rating, version, category, release date, screenshots.

## `scrapeReviews` (type: `boolean`):

Review title, text, score, date and app version. Author name and profile link are never returned.

## `maxReviewsPerApp` (type: `integer`):

Apple's public review feed is capped at 500 reviews per app per country storefront. Higher values are clamped to 500. Set 0 to skip reviews entirely. Add more countries to collect more.

## `reviewsSort` (type: `string`):

Newest is the usual choice for monitoring. Apple's feed offers no sort by rating — use the score filter below instead.

## `filterByScore` (type: `integer`):

Leave empty for all. Apple has no server-side score filter, so this is applied after fetching: you get the 1-star reviews out of the 500 reachable ones, not 500 one-star reviews.

## `reviewsSince` (type: `string`):

Format YYYY-MM-DD. Works with Newest first. Useful for daily monitoring.

## `annotateAuthorCollisions` (type: `boolean`):

Adds authorHashCount, authorHashApps and authorHashCountries to each review. One Apple ID can leave only one review per app, so a repeated hash within a single app means different people with the same nickname. A repeat across apps may mean the same reviewer. A repeat across storefronts usually means different people.

## `countries` (type: `array`):

Two-letter codes, e.g. us, gb, de. Each storefront has its own reviews, prices and its own 500-review cap, so adding countries is the way to get more reviews for one app. Defaults to us.

## `country` (type: `string`):

Shortcut for one storefront, kept for input compatibility with our Google Play actor. Ignored when Country storefronts above is filled in.

## `includeDescription` (type: `boolean`):

Turn off for a lighter dataset when you only need numbers.

## `maxConcurrency` (type: `integer`):

Higher is faster but more likely to be rate limited by Apple.

## Actor input object example

```json
{
  "appIds": [
    "310633997"
  ],
  "searchQuery": "note taking",
  "maxAppsFromSearch": 10,
  "scrapeAppDetails": true,
  "scrapeReviews": true,
  "maxReviewsPerApp": 200,
  "reviewsSort": "newest",
  "reviewsSince": "2026-01-01",
  "annotateAuthorCollisions": true,
  "countries": [
    "us"
  ],
  "includeDescription": true,
  "maxConcurrency": 5
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "appIds": [
        "310633997"
    ],
    "countries": [
        "us"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datamill.top/app-store-reviews-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "appIds": ["310633997"],
    "countries": ["us"],
}

# Run the Actor and wait for it to finish
run = client.actor("datamill.top/app-store-reviews-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "appIds": [
    "310633997"
  ],
  "countries": [
    "us"
  ]
}' |
apify call datamill.top/app-store-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datamill.top/app-store-reviews-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Ha1dhl8ssRseQtLdK/builds/11cKGSKRY5Ng0hGB8/openapi.json
