# App Store & Google Play Reviews Scraper - iOS + Android (`boztek-ltd/app-store-google-play-reviews`) Actor

Scrape iOS App Store and Google Play reviews together across 150+ countries. Rating, text, version and developer replies in one schema, plus a free insights report.

- **URL**: https://apify.com/boztek-ltd/app-store-google-play-reviews.md
- **Developed by:** [Boztek LTD](https://apify.com/boztek-ltd) (community)
- **Categories:** AI, Developer tools, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.10 / 1,000 review scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## 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 & Google Play Reviews Scraper — iOS + Android in One Run

Scrape reviews from **both** app stores in a single run, across as many countries as you like, and get one normalized schema back. Add an app twice — once per store — and you can compare iOS against Android directly.

Most review scrapers do one store, one country. This one does both stores, 150+ countries, only-what-changed runs, and a rules-based insights report with no LLM and no API key.

### What you get

**Per review**

| Field | iOS | Android |
|---|:---:|:---:|
| Rating, review text, author, date | ✅ | ✅ |
| Review title | ✅ | — *(Google Play has no title field)* |
| App version | ✅ | ✅ |
| Helpful / thumbs-up count | ✅ | ✅ |
| Up-vote sum | ✅ | — |
| Developer reply text and date | — | ✅ |
| Country and direct review link | ✅ | ✅ |

**Per app — the insights row**

- Rating distribution (1★ to 5★) and the average across the reviews collected
- The store-wide rating and rating count, for comparison against your sample
- **Per-version breakdown** — average rating and volume for each app version, so a bad release is obvious
- **Monthly volume** — review count and average rating per calendar month
- **Negative and positive themes** — the words that show up disproportionately in 1-2★ versus 4-5★ reviews
- **Developer reply rate** — what share of reviews got an answer

### Only the new reviews

Turn on **`onlyNewReviews`** and the Actor remembers every review ID it has returned and emits only new ones.

Combine it with `maxRating: 1` and a schedule and you have a 1-star alarm: every new angry review, within the hour, pushed wherever you want it.

### What does it cost?

Pay-per-event. Filters run before charging, so `minRating` / `maxRating` / `sinceDate` genuinely lower the bill.

| Event | Price | When |
|---|---|---|
| Review scraped | **$0.0001** | Per review in the dataset |
| App metadata fetched | **$0.002** | Once per app, only with metadata on |
| Insights report | **$0.02** | Once per app, only with insights on |
| Actor start | **$0.00005** | Once per run |

| Scenario | Cost |
|---|---|
| One app, both stores, 1,000 reviews each, with insights | ~$0.25 |
| 10 competitor apps, 500 reviews each, insights on | ~$0.72 |
| Daily 1★ watch on 5 apps, ~40 new reviews/day | ~$0.02/day |

### Naming apps

Mix any of these in the `apps` list:

| Form | Example |
|---|---|
| App Store URL | `https://apps.apple.com/us/app/spotify/id324684580` |
| Google Play URL | `https://play.google.com/store/apps/details?id=com.spotify.music` |
| iOS numeric ID | `324684580` |
| Android package | `com.spotify.music` |
| Explicit | `ios:324684580` · `android:com.spotify.music` |

### Countries matter more than you think

Apple serves at most **500 reviews per country** (10 pages of 50) and Google Play's pool is country-scoped too. Countries are the way to scale: `["us","gb","ca","au","de","fr"]` gives you six separate pools of the same app.

`maxReviewsPerApp` is a total across countries, split evenly, so 600 reviews over 6 countries pulls 100 from each.

### Output

One dataset, tagged with `recordType`, with a **Reviews** view and an **Insights** view in the Console.

```json
{
  "recordType": "review",
  "platform": "ios",
  "appId": "324684580",
  "appName": "Spotify: Music and Podcasts",
  "country": "us",
  "reviewId": "14407833146",
  "rating": 4,
  "title": "I do love this app but",
  "text": "I do love this app but all I ask is that you please add...",
  "author": "Biker 125",
  "appVersion": "9.1.70",
  "submittedAt": "2026-08-09T19:46:45.000Z",
  "thumbsUp": 0,
  "voteSum": 0,
  "reviewUrl": "https://apps.apple.com/us/app/id324684580?see-all=reviews",
  "scrapedAt": "2026-08-11T01:40:12.004Z"
}
```

```json
{
  "recordType": "insights",
  "platform": "android",
  "appName": "Spotify: Music and Podcasts",
  "countries": ["us", "gb", "de"],
  "reviewCount": 120,
  "averageRating": 3.63,
  "storeRating": 4.34,
  "storeRatingCount": 36095801,
  "ratingOne": 30, "ratingTwo": 5, "ratingThree": 6, "ratingFour": 10, "ratingFive": 69,
  "negativeThemes": ["paying", "update", "keeps", "anymore", "premium", "listen"],
  "positiveThemes": ["learn"],
  "versionBreakdown": [
    { "version": "9.1.72.1891", "reviewCount": 90, "averageRating": 3.72 },
    { "version": "9.1.68.1888", "reviewCount": 9, "averageRating": 4.11 }
  ],
  "developerReplyRate": 12.5
}
```

Note the gap between `averageRating` (3.63 in this run's most-recent reviews) and `storeRating` (4.34 lifetime). Recent sentiment diverging from the lifetime score is exactly the signal a store rating hides.

### Input options

| Option | Type | Default | Description |
|---|---|---|---|
| `apps` | array | **required** | URLs, IDs, packages or `ios:` / `android:` entries |
| `countries` | array | `["us"]` | Two-letter country codes |
| `language` | string | `""` | Google Play language override; Apple follows the country |
| `maxReviewsPerApp` | integer | `200` | Total per app across countries (`0` = as many as served) |
| `sortBy` | string | `recent` | `recent` / `helpful` / `rating` (Play only) |
| `minRating` / `maxRating` | integer | `1` / `5` | Star range to keep |
| `sinceDate` | string | `""` | ISO date, e.g. `2026-01-01` |
| `onlyNewReviews` | boolean | `false` | Emit only reviews not seen before |
| `generateInsights` | boolean | `true` | Add the per-app insights row |
| `includeAppMetadata` | boolean | `true` | Look up name, developer, category, store rating |
| `stateStoreName` | string | `app-reviews-state` | Key-value store backing incremental mode |
| `maxConcurrency` | integer | `5` | Parallel requests |
| `proxyConfiguration` | object | off | Optional |

### Use cases

1. **Release monitoring** — the per-version breakdown shows a regression the day it ships, not when the store rating finally moves.
2. **1-star alerting** — `maxRating: 1` + `onlyNewReviews` + a schedule.
3. **Competitor teardown** — run five rivals at once and read their negative themes to find what their users keep asking for.
4. **ASO and localisation** — compare ratings and complaints per country before deciding where to invest.
5. **Support and product feedback** — pipe reviews into your own tooling; the developer reply rate shows where nobody is answering.

### Limitations

- **Apple caps each country at 500 reviews.** Page 11 of its feed returns HTTP 400. Add countries to widen the pool — there is no way around the per-country ceiling.
- **Google Play reviews carry no title.** The store dropped the field; `title` is populated for iOS only.
- **Developer replies come from Google Play only.** Apple's public feed does not include them.
- **Theme extraction is rules-based, not an LLM.** That keeps it free and predictable, but it counts words rather than understanding them. Function words for English, German, French, Spanish, Portuguese, Italian, Dutch and Turkish are filtered out, along with the app's own name — yet a review pool mixing several languages still produces some noise. **For the cleanest themes, run one country at a time.** Positive themes are often sparse because 5-star reviews tend to be very short.
- **`averageRating` describes your sample, not the app.** It is computed over the reviews this run collected, which with `sortBy: recent` means recent sentiment. `storeRating` is the lifetime figure.
- **Google Play tolerates datacenter IPs at moderate volume.** Apple's feed needs no proxy at all. If you scrape many countries at high frequency, add a proxy for the Play side.
- **Apple's feed occasionally repeats reviews across pages.** Duplicates are removed by review ID, so a country can return slightly fewer reviews than its page count suggests.

# Actor input Schema

## `apps` (type: `array`):

One entry per app. All of these work and can be mixed:

- **App Store URL** — `https://apps.apple.com/us/app/spotify/id324684580`
- **Google Play URL** — `https://play.google.com/store/apps/details?id=com.spotify.music`
- **iOS numeric ID** — `324684580`
- **Android package** — `com.spotify.music`
- **Explicit** — `ios:324684580` or `android:com.spotify.music`

Add the same app on both stores to compare iOS and Android side by side.

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

Two-letter country codes, e.g. `us`, `gb`, `de`, `tr`, `jp`. Each country is a separate review pool on both stores — adding more countries is how you get past Apple's 500-review-per-country ceiling.

## `language` (type: `string`):

Two-letter language code used for Google Play only, e.g. `en`. Leave empty to use the country's own language. Apple's feed always follows the country.

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

Cap across all countries for one app. `0` means as many as the stores will serve.

## `sortBy` (type: `string`):

`recent` is the right choice for monitoring. `helpful` surfaces the reviews each store promotes. `rating` is Google Play only and falls back to `recent` on the App Store.

## `minRating` (type: `integer`):

Keep only reviews at or above this star rating.

## `maxRating` (type: `integer`):

Keep only reviews at or below this star rating. Set both to 1 to read just the angriest feedback.

## `sinceDate` (type: `string`):

ISO date, e.g. `2026-01-01`. Leave empty for no date limit.

## `onlyNewReviews` (type: `boolean`):

Remembers every review ID returned before and emits only new ones. Ideal for a scheduled alert on 1-star reviews.

## `generateInsights` (type: `boolean`):

Adds one row per app with the rating distribution, average rating, per-version ratings, review volume by month and the keyword themes that dominate positive and negative reviews. Computed by rules — no LLM, no API key, no extra cost beyond the event.

## `includeAppMetadata` (type: `boolean`):

Look up the app's name, developer, category, price, current version and store-wide rating. One request per app per store.

## `stateStoreName` (type: `string`):

Named key-value store backing **Only reviews not seen in previous runs**. Use different names to keep separate schedules independent.

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

Parallel requests across apps and countries.

## `proxyConfiguration` (type: `object`):

Optional. Apple's feed needs no proxy. Google Play tolerates datacenter IPs at moderate volume; add a proxy if you scrape many countries at once.

## Actor input object example

```json
{
  "apps": [
    "ios:324684580",
    "android:com.spotify.music"
  ],
  "countries": [
    "us"
  ],
  "language": "",
  "maxReviewsPerApp": 200,
  "sortBy": "recent",
  "minRating": 1,
  "maxRating": 5,
  "sinceDate": "",
  "onlyNewReviews": false,
  "generateInsights": true,
  "includeAppMetadata": true,
  "stateStoreName": "app-reviews-state",
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `reviews` (type: `string`):

One item per review: platform, app, country, rating, title (iOS only), text, author, app version, date, helpful count and the developer reply (Google Play only).

## `reviewsCsv` (type: `string`):

The same reviews as a spreadsheet-ready CSV.

## `insightsColumns` (type: `string`):

Narrowed to the insights columns: review count, average rating in this run against the lifetime store rating, the 1-5 star distribution, and the negative and positive themes. Note this selects columns, not rows, so review rows still appear with those columns blank.

## `runSummary` (type: `string`):

Which apps were collected, across which countries, how many reviews and insights rows were pushed, which entries could not be read, and the count of each event charged.

# 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 = {
    "apps": [
        "ios:324684580",
        "android:com.spotify.music"
    ],
    "countries": [
        "us"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("boztek-ltd/app-store-google-play-reviews").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 = {
    "apps": [
        "ios:324684580",
        "android:com.spotify.music",
    ],
    "countries": ["us"],
}

# Run the Actor and wait for it to finish
run = client.actor("boztek-ltd/app-store-google-play-reviews").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 '{
  "apps": [
    "ios:324684580",
    "android:com.spotify.music"
  ],
  "countries": [
    "us"
  ]
}' |
apify call boztek-ltd/app-store-google-play-reviews --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,boztek-ltd/app-store-google-play-reviews"
        }
    }
}

```

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/N4DchKPYqKxb7dWHV/builds/PMySvuSLfHtlvfMBp/openapi.json
