# Google Play Scraper - App Details & Rating Tracker (`neverempty/google-play-app-details-tracker`) Actor

For ASO teams, app marketers and competitor trackers: rating, rating count, star histogram, installs, price, ads, in-app purchases, last update and version for many Google Play apps. Matched the live store page on 5 of 5 apps checked 2026-09-21. Monitoring mode returns only apps that changed.

- **URL**: https://apify.com/neverempty/google-play-app-details-tracker.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.46 / 1,000 app details returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Scraper - App Details & Rating Tracker

For ASO teams, app marketers and competitor trackers: the rating, rating count, star histogram, installs, price, ads, in-app purchases, last update date, version and content rating of every Google Play app in your list, one row per app. On 2026-09-21 the rating, rating count, downloads and update date matched what the live store page shows in a browser for 5 of 5 apps checked (WhatsApp, Spotify, Minecraft, AntennaPod, Termux). Paste package names or store URLs, get a clean table, and turn on monitoring mode to receive only the apps whose numbers or release changed since the last run.

Export as JSON, CSV or Excel. Unofficial. Public data only.

### What it does

- Reads the public app page `https://play.google.com/store/apps/details?id=<package>` for each app, in the country and language you choose.
- Many apps per run (up to 1,000), from package names (`com.spotify.music`) or URLs, mixed.
- **Does not fail on bad entries.** An app that does not exist, a country Google Play rejects, a bot check or a typo comes back as a free row that says why. The run carries on with the rest of the list.
- **Monitoring mode** remembers each app and returns only the apps whose shown rating, rating count, installs, last update, version or price changed, with the previous value and the difference.

It only reads app detail pages. It does not use Google Play search, reviews or the data-safety pages (robots.txt of play.google.com disallows those for crawlers).

### Input

| Field | What it does |
|---|---|
| `apps` | Package names or Google Play app URLs. Case-sensitive: `com.whatsapp` works, `COM.WHATSAPP` does not. A URL's own `gl` and `hl` win over `country` and `language` for that app. Leave it out (with monitoring off) to read two example apps. |
| `country` | Two-letter store country (`us`, `gb`, `de`, `jp`, `in` ...). The average rating, the split of the star histogram, price, currency and whether the app can be installed differ by country (Minecraft: 4.3 in the US, 4.0 in Japan). The rating count and installs are not per country: the same run read 5,838,279 ratings for Minecraft in both the US and Japan, and 20 apps read in the US and the UK had the same installs. Google Play answers HTTP 400 for some unknown codes (XX) but silently shows the US store for others (ZZ); check `currency` if you are unsure. |
| `language` | Page language (`en`, `en-GB`, `de`, `ja` ...). Changes the texts and the shown labels (`ratingShown`, `installsShown`, `priceText`, `updatedText`), not the numbers. Google Play falls back to English for a language it does not know; `pageLanguage` says what you got. |
| `maxApps` | With monitoring off, stop after this many charged rows (a free row says how many entries were not read). |
| `monitoringMode` | Return an app only when it changed since the row last returned for it. The first run returns every app once. |
| `changeOn` | What counts as a change in monitoring mode: `any`, `rating`, `installs`, `release` or `price`. |
| `resetMonitoringState` | Forget everything remembered and start over (turn it off again after one run). |
| `useProxy` | Retry a page through a datacenter proxy only when Google Play answers with a bot check or a rate limit. |

Example:

```json
{
    "apps": ["com.spotify.music", "com.duolingo", "https://play.google.com/store/apps/details?id=com.mojang.minecraftpe&gl=JP&hl=ja"],
    "country": "us",
    "language": "en",
    "monitoringMode": true,
    "changeOn": "any"
}
```

### Output

One row per app. Example (WhatsApp, US, read on 2026-09-21, monitoring columns shortened):

