# Google Play Store Scraper (Apps, Charts, Similar, Reviews) (`mdcurt/google-play-app-scraper`) Actor

Google Play app details, search results, developer catalogs, similar apps and top charts as clean JSON for any country: rating, installs, price, in-app purchases, content rating, version, last update, developer contact, screenshots and optional recent reviews. One record per app.

- **URL**: https://apify.com/mdcurt/google-play-app-scraper.md
- **Developed by:** [Matthew Curtis](https://apify.com/mdcurt) (community)
- **Categories:** E-commerce, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 app records

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Google Play Store Scraper (Apps, Charts, Similar, Reviews)

Turn any Google Play query into clean JSON: **app details**, **search results**, **developer catalogs**, **similar-apps clusters** and **top charts**, for one or many countries in a single run, with optional recent reviews embedded in each app record. Built for reliability and speed — one app lookup returns in about a second, a 120-app run finishes in under 30 seconds — and priced per app record so a single lookup costs well under a cent.

Typical uses: ASO and competitor tracking, market research (installs, ratings, price, IAP range per country), lead lists of app publishers (developer website and support email are public on every listing), app-store monitoring in agents and automations, and keeping a product database in sync with Play Store metadata.

### What you get

One dataset item per app. App-id queries always return the **full record**; search and developer queries return the full record when **Fetch full details** is on (default) or a lighter **listing card** when it is off.

| Field | Meaning |
|---|---|
| `id` | Android package name (`com.spotify.music`) — the stable key |
| `url` | Play Store URL for the requested locale |
| `title`, `summary` | Listing title and short description |
| `descriptionSnippet`, `descriptionLength` | First 300 characters of the long description plus its full length (the full text is not reproduced — follow `url` for it) |
| `developer` | `{ name, id, url, website, email, legalName, address }` as shown publicly on the listing |
| `price`, `currency`, `priceText`, `free` | Price in the store front's currency (0 = free) |
| `offersInAppPurchases`, `inAppPurchaseRange` | IAP flag and the displayed range (`"$4.99 - $203.88 per item"`) |
| `score`, `scoreText`, `ratingsCount`, `reviewsCount`, `histogram` | Average rating (null for apps with too few ratings), counts and 1–5 star histogram |
| `installs`, `minInstalls`, `maxInstalls` | Displayed install bucket and Google's numeric bounds |
| `genre`, `genreId`, `categories[]` | Category name and id (`MUSIC_AND_AUDIO`) |
| `contentRating`, `contentRatingDescription` | Age rating for the locale (`Teen`, `USK ab 0 Jahren`, …) |
| `adSupported`, `available`, `preregister`, `playPass` | Flags |
| `released`, `updatedAt`, `version`, `androidVersion` | Release date as displayed, last update as ISO 8601, current version (null when it "varies with device") |
| `recentChangesSnippet` | First 300 characters of "What's new" |
| `privacyPolicyUrl`, `icon`, `headerImage`, `images[]`, `video` | Links to the listing's media (not downloaded) |
| `reviews[]` | Only when **Recent reviews per app** > 0: `{ id, author, score, text (≤300 chars), textTruncated, date, thumbsUp, appVersion, replyText, replyDate, url }`, newest first |
| `locale`, `kind`, `source`, `scrapedAt` | `locale` = `{language, country}`; `kind` = `detail` or `listing`; `source` = `{query, type, rank}` says which query produced the record and its position in that result list |

Listing cards (`kind: "listing"`) carry `id, url, title, summary, developer {name, id}, score, scoreText, price, currency, free, icon` plus `locale, source, scrapedAt`.

#### Sample record

```json
{
  "id": "com.spotify.music",
  "url": "https://play.google.com/store/apps/details?id=com.spotify.music&hl=en&gl=us",
  "title": "Spotify: Music and Podcasts",
  "summary": "Listen to songs, play podcasts, create playlists and discover music you'll love",
  "descriptionSnippet": "With the Spotify music and podcast app, you can play millions of songs, albums and original podcasts for free. We have e…",
  "descriptionLength": 3868,
  "developer": {
    "name": "Spotify AB",
    "id": "Spotify AB",
    "url": "https://play.google.com/store/apps/developer?id=Spotify%20AB&hl=en&gl=us",
    "website": "https://www.spotify.com",
    "email": "support@spotify.com",
    "legalName": "Spotify AB",
    "address": "Regeringsgatan 19, 111 53 Stockholm, Sweden"
  },
  "price": 0, "currency": "USD", "priceText": "Free", "free": true,
  "offersInAppPurchases": true, "inAppPurchaseRange": "$4.99 - $203.88 per item",
  "score": 4.3491316, "scoreText": "4.3", "ratingsCount": 36534575, "reviewsCount": 1857195,
  "histogram": { "1": 3715958, "2": 1065335, "3": 1308283, "4": 3102776, "5": 27342207 },
  "installs": "1,000,000,000+", "minInstalls": 1000000000, "maxInstalls": 3172798659,
  "genre": "Music & Audio", "genreId": "MUSIC_AND_AUDIO",
  "categories": [{ "name": "Music & Audio", "id": "MUSIC_AND_AUDIO" }],
  "contentRating": "Teen", "contentRatingDescription": null,
  "adSupported": true, "available": true, "preregister": false, "playPass": false,
  "released": "May 27, 2014", "updatedAt": "2026-10-23T12:37:22.000Z",
  "version": null, "androidVersion": null,
  "recentChangesSnippet": "We’re always making changes and improvements to Spotify. To make sure you don’t miss a thing, just keep your Updates turned on.",
  "privacyPolicyUrl": "https://www.spotify.com/legal/privacy-policy/",
  "icon": "https://play-lh.googleusercontent.com/IzQgYCcnCFCD08GR-3bdtcT8xzOvrNkC84avGT5CwTX2VIqmTmKKJcP_Cd4JoBOdmCMlTndlOzV6hrthg2fOWA",
  "headerImage": "https://play-lh.googleusercontent.com/WdedaGD6EAXZgy88wZM97aIcenQ5Cqzu_IUWBMMalkZmVe2lhv_3YqIUh-6pFqLv27kZ44cQX3qxyZTXHH-S91Q",
  "images": ["https://play-lh.googleusercontent.com/c1jnjL-9CIFlwlTMX3xNyeo8aEH39sNuC5zpTHhfW8keGxQE3TBMRX9Rm3VqM3E03Cyl9AqtnxuKvxJpcW0WCA", "… 39 more"],
  "video": null,
  "reviews": [
    { "id": "c8a50b3d-232e-45ae-b30a-99bc96c67830", "author": "Allison H.", "score": 1, "text": "No easy way to delete your account …", "textTruncated": false, "date": "2026-10-06T18:09:25.911Z", "thumbsUp": 0, "appVersion": null, "replyText": null, "replyDate": null, "url": "https://play.google.com/store/apps/details?id=com.spotify.music&reviewId=c8a50b3d-232e-45ae-b30a-99bc96c67830" }
  ],
  "locale": { "language": "en", "country": "us" },
  "kind": "detail",
  "source": { "query": "com.spotify.music", "type": "app", "rank": null },
  "scrapedAt": "2026-10-24T23:39:19.707Z"
}
```

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `queries` | string\[] | `["meditation"]` | What to look up. Each entry is auto-detected: a package name (`com.spotify.music`) → app details; a search term (`meditation`) → search results; `dev:Spotify AB`, a numeric developer id (`5700313618786177705`) or a developer URL → that publisher's catalog; `similar:com.spotify.music` → Google's "Similar apps" cluster for that app; `chart:TOP_FREE`, `chart:TOP_PAID:GAME`, `chart:GROSSING:FINANCE` → top charts (collections `TOP_FREE`, `TOP_PAID`, `GROSSING`; any Play category id such as `GAME`, `FINANCE`, `GAME_PUZZLE`; default all apps); any `play.google.com` app / search / developer / cluster URL also works. |
| `locales` | string\[] | `["en-US"]` | One or more store fronts as `language-COUNTRY`. Every query runs once per store front and each (app, country) pair is its own record, so one run compares prices, currency, availability, content ratings and rankings across countries. `de-DE`, `pt-BR`, `ja-JP`, `en-IN`, … |
| `maxItems` | integer | `50` | Upper bound on app records for the whole run (1–5000). Direct app ids take one slot each; the rest is split evenly across listing queries × store fronts, so `maxItems: 50` with five keywords returns the top 10 of each. Each record is one charged dataset item. |
| `fetchDetails` | boolean | `true` | Open every app found by a search or developer query for the full record. Turn off for a cheap shortlist (one request per query, no per-app requests). |
| `reviewsPerApp` | integer | `0` | Embed up to this many newest reviews (0–200) in each app record. Reviews are part of the record and are not charged separately. |
| `knownIds` | string\[] | `[]` | Incremental mode — app ids you already have are skipped before any request is made (see Monitoring below). |
| `proxyConfiguration` | object | Apify datacenter proxy | Google Play serves datacenter IPs at polite rates; switch to residential only if `SUMMARY.blocked` is ever non-zero. |

#### Examples

One app, full record (the agent / MCP call):

```json
{ "queries": ["com.spotify.music"], "locale": "en-US", "maxItems": 1 }
```

Top results for a keyword in Germany with the 20 newest reviews each:

```json
{ "queries": ["haushaltsbuch"], "locale": "de-DE", "maxItems": 20, "reviewsPerApp": 20 }
```

A publisher's whole catalog as a cheap shortlist (no detail requests):

```json
{ "queries": ["dev:Microsoft Corporation", "5700313618786177705"], "maxItems": 500, "fetchDetails": false }
```

Competitor set and charts for a category, compared across three countries:

```json
{ "queries": ["similar:com.duolingo", "chart:TOP_FREE:EDUCATION", "chart:GROSSING:EDUCATION"], "locales": ["en-US", "pt-BR", "ja-JP"], "maxItems": 300 }
```

### Pricing

Pay per event, no subscription:

| Event | Price |
|---|---|
| Actor start | $0.005 per run (per GB of memory, minimum one — the Actor runs in 256 MB) |
| App record | $0.001 per dataset item |

A single app lookup therefore costs **$0.006**; 1,000 full app records with reviews cost about **$1.01** plus your platform compute (a few seconds per hundred apps). Embedded reviews are free: 200 reviews for an app come inside one $0.001 record. There is no extra charge for different locales, for developer catalogs or for listing cards.

### Monitoring / incremental recipe

Schedule the Actor with the queries you care about and pass the app ids you already hold in `knownIds`. Only apps that are new in that search or catalog since last time are fetched and charged, so a daily "new apps for keyword X / new releases by publisher Y" monitor costs the Actor start plus a handful of records. To track metadata changes of known apps (new version, rating movement, price change), schedule a run with those package names as `queries` and diff the `version`, `updatedAt`, `score`, `ratingsCount` and `price` fields; each app is one record.

The run also writes a `SUMMARY` record to the key-value store with counts (pushed, duplicatesSkipped, knownSkipped, notFound, blocked, rateLimited), the time to the first record, p95 latency, the adaptive timeout, peak concurrency and any inferred rate limit — handy for alerting.

### Speed and reliability

Measured on the live site on 25 Oct 2026 from a datacenter IP (see `tests.md`): a single app lookup pushed its record **1.1 s** after start; a 10-app search with 20 reviews each took **5.6 s** (≈107 records/min including 200 reviews); an 86-record developer + similar-apps shortlist took **2.1 s** (5 requests); a 225-record run across a search, a developer, a similar-apps cluster and two charts took **44 s** at up to 8 concurrent requests (≈310 records/min), 233 requests, 0 failures.

Under the hood: plain HTTP only (no browser — the data is server-rendered), Google's own continuation endpoint for pagination, adaptive concurrency that starts at 2 and grows only while the error rate stays under 2% (and halves on throttling), a per-host minimum delay, timeouts derived from the measured latency distribution, transient errors retried up to three times, challenge pages detected and counted instead of retried, and every record deduplicated by package name before it is pushed. If a page is not found (removed app, wrong id) it is logged and skipped, never retried.

### Limitations

- Google Play returns at most about 25–30 results for a search term without a signed-in session; that is what every scraper gets. For more coverage use several related terms, or a developer query (catalogs paginate to a few hundred apps).
- `description` and `recentChanges` are returned as 300-character snippets with the full length; reviews are snippets too (`textTruncated` tells you when). Media is linked, not downloaded.
- `score` / `ratingsCount` are `null` for apps that do not yet show a rating. `version` and `androidVersion` are `null` when Google shows "Varies with device". `priceText` for free apps is `"Free"` in every locale.
- The data-safety section and app permissions are not collected (Google disallows that path for crawlers).
- Top charts return up to 200 apps per collection/category (Google's cap); `similar:` clusters return the first 50 Google shows, with continuation when available. When a listing query has fewer apps than its share of `maxItems`, the unused share is not redistributed to other queries, so a run can finish below `maxItems`.
- Pre-registration and unavailable apps are returned with `available: false` / `preregister: true` and whatever fields Google shows.
- Country availability matters: an app that is not published in the selected `locale` country returns a not-found page and is skipped.

### Get alerted when records change

Pair this Actor with [Dataset Diff & Alert Monitor](https://apify.com/mdcurt/dataset-diff-monitor) to receive only the new, changed or removed records in Slack, Discord, Telegram or a webhook. Schedule the monitor, not this Actor: it runs this Actor for you, compares runs by `id`, and a run that finds nothing costs only the comparison. This is the competitor watch from the recipe above without writing the diff yourself: a new version, a rating move or a price change on any tracked app becomes one message.

```json
{
  "sourceActorId": "mdcurt/google-play-app-scraper",
  "sourceInput": {
    "queries": [
      "com.duolingo",
      "com.babbel.mobile.android.en"
    ],
    "locale": "en-US",
    "maxItems": 2
  },
  "keyFields": [
    "id"
  ],
  "watchFields": [
    "version",
    "score",
    "ratingsCount",
    "price",
    "updatedAt"
  ],
  "notifyOn": [
    "changed"
  ],
  "labelField": "title",
  "urlField": "url",
  "slackWebhookUrl": "https://hooks.slack.com/services/…"
}
```

### Changelog

- **1.1.0** (2026-10-25) — Similar-apps clusters (`similar:`), top charts (`chart:COLLECTION:CATEGORY`), cluster URLs, multi-country `locales` list, fair per-query split of `maxItems`, per-locale record keys.
- **1.0.0** (2026-10-24) — Initial release: app details, search, developer catalogs with continuation paging, embedded recent reviews, locale support, incremental `knownIds`, adaptive concurrency and timeouts, SUMMARY record.

# Actor input Schema

## `queries` (type: `array`):

What to look up - one record per app. Each entry is auto-detected: an Android package name (`com.spotify.music`) → full app details; a search term (`meditation`) → the first ~25-30 search results; `dev:Spotify AB` or a numeric developer id → that publisher's catalog; `similar:com.spotify.music` → Google's similar-apps cluster for that app; `chart:TOP_FREE`, `chart:TOP_PAID:GAME`, `chart:GROSSING:FINANCE` → top charts (collection TOP_FREE / TOP_PAID / GROSSING, optional category id); any play.google.com app, search, developer or cluster URL also works. A single app id returns in about a second.

## `locales` (type: `array`):

One or more store fronts as `en-US`, `de-DE`, `pt-BR`, `ja-JP`, `en-IN`… Every query runs once per store front and each (app, country) pair is its own record, so you can compare prices, availability, ratings and rankings across countries in one run.

## `maxItems` (type: `integer`):

Stop after this many app records in total across all queries (each record is one charged dataset item).

## `fetchDetails` (type: `boolean`):

On: every app found by a search or developer query is opened for the full record (installs, ratings count, histogram, IAP, content rating, version, update date, developer contact, screenshots). Off: search and developer queries return the lighter listing card (title, developer, rating, price, icon) with no extra requests - fastest and cheapest for shortlists. App-id queries always return full details.

## `reviewsPerApp` (type: `integer`):

Embed up to this many most-recent user reviews in each app record (author, 1-5 score, text snippet of at most 300 characters, date, thumbs up, app version, developer reply). 0 = no reviews. Reviews are part of the app record and are not charged separately.

## `knownIds` (type: `array`):

App IDs you already have. Apps in this list are skipped before any detail request is made, so a scheduled run of a search or developer query only pays for apps that are new since last time.

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

Google Play serves datacenter IPs fine at polite rates, so Apify datacenter proxies (the default) are enough; switch to residential only if your runs report blocked pages.

## Actor input object example

```json
{
  "queries": [
    "meditation"
  ],
  "locales": [
    "en-US"
  ],
  "maxItems": 50,
  "fetchDetails": true,
  "reviewsPerApp": 0,
  "knownIds": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `apps` (type: `string`):

One record per Google Play app: id (package name), url, title, summary, developer, price, currency, score, ratingsCount, reviewsCount, histogram, installs, genre, categories, contentRating, version, updatedAt, images and optional reviews\[].

## `summary` (type: `string`):

SUMMARY record: queries processed, records pushed, duplicates skipped, not-found and blocked pages, inferred rate limit, p95 latency, time to first record and compute seconds.

# 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 = {
    "queries": [
        "meditation"
    ],
    "locales": [
        "en-US"
    ],
    "maxItems": 50,
    "fetchDetails": true,
    "reviewsPerApp": 0,
    "knownIds": [],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("mdcurt/google-play-app-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 = {
    "queries": ["meditation"],
    "locales": ["en-US"],
    "maxItems": 50,
    "fetchDetails": True,
    "reviewsPerApp": 0,
    "knownIds": [],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("mdcurt/google-play-app-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 '{
  "queries": [
    "meditation"
  ],
  "locales": [
    "en-US"
  ],
  "maxItems": 50,
  "fetchDetails": true,
  "reviewsPerApp": 0,
  "knownIds": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call mdcurt/google-play-app-scraper --silent --output-dataset

```

## MCP server setup

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