# App Store & Google Play Scraper (`eiv/app-store-scraper`) Actor

Scrape app metadata and reviews from the Apple App Store and Google Play. Ratings and prices per country, exact install counts, star histograms, versions and release notes, plus Play reviews with developer replies. Same app, every storefront, one run.

- **URL**: https://apify.com/eiv/app-store-scraper.md
- **Developed by:** [Eimantas V](https://apify.com/eiv) (community)
- **Categories:** E-commerce, Social media, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 app scrapes

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 Scraper

App metadata and reviews from **both** mobile stores, in one run. Ratings per country, the exact install count behind Google's rounded bucket, pricing, release cadence, and Google Play reviews with developer replies.

```
Pokémon GO        us 3.60   gb 3.74   de 3.50   in 4.06     spread 0.56
Nebula            us 4.57   gb 4.22   de 3.62   in 4.39     spread 0.95
Spotify           us 4.34   gb 4.26   de 4.18   in 4.16
```

Those are the same apps on the same day. A single-country rating tells you almost nothing about how an app is received — this Actor gets every storefront in one pass.

No API keys, no browser. 16 listings and 1,200 reviews in **1.4 seconds**.

***

### What you get

**Per app, per storefront**

| Group | Fields |
|---|---|
| **Ratings** | `score` (this country), `ratingCount`, **`ratingHistogram`** (1★–5★ breakdown), `reviewCount` |
| **Reach** | `installs` (Play's bucket), **`installsExact`** (the real number behind it) |
| **Money** | `price`, `currency`, `formattedPrice`, `isFree`, `inAppPurchaseRange`, `containsAds` |
| **Releases** | `version`, `released`, `updated`, **`daysSinceUpdated`**, `releaseNotes` |
| **Listing** | `title`, `developer`, `developerId`, `category`, `contentRating`, `icon`, `screenshotCount` |
| **Apple extras** | `bundleId`, `fileSizeBytes`, `minOsVersion`, `supportedLanguages`, `advisories` |

**Per review** (Google Play) — `rating`, `text`, `author`, `date`, `thumbsUp`, **`appVersion`**, `developerReply`, `developerReplyDate`.

`appVersion` is the one that turns reviews into an engineering signal: it tells you *which build* the complaints are about.

***

### Who this is for

- **Competitor tracking** — run daily, diff on `appId` + `country`. Rating moves, version bumps, price changes and install growth all fall out of the diff.
- **ASO and market research** — search a category and get every app's rating, installs, category and update cadence in one dataset.
- **Voice of customer** — pull 1-star reviews for a competitor, group by `appVersion`, and read what broke.
- **Localisation priorities** — a 0.5-star gap between storefronts usually means a translation, pricing or support problem in that market, not a product problem.
- **Due diligence** — `installsExact`, rating trajectory and `daysSinceUpdated` say more about an app's health than a pitch deck.

***

### Input

```json
{
  "apps": ["com.spotify.music", "1459969523"],
  "countries": ["us", "gb", "de", "jp"],
  "includeReviews": true,
  "maxReviewsPerApp": 500,
  "reviewRating": 1
}
```

Identifiers can be a package name, a numeric App Store id, or a URL from either store. A URL that names a storefront (`/de/app/…`, `&gl=IN`) overrides the country list for that app.

| Option | Default | Notes |
|---|---|---|
| `countries` | `["us"]` | One record per app **per country**, each billed |
| `stores` | both | Only affects search and bare ids |
| `searchTerms` | — | Map a whole category |
| `maxAppsPerSearch` | `30` | Your billing guard on search |
| `includeReviews` | `false` | Google Play only |
| `maxReviewsPerApp` | `200` | 100 arrive per request |
| `reviewRating` | — | `1`–`5`; filtering happens at the store |
| `reviewsWithinDays` | — | See the caveat below |
| `repliedOnly` | `false` | Only reviews the developer answered |
| `includeDescriptions` | `false` | Off keeps the dataset far smaller |

***

### Four things worth knowing

Each of these produced visibly wrong numbers before it was handled, and each was found by running against live stores.

**No App Store reviews, and no pretending otherwise.** Apple retired the public customer-reviews RSS feed — it now answers `200 OK` with zero entries on every page — and the replacement API needs a bearer token that is no longer served to anonymous clients. Both were checked directly, along with the client-rendered `?see-all=reviews` page. **Reviews in this Actor are Google Play only.** App Store *metadata* is complete and comes from Apple's own official endpoint.

**Google Play does not return reviews in date order.** One 100-review page was measured spanning 2024-08-28 to 2026-08-09, interleaved — 44 descending steps against 55 ascending. The obvious optimisation, stopping at the first review older than your window, silently truncates a recent-reviews query on its first page. This Actor pages past old reviews instead, and **sorts the output newest-first itself**. The cost is real: a 30-day window took 7 requests where an unfiltered pull took 3.

**On Google Play, `score` is per-country but `ratingCount` is global.** Pokémon GO reports 3.50 in Germany and 4.06 in India off the same 15,373,739 ratings. They are not a matched pair, so don't compute a weighted average across countries from them. Apple reports both per storefront.

**Google sometimes rounds `ratingCount`.** Spotify came back as exactly 36,104,000 on one request and 36,103,769 on another. `ratingHistogram` is not rounded — its five buckets summed to 36,103,978 against that rounded total. If you need a precise figure, or you want to know whether a 3.5 is lukewarm or polarised, use the histogram rather than the total.

**Titles are localised.** Nebula is "Nebula: Spiritual Guidance" in the US and "Nebula: Raum für Spirituelle Beratung" in Germany. Join storefronts on `appId`, never on `title`.

***

### Output

```json
{
  "store": "google", "appId": "com.spotify.music", "country": "de",
  "title": "Spotify: Music and Podcasts", "developer": "Spotify AB",
  "score": 4.18, "ratingCount": 36103782, "reviewCount": 1847743,
  "installs": "1,000,000,000+", "installsExact": 3084933551,
  "isFree": true, "inAppPurchaseRange": "$6.99 - $203.88 per item", "containsAds": true,
  "updated": "2026-08-11T…", "daysSinceUpdated": 1, "contentRating": "Teen",
  "recordType": "app"
}
```

Three ready-made dataset views: **Apps**, **Rating by country** and **Reviews**. Set `flattenOutput: true` for CSV.

***

### Honest limits

- **No App Store reviews.** Explained above.
- **`version` is often null on Google Play.** Listings that ship a different build per device publish no version at all; Apple always reports one.
- **An app missing from a storefront returns `NOT_FOUND`** and is **not charged**. Apps are routinely published to some countries and not others.
- **Install counts are Google Play only.** Apple publishes none, and any figure claiming otherwise is modelled, not measured.
- **Review coverage is a window, not an archive.** Play serves a few thousand recent reviews per app, not the full history.
- **Prices are in each storefront's currency** — that is the point of the country list, but it means don't compare `price` across countries without converting.

***

### Pricing

| Event | Price | When |
|---|---|---|
| Actor start | $0.005 | Once per run |
| App scraped | $0.004 | Per app listing profiled (one per app per country) |
| Review scraped | $0.0002 | Per review returned, after filters |

**$4.00 per 1,000 app listings** and **$0.20 per 1,000 reviews**. Tracking 20 competitor apps across 10 storefronts daily costs about $0.85 a day. Listings that fail or don't exist in a storefront are never charged.

***

### Tips

- **Schedule daily and diff on `appId` + `country`.** A `daysSinceUpdated` that resets is a release; a `score` that drops is a release that went badly.
- **`reviewRating: 1` is the cheapest early-warning system** — you pay only for the complaints.
- **Group 1-star reviews by `appVersion`** to find the exact build a regression shipped in.
- **Track `ratingHistogram.oneStar` over time, not `score`.** An average moves slowly once an app has millions of ratings; the 1-star bucket moves the day something breaks.
- **Watch the spread across storefronts, not the average.** A wide spread points at a specific market; a uniform drop points at the product.
- **Use `searchTerms` once to discover a category, then feed the resulting `appId`s back as `apps`** for cheap recurring monitoring.

# Actor input Schema

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

App identifiers or store URLs, one per line. Accepts an Android package name ('com.spotify.music'), a numeric App Store id ('1459969523'), or a full apps.apple.com / play.google.com URL. A URL that names a storefront wins over the country list below.

## `searchTerms` (type: `array`):

Search both stores and profile everything found. Use to map a whole category rather than a known list of apps.

## `maxAppsPerSearch` (type: `integer`):

Cap on results taken from each search term, per store, per country. Your main billing guard when using search.

## `startUrls` (type: `array`):

Alternative to Apps, for feeding output from another Actor straight into this one.

## `sourceDatasetId` (type: `string`):

Read app identifiers from an existing dataset instead of typing them.

## `sourceDatasetField` (type: `string`):

Which field on the source dataset holds the app identifier.

## `stores` (type: `array`):

Which stores to query. Only applies to bare identifiers and search terms — a package name is always Google Play and a numeric id is always the App Store, whatever is selected here.

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

Two-letter country codes to profile, e.g. us, gb, de, jp, br. Worth more than it looks: the same app scored 4.57 in the US App Store and 3.62 in Germany, and Pokémon GO runs 3.50 in Germany against 4.06 in India on Google Play. One record is produced per app per storefront, and each is billed.

## `includeReviews` (type: `boolean`):

Also scrape user reviews with star rating, text, date, helpful count, the reviewer's app version and any developer reply. Google Play only — Apple retired its public reviews feed and the replacement needs credentials this Actor does not have.

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

Hard cap per app per storefront. 100 reviews arrive per request. Set 0 to skip reviews entirely.

## `reviewRating` (type: `integer`):

Keep only reviews with this many stars, 1 to 5. Pushed into the request so the store does the filtering. 0 keeps every rating.

## `reviewsWithinDays` (type: `integer`):

Keep only recent reviews. Costs more requests than you might expect: Google Play does not return reviews in date order — a single page was measured spanning two years — so the Actor pages past old reviews rather than stopping at the first one. 0 keeps everything.

## `repliedOnly` (type: `boolean`):

Keep only reviews the developer has publicly answered. Useful for studying how a competitor handles complaints.

## `includeDescriptions` (type: `boolean`):

Store each app's full store description and release notes. Off by default because descriptions dominate dataset size; a 300-character snippet is always included.

## `includeScreenshots` (type: `boolean`):

Store the full list of screenshot URLs. A count is always included regardless.

## `flattenOutput` (type: `boolean`):

Emit flat dot-notation columns with arrays joined by ' | '. Use for spreadsheets; leave off for warehouses.

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

App listings processed in parallel. A Google Play listing is about 1.2 MB, so raise memory alongside this.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout.

## `maxRetries` (type: `integer`):

Retries for connection resets and 5xx responses. A 404 is not retried, because an app missing from a storefront stays missing.

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

Usually unnecessary — both stores serve this data to anonymous clients. Worth enabling for very large runs, or to pin requests to a country.

## Actor input object example

```json
{
  "apps": [
    "com.spotify.music",
    "com.duolingo",
    "1459969523"
  ],
  "maxAppsPerSearch": 30,
  "sourceDatasetField": "appId",
  "stores": [
    "apple",
    "google"
  ],
  "countries": [
    "us"
  ],
  "includeReviews": false,
  "maxReviewsPerApp": 200,
  "reviewRating": 0,
  "reviewsWithinDays": 0,
  "repliedOnly": false,
  "includeDescriptions": false,
  "includeScreenshots": false,
  "flattenOutput": false,
  "maxConcurrency": 10,
  "requestTimeoutSecs": 45,
  "maxRetries": 1,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

App records carry recordType 'app'; review records carry recordType 'review'.

# 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": [
        "com.spotify.music",
        "com.duolingo",
        "1459969523"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("eiv/app-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": [
        "com.spotify.music",
        "com.duolingo",
        "1459969523",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("eiv/app-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": [
    "com.spotify.music",
    "com.duolingo",
    "1459969523"
  ]
}' |
apify call eiv/app-store-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,eiv/app-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/bxd4ceicBRKT7sgXm/builds/2GhvJ4ss65bZ1xCPw/openapi.json