```json
{
    "status": "ok",
    "appId": "com.whatsapp",
    "appUrl": "https://play.google.com/store/apps/details?id=com.whatsapp",
    "country": "us",
    "language": "en",
    "pageLanguage": "en",
    "title": "WhatsApp Messenger",
    "developer": "WhatsApp LLC",
    "developerId": "WhatsApp LLC",
    "category": "Communication",
    "categoryId": "COMMUNICATION",
    "rating": 4.616693,
    "ratingShown": "4.6",
    "ratingCount": 244067678,
    "ratingHistogram": { "oneStar": 10883019, "twoStars": 3458555, "threeStars": 8912990, "fourStars": 21819053, "fiveStars": 198994008 },
    "installsText": "10,000,000,000+",
    "installsShown": "10B+",
    "minInstalls": 10000000000,
    "realInstalls": 12322703000,
    "price": 0,
    "currency": "USD",
    "priceText": null,
    "isFree": true,
    "containsAds": false,
    "inAppPurchases": true,
    "inAppPurchaseRange": "$0.40 - $199.99 per item",
    "installableInCountry": true,
    "updatedAt": "2026-09-19T00:49:04.000Z",
    "updatedText": "Sep 19, 2026",
    "releasedAt": "2010-10-18T20:58:53.000Z",
    "version": null,
    "versionVariesWithDevice": true,
    "minAndroidVersion": null,
    "contentRating": "Everyone",
    "summary": "Simple. Reliable. Private.",
    "descriptionStart": "WhatsApp from Meta is a FREE messaging ...",
    "descriptionLength": 2200,
    "iconUrl": "https://play-lh.googleusercontent.com/...",
    "change": "details-changed",
    "changedFields": ["ratingCount"],
    "previousRatingCount": 244040000,
    "ratingCountDelta": 27678,
    "previousRealInstalls": 12322703000,
    "realInstallsDelta": 0,
    "hoursSincePreviousDetails": 24
}
```

#### How exact are the numbers?

- **`ratingCount` is the full number** from the page data. The store page shows it rounded ("244M reviews"); on 2026-09-21 the rounded label matched `ratingCount` for all 5 apps compared in a browser. The page calls it "reviews", but it is the number of star ratings; `ratingHistogram` gives a count per star. Its sum is close to, not always equal to, `ratingCount` (5,838,190 against 5,838,207 for Minecraft in the US), but the split between stars depends on the country (one-star: 617,987 in the US, 891,306 in Japan for Minecraft), so read it as the star distribution Google shows in that store, not as that country's own ratings.
- **`rating`** is the decimal average from the page data (4.616693); **`ratingShown`** is what the page shows (4.6).
- **Installs**: the page only shows a tier (`installsShown` "10B+", `installsText` "10,000,000,000+", `minInstalls` 10000000000). **`realInstalls`** is a finer number that Google Play puts in the page data but does not show. Google does not document it. Of 12 apps read on 2026-09-21, 10 had a number down to the unit, WhatsApp's ended in 000 and Minecraft's in 00. Treat it as Google's own figure, not an audited count.
- **Counts differ between requests** for large apps: reading the same page 4 times within a few minutes gave WhatsApp's rating count as 244,072,282 and 244,067,519 alternately, and Google Maps' rating as 3.219542 and 3.2195508. Installs, update date, version, price and the shown rating did not move. Four smaller apps (6,733 to 250,710 ratings) did not move at all.
- **`updatedAt`** is the exact time from the page data (UTC). **`updatedText`** is Google's own date text in the page language; its day can differ from the UTC date of `updatedAt` (Minecraft: "Sep 16, 2026" shown, 2026-09-15T18:13:38Z).
- **`version` is null with `versionVariesWithDevice: true`** when Google Play gives no single version (large apps such as WhatsApp and Spotify); `minAndroidVersion` is null for those too.
- **`installableInCountry`** is false when the store shows no Install or Buy button in that country (measured: d POINT CLUB, Yahoo! JAPAN and Rakuten Pay in the US). Such apps often have no rating in that country; `rating` and `ratingCount` are then null, never 0.
- `containsAds` and `inAppPurchases` are false when the page has no such label (checked on AntennaPod).
- `price` is in `currency` (6.99 USD for Minecraft in the US, 1300 JPY in Japan).
- Personal contact details that appear inside `descriptionStart` (emails, phone numbers) are replaced with `[email removed]` / `[phone removed]`.

#### Rows that are not charged

Every row has `status`. Only `ok` rows are charged. The others say why in `note`:

