# Full Scraper Reviews App Store & Google Play (`alexmorain/app-store-play-store-scraper`) Actor

Reviews, metadata and keyword rankings for iOS and Android apps, in one common schema, across every storefront. Full review sweep that does not lose reviews on the way. No login, no cookies.

- **URL**: https://apify.com/alexmorain/app-store-play-store-scraper.md
- **Developed by:** [Alex Morain](https://apify.com/alexmorain) (community)
- **Categories:** Automation, Agents, AI
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 app on a storefronts

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/actors/running/actors-in-store.md#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 Intelligence

Reviews, metadata and keyword rankings for iOS and Android apps, in one common schema, across every storefront. One input, both stores, no login, no cookies.

### Why most iOS review scrapers quietly return nothing

Apple's public RSS review feed is capped and, worse, it lies. Whole pages come back empty, scattered between full ones, and page one is always empty. A scraper that paginates the normal way, stopping when a page is empty, returns **zero** reviews on real apps. Check it yourself in thirty seconds:

| App | Storefront | RSS feed (what most scrapers use) | This actor |
|---|---|---|---|
| Spotify | US | **0 reviews** across all 10 pages | 1200+ reviews |
| WhatsApp | US | 100, or 0 if it stops on the first empty page | 600+ reviews |
| Doctolib | FR | 150 | full depth |

This actor reads Apple's real store API instead of the RSS feed. It sweeps every page to the true end, so it collects reviews where RSS-based tools collect nothing. Same discipline on Google Play: it follows the continuation token to the end rather than stopping at an arbitrary limit.

### What you get

- **Reviews** from both stores in a single schema. An iOS review and an Android review come out with the same field names, the same types, the same date format. No post-processing to merge two formats.
- **App metadata** per storefront: name, developer, average rating, rating and review counts, version, genre, installs.
- **Keyword rankings**: for a search term and a storefront, the ranked list of apps. The segment nobody serves well.

### How to scrape the App Store and Google Play

1. Open the **Input** tab. Two apps are already filled in, one from each store, so you can press **Start** immediately and see the output shape.
2. Replace them with your own apps. Paste the store URL straight from your browser, or use the App Store numeric id (`310633997`) or the Google Play package name (`com.whatsapp`). Mixed lists are fine.
3. Pick the **storefronts**. Each store is partitioned by country: a US review never appears in the French store. One storefront to start, more when you need the comparison.
4. Leave **Reviews** and **App metadata** ticked. They are covered by the same billable unit, so taking both is strictly better than taking one.
5. Press **Start**, then download the dataset as JSON, CSV or Excel.

#### Which fields should I fill?

| If you want to... | Fill in | Leave alone |
|---|---|---|
| Read what users say about an app | `apps`, one storefront | everything else |
| Compare an app across markets | `apps`, several `countries` | everything else |
| Monitor new feedback daily | `mode: new-only` and a schedule | everything else |
| Track App Store search positions | tick `rankings`, fill `keywords` | `apps` can stay empty |
| Benchmark competitors | all competitor `apps` in one list | everything else |

Only `apps` really matters, and even that can stay empty if you run keyword rankings alone.

### Input

| Field | What it does |
|---|---|
| `apps` | iOS ids (`310633997`), Android packages (`com.whatsapp`), or full store URLs. Mixed lists are fine. |
| `countries` | Storefronts to sweep, e.g. `US`, `FR`, `GB`. Each app is swept on each storefront. |
| `include` | Tick `reviews`, `metadata`, `rankings` independently. |
| `keywords` | Search terms to track, for ranking mode. Apple in this version. |
| `mode` | `all` returns every review. `new-only` keeps a persistent registry and returns only reviews unseen since the last run, for recurring pipelines. |
| `maxReviewsPerApp` | Upper bound per app and storefront. |

Example:

```json
{
  "apps": ["310633997", "com.spotify.music", "https://apps.apple.com/fr/app/id925339063"],
  "countries": ["us", "fr"],
  "include": ["reviews", "metadata", "rankings"],
  "keywords": ["music streaming"],
  "mode": "all",
  "maxReviewsPerApp": 500
}
```

### Output

Every record carries a `type`: `review`, `metadata` or `ranking`. Reviews share these fields across both stores:

```json
{
  "type": "review",
  "store": "apple",
  "app_id": "310633997",
  "country": "us",
  "review_id": "13657831289",
  "rating": 5,
  "title": "WhatsApp not bad",
  "body": "WhatsApp's not bad at all...",
  "author": "Ed Bradway",
  "version": null,
  "posted_at": "2026-01-21T04:48:38Z",
  "is_edited": false,
  "reply_body": null,
  "reply_at": null
}
```

### Pricing

Pay per event. The review itself is never billed by the unit, that market is a race to the bottom. You pay for the collection work:

- **Actor start**: once per run.
- **App on a storefront**: one app swept on one storefront, metadata and full review sweep included, however many reviews that returns.
- **Keyword ranking**: one keyword tracked on one storefront.

### Notes

Both stores are reached over plain HTTP. No proxy is needed in normal use. For very high-volume sweeps you can enable an Apify proxy in the input. The actor degrades cleanly: one failing app or storefront never stops the rest of the run.

# Actor input Schema

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

The apps to collect, one per line. Four accepted forms, freely mixed in the same list:<br>• an App Store numeric id: <code>310633997</code><br>• a Google Play package name: <code>com.whatsapp</code><br>• a full App Store URL: <code>https://apps.apple.com/us/app/id324684580</code><br>• a full Google Play URL: <code>https://play.google.com/store/apps/details?id=com.spotify.music</code><br><br><b>Where to find them:</b> the numeric id is at the end of any App Store URL, right after <code>id</code>. The package name is the <code>id=</code> parameter of any Google Play URL. Pasting the URL straight from your browser always works.<br><br>Anything the Actor cannot read is skipped with an explanation in the log, and the rest of the list still runs. Leave this empty only if you run in keyword-ranking mode.

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

ISO 3166-1 country codes of the app stores to sweep: <code>us</code>, <code>fr</code>, <code>gb</code>, <code>de</code>, <code>jp</code>... Case does not matter.<br><br><b>Why it matters:</b> both stores are partitioned by country. A US review never appears in the French store, and an app can be ranked 3rd in one country and absent from another. One app on three storefronts means three sweeps, three sets of reviews, and three billable units.<br><br>Start with one storefront, add more once you have seen the shape of the data. Leave empty for the US storefront only.

## `include` (type: `array`):

Tick each item independently.<br>• <b>Reviews</b>: every review the store serves, swept to the real end of the feed.<br>• <b>App metadata</b>: the store profile of the app, name, developer, average rating, rating count, version, genre, price, installs, description and icon.<br>• <b>Keyword rankings</b>: for each search term you give below, the ranked list of apps in that storefront. App Store only in this version.<br><br>Reviews and metadata are covered by the same single unit per app and storefront, so leaving both ticked is strictly the best value. Rankings are billed separately, per keyword and storefront.

## `mode` (type: `string`):

<b>All reviews</b> returns everything the store serves, on every run. That is what you want the first time, and for any one-off analysis.<br><br><b>New reviews only</b> keeps a persistent registry per app and storefront and returns only reviews it has never seen. Schedule it daily and you get a clean feed of new feedback instead of the same corpus again. The first run in this mode still returns the whole backlog, since nothing has been seen yet.

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

Upper bound on reviews collected for one app on one storefront.<br><br><b>It does not change the price</b>, which is per app and storefront whatever the volume, so raise it freely when you want depth. Use it to control run duration instead: <code>100</code> gives you the current mood of an app in seconds, <code>500</code> is a solid sample, several thousand walks the feed to its real end.<br><br>The sweep never stops on an apparently empty page, which is how it gets past the App Store quirk that makes RSS-based scrapers return nothing at all.

## `keywords` (type: `array`):

Search terms to track, one per line: <code>music streaming</code>, <code>meditation</code>, <code>expense report</code>. For each term and each storefront the Actor records the ranked list of apps, position by position. This is App Store SEO tracking.<br><br>Only used when <b>Keyword rankings</b> is ticked above, so leave it empty otherwise. It works with no app in the list at all: keywords alone is a perfectly valid run.

## `rankingDepth` (type: `integer`):

How many ranked apps to record per keyword and storefront. <code>10</code> answers "who owns the top of this search", <code>50</code> covers the realistic discovery zone, <code>200</code> is the deepest the store serves. The price is per keyword and storefront, not per position, so depth is free.

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

<b>Leave this off.</b> Both stores are read over plain HTTP through their public endpoints and need no proxy in normal use; enabling one only makes the run slower. Turn it on if you sweep hundreds of app-storefront pairs in a single run and start seeing rate limits in the log.

## Actor input object example

```json
{
  "apps": [
    "310633997",
    "com.whatsapp",
    "https://apps.apple.com/us/app/id324684580"
  ],
  "countries": [
    "us",
    "fr",
    "gb"
  ],
  "include": [
    "reviews",
    "metadata"
  ],
  "mode": "all",
  "maxReviewsPerApp": 500,
  "keywords": [
    "music streaming",
    "meditation"
  ],
  "rankingDepth": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Individual reviews from both stores, common fields: store, app\_id, country, rating, title, body, author, version, posted\_at, developer reply.

## `metadata` (type: `string`):

App listings per storefront: name, developer, average rating, rating and review counts, version, genre, installs.

## `rankings` (type: `string`):

Ranked apps per keyword and storefront, with position.

# 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": [
        "310633997",
        "com.spotify.music"
    ],
    "countries": [
        "us"
    ],
    "maxReviewsPerApp": 500,
    "rankingDepth": 50,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("alexmorain/app-store-play-store-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 = {
    "apps": [
        "310633997",
        "com.spotify.music",
    ],
    "countries": ["us"],
    "maxReviewsPerApp": 500,
    "rankingDepth": 50,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("alexmorain/app-store-play-store-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 '{
  "apps": [
    "310633997",
    "com.spotify.music"
  ],
  "countries": [
    "us"
  ],
  "maxReviewsPerApp": 500,
  "rankingDepth": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call alexmorain/app-store-play-store-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,alexmorain/app-store-play-store-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/r96xcvjaBEcfRreYO/builds/hvoPGvZiAWj8bJLUG/openapi.json