| status | Meaning |
|---|---|
| `ok` | The app page was read. Charged. |
| `no-such-app` | Google Play answered 404: no public page for this package name (never existed, removed, or spelled differently; names are case-sensitive). |
| `country-not-accepted` | Google Play answered 400 for that country code. |
| `different-app-returned` | The page belonged to another package than the one asked for; not sold as this app. |
| `blocked` | A bot check, rate limit or empty answer, even after retries. The app is not reported as missing. |
| `unreadable` | The page loaded but its data block was missing or had an unknown shape. |
| `invalid-input` | Not a package name or Google Play app URL, or an input setting is wrong. |
| `duplicate` | Same app, country and language as an earlier entry in the list. |
| `no-change` | Monitoring mode: nothing changed since the rows last returned. Only the check fee applies. |
| `not-checked` | Entries after `maxApps` rows, or beyond the first 1,000. |
| `budget-reached` | The run's maximum total charge left no room for another row; says how many entries were not read. |

In monitoring mode, an app that disappears (404) keeps its remembered values and its free row carries `lastSeenRatingCount`, `lastSeenRealInstalls`, `lastSeenVersion` and `lastSeenAt`.

### Monitoring mode

Schedule the Actor (for example daily) with `monitoringMode: true`:

1. First run: every app comes back with `change: "first-check"`.
2. Later runs: an app comes back only with `change: "details-changed"`, listing `changedFields` and the previous values (`previousRatingShown`, `previousRatingCount` and `ratingCountDelta`, `previousMinInstalls`, `previousRealInstalls` and `realInstallsDelta`, `previousUpdatedAt`, `previousVersion`, `previousPrice`, `previousCurrency`, `previousCheckedAt`, `previousDetailsReadAt`, `hoursSincePreviousDetails`).
3. Because large apps' rating counts move back and forth by up to 0.002% between reads, a rating count counts as changed only when it moved by more than 0.01% since the row last returned. An app that is not returned keeps the values of that row, so small moves add up and are reported once they pass 0.01%.
4. When the only change is in the rating count or the shown rating (the values that move between reads), the page is read again a few seconds later and the app is returned only if the change is still there (`confirmedByRereading` is true; false means the second read failed and the first read was returned). Changes in installs, update date, version or price did not move between reads and are returned without a second read (`confirmedByRereading` is null). A change that disappears on the second read is not reported and not charged.

Details are remembered per app, country, language and `changeOn`, across all your runs; a schedule with a different `changeOn` keeps its own memory and starts with a first check. The rating count and installs are the same in every country, so watching one app in five countries returns and charges the same change five times: watch the rating count and installs in one country. Do not put the same app in two schedules that can run at the same time: the store has no atomic update, so one run can overwrite what the other remembered.

### Pricing

Pay per event:

- **$2.00 per 1,000 app rows**, charged only for rows with `status: "ok"`.
- In monitoring mode, also **$0.30 per 1,000 app checks**: every app whose page was read is a check, changed or not. 100 apps checked every day is 3,000 checks a month, which is **$0.90** on top of the rows for the apps that changed.
- Free rows (missing apps, blocked pages, bad input, duplicates, notes) are never charged.

The Actor never reads more apps than your run's maximum total charge can pay for. In monitoring mode it reserves a check and a change row for each app before reading it.

### Tips

- Paste the URL from your browser: `https://play.google.com/store/apps/details?id=com.duolingo&hl=de&gl=DE` reads the German store in German.
- The same app in several countries: list its URL once per country (`&gl=us`, `&gl=jp` ...) or run once per `country`.
- For release tracking only, use `changeOn: "release"`; for price drops, `changeOn: "price"`.

# Actor input Schema

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

Apps to read: a package name such as com.spotify.music, or an app URL such as https://play.google.com/store/apps/details?id=com.spotify.music. Package names are case-sensitive. If a URL carries its own gl (country) or hl (language), those win over the fields below for that app. A repeated app (same country and language) is read and charged once and the repeat gets a free 'duplicate' row. If you leave this field out with monitoring off, two example apps are read; with monitoring on it is required. An empty list is rejected.

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

Two-letter country code of the Google Play store to read (gl), such as us, gb, de, jp or in. The average rating, the split of the star histogram, price, currency and whether the app can be installed differ by country; the rating count and installs are the same in every store. Google Play answers HTTP 400 for some unknown codes (such apps come back as a free 'country-not-accepted' row) but silently shows the US store for others, so check the currency column if you are unsure.

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

Language code of the page (hl), such as en, en-GB, de or ja. It changes the title, description, category name and the shown labels (rating, installs, price and date text), not the numbers. Google Play falls back to English for a language it does not know; every row says the language the page was actually in (pageLanguage).

## `maxApps` (type: `integer`):

With monitoring off, reading stops once this many charged rows have been returned, and a free row says how many entries at the end of the list were not read. Apps that do not exist and unreadable pages do not count. In monitoring mode it does not limit the changes returned: every app in the list is checked, as far as the run's maximum total charge allows. Up to 1,000 entries are read per run.

## `monitoringMode` (type: `boolean`):

Off = every app comes back with its current details, charged per row. On = the Actor remembers each app's details and, on later runs, returns an app only when its shown rating, rating count (by more than 0.01%), installs, last update date, version or price changed since the row last returned for it (narrow it with changeOn), with the previous values and the difference. The first run returns every app once to set the baseline. **In monitoring mode every app whose page is read costs $0.30 per 1,000 checks, changed or not** (apps that do not exist, unreadable pages and bot checks are free), plus the row price for the rows returned. Example: 100 apps every day = 3,000 checks a month = $0.90. The Actor reads only as many apps as the run's maximum total charge can pay for with a check and a change row each. Details are remembered per app, country, language and changeOn; do not put the same app in two schedules that can run at the same time. The rating count and installs are the same in every country, so watch them for an app in one country only.

## `changeOn` (type: `string`):

any = the shown rating, the rating count (by more than 0.01%), the installs (the shown tier or the finer number), the last update date, the version or the price changed. rating = only the shown rating or the rating count. installs = only the installs. release = only the last update date or the version. price = only the price or currency. A rating count moves back and forth by up to 0.002% between two reads a minute apart on large apps, so smaller moves are not reported as a change; an app that is not returned keeps the values of the row last returned for it, so small moves add up until they pass 0.01%. A change of only the rating count or shown rating is read again a few seconds later and returned only if it is still there. Each changeOn setting keeps its own memory, so a schedule with a new setting starts with a first check.

## `resetMonitoringState` (type: `boolean`):

Clears every remembered app for this Actor, so the next monitoring run returns each app once again as a first check. This affects all your monitoring runs, because details are stored per app rather than per list. Turn it off again after one run: left on in a schedule, every run returns every app as a first check and charges the row price for it.

## `useProxy` (type: `boolean`):

Every app page is requested directly first. Only if Google Play answers a page with a bot check, a rate limit, an empty response or no response is that page retried through a datacenter proxy session; the next app is requested directly again. With it off, that answer is retried directly and then returned as a free 'blocked' row, and no proxy is used.

## Actor input object example

```json
{
  "apps": [
    "com.spotify.music",
    "https://play.google.com/store/apps/details?id=com.duolingo"
  ],
  "country": "us",
  "language": "en",
  "maxApps": 100,
  "monitoringMode": false,
  "changeOn": "any",
  "resetMonitoringState": false,
  "useProxy": true
}
```

# Actor output Schema

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

One row per Google Play app whose page was read: package name, title, developer, category, rating (decimal and as shown), rating count, star histogram, installs (shown tier and the finer number in the page data), price and currency, ads, in-app purchases, whether it can be installed in the country, last update, release date, version, minimum Android version, content rating, summary, the start of the description and the icon URL; in monitoring mode also which fields changed and their previous values. Apps that do not exist, rejected countries, bot checks and invalid input come back as free rows that say why.

# 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",
        "https://play.google.com/store/apps/details?id=com.duolingo"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/google-play-app-details-tracker").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",
        "https://play.google.com/store/apps/details?id=com.duolingo",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/google-play-app-details-tracker").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",
    "https://play.google.com/store/apps/details?id=com.duolingo"
  ]
}' |
apify call neverempty/google-play-app-details-tracker --silent --output-dataset

```

## MCP server setup

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

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/gwZiXzUMN90sLCwgX/builds/AbbaXf88apx8BmpbG/openapi.json
