# Google Play Reviews – App Ratings, Replies & Countries (`abotapi/google-play-reviews-scraper`) Actor

Collect Google Play reviews and ratings for any app across country storefronts. Search by app name or use a Google Play URL. Returns one row per review with rating, text, author, date, app version, thumbs-up count, developer reply, and storefront country.

- **URL**: https://apify.com/abotapi/google-play-reviews-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Developer tools, Social media, Other
- **Stats:** 4 total users, 0 monthly users, 93.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 reviews

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?

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

## Google Play Reviews Scraper

Collect Google Play Store reviews and ratings for any app, across country storefronts. Give it an app name or a Google Play link and it returns a clean, one-row-per-review dataset: the star rating, review body, author, date, the app version reviewed, thumbs-up count, any developer reply, and the storefront country. Reviews are storefront-specific, so you can sweep many countries to gather the full picture for a single app.

### Why this scraper

- Reviews are the primary output: rating, body, author, date, app version, thumbs-up, developer reply, country, and language on every row.
- Two ways in: search by app name, or paste one or more Google Play app links (a bare package id like `com.spotify.music` works too).
- Multi-country sweep: pass a list of countries, or use "all" for a broad built-in set of major markets.
- Sort by newest, most helpful, or by rating.
- Filter by minimum and maximum star rating.
- Choose the review language / locale.
- Optional app-metadata enrichment: developer, average score, ratings count, installs, category, icon, price, and description attached to each review.
- 20+ output fields, more than typical alternatives, at a predictable per-result price.

### Data you get

> Sample shape, values are illustrative placeholders, not from a live review.

| Field | Example |
|---|---|
| reviewId | 00000000-0000-0000-0000-000000000000 |
| appId | com.example.app |
| appName | Sample App |
| country | us |
| language | en |
| rating | 5 |
| body | Full review text appears here. |
| author | Reviewer Name |
| authorId | 100000000000000000000 |
| authorImage | https://play-lh.googleusercontent.com/… |
| thumbsUp | 12 |
| appVersion | 1.0.0 |
| reviewDate | 2026-01-01T00:00:00+00:00 |
| reviewTimestamp | 1780000000 |
| reviewUrl | https://play.google.com/store/apps/details?id=com.example.app\&reviewId=… |
| developerReply | Thanks for the feedback! |
| developerReplyDate | 2026-01-02T00:00:00+00:00 |

With enrichment enabled, each review also carries: appDeveloper, appDeveloperEmail, appDeveloperWebsite, appScore, appRatingsCount, appInstalls, appCategory, appContentRating, appPrice, appCurrency, appIconUrl, appUpdated, appDescription, appStoreUrl.

### How to use

Search by app name:

```json
{
  "mode": "search",
  "queries": ["Instagram", "Spotify"],
  "appsPerQuery": 1,
  "countries": ["us"],
  "sortBy": "newest",
  "maxItems": 100
}
```

Sweep several countries for one app via its link:

```json
{
  "mode": "url",
  "urls": ["https://play.google.com/store/apps/details?id=com.instagram.android"],
  "countries": ["us", "gb", "de", "jp"],
  "maxItems": 500
}
```

Only high-rated reviews, with app details attached:

```json
{
  "mode": "url",
  "urls": ["https://play.google.com/store/apps/details?id=com.instagram.android"],
  "countries": ["us"],
  "minRating": 4,
  "fetchDetails": true,
  "maxItems": 200
}
```

Multiple apps at once (bare package ids work too):

```json
{
  "mode": "url",
  "urls": [
    "com.instagram.android",
    "com.spotify.music"
  ],
  "countries": ["us"],
  "maxItems": 200
}
```

### Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| mode | string | search | "search" (by app name) or "url" (paste app links). |
| queries | array | \["Instagram"] | App names or keywords (search mode). |
| appsPerQuery | integer | 1 | Top matching apps to take per search term. |
| urls | array | (example) | Google Play app links or package ids (url mode). |
| countries | array | \["us"] | Country codes to collect from. Empty uses the link's country or us. "all" sweeps major markets. |
| language | string | en | Review language / locale code. |
| sortBy | string | newest | "newest", "mostHelpful", or "rating". |
| minRating | integer | (none) | Keep reviews at or above this star rating. |
| maxRating | integer | (none) | Keep reviews at or below this star rating. |
| fetchDetails | boolean | false | Attach app metadata to each review. |
| maxItems | integer | 20 | Total review cap. 0 means no limit. |
| maxPages | integer | (none) | Optional safety bound on review pages read per country (about 40 reviews per page). Leave empty to walk every page Google Play serves — the run still stops at Max reviews. |
| resumeFromRunId | string | (none) | Run id (or dataset id) of a previous run of this Actor. Reviews it already collected are skipped, so this run only fetches new ones. |
| incrementalMode | boolean | false | Recurring monitoring: remembers what a previous run of the SAME search saw and marks each review NEW / UPDATED / UNCHANGED / REAPPEARED / EXPIRED. |
| stateKey | string | (none, auto) | Name this monitoring campaign explicitly. Leave empty to derive one automatically from mode, queries/urls, countries, language, sortBy, minRating/maxRating, and fetchDetails (maxItems/maxPages and delivery options like mcpConnectors do NOT affect it). |
| emitUnchanged | boolean | false | Also return (and bill for) reviews unchanged since the last run. |
| emitExpired | boolean | false | Also return (and bill for) a tombstone row for reviews the previous run saw that this run no longer finds. Only reported after a run that fully re-walked every tracked app/country stream — never after a capped or resumed run. |
| proxy | object | Apify proxy | Connection settings. |

### Incremental mode (recurring monitoring)

Turn on `incrementalMode` and schedule the Actor to run on the same app/country/filter set (or set `stateKey` yourself for an explicit campaign name). Each run compares against what the previous run of the SAME campaign saw and adds `changeType` (`NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED`, or `EXPIRED`), `changedFields`, `firstSeenAt`, and `lastSeenAt` to every row. By default only NEW/UPDATED/REAPPEARED rows are returned — turn on `emitUnchanged`/`emitExpired` to also see (and pay for) the rest.

What counts as a change: rating, review text, author, `thumbsUp`, app version, and developer replies are all compared — every field a review actually carries is real data a monitoring run exists to report. One thing is deliberately excluded from change detection (though still shown on every row):

- **App-metadata fields** attached when `fetchDetails` is on (`appScore`, `appRatingsCount`, `appInstalls`, `appDeveloper`, ...) — these describe the APP, not the individual review, and change on their own as other users interact with the app. Excluding them stops one unrelated review's helpful-vote or the app's aggregate rating from flipping every other review to `UPDATED` in lockstep.

`thumbsUp` was suspected of ticking up independently of the review (like a vote counter) and was excluded in an earlier draft on that guess. It was measured directly: two live fetches of the same popular app's top-40 most-helpful reviews (Gmail, `com.google.android.gm`), 1810s (30.2 minutes) apart, diffed field-by-field. Result: **zero drift** — every one of the 40 `thumbsUp` values (ranging 2–3318, including reviews as recent as days old) was byte-identical across both passes, as was every other field. The guess wasn't supported by data, so `thumbsUp` is now treated as ordinary review data and included in change detection. This was tested at a 30-minute window, not the actor's real incremental cadence (typically daily/weekly) — if a future run surfaces `thumbsUp`-only `UPDATED` noise at that longer cadence, revisit `_VOLATILE_FIELDS` in `src/incremental.py`.

Combining `resumeFromRunId` with `incrementalMode` is only for bootstrapping a brand-new campaign from an earlier non-incremental run's dataset; once a campaign has its own tracked state, combine them again and the run fails fast (remove `resumeFromRunId` or use a different `stateKey`). `emitExpired` reviews are only ever reported after a complete, uncapped, non-resumed scan — a partial run can't tell "deleted" apart from "not reached yet".

### Output example

> Sample shape, values are illustrative placeholders.

```json
{
  "reviewId": "00000000-0000-0000-0000-000000000000",
  "appId": "com.example.app",
  "appName": "Sample App",
  "country": "us",
  "language": "en",
  "rating": 5,
  "body": "Full review text appears here.",
  "author": "Reviewer Name",
  "authorId": "100000000000000000000",
  "authorImage": "https://play-lh.googleusercontent.com/%E2%80%A6",
  "thumbsUp": 12,
  "appVersion": "1.0.0",
  "reviewDate": "2026-01-01T00:00:00+00:00",
  "reviewTimestamp": 1780000000,
  "reviewUrl": "https://play.google.com/store/apps/details?id=com.example.app&reviewId=%E2%80%A6",
  "developerReply": null,
  "developerReplyDate": null
}
```

### Send results into your apps (MCP connectors)

You can optionally pipe results into the apps you already use through Model Context Protocol (MCP) connectors. Authorize a connector under Apify, Settings, API & Integrations, then select it in the input. For Notion, set a parent page URL and each review is written as a page. Other connectors receive a best-effort write.

The connector receives a condensed, human-readable summary of each review (a heading plus the key fields and body text), not the full JSON. The complete record always stays in the Apify dataset. Leave the connector field empty to skip this step; it never changes the dataset output.

### Plan requirement

Runs on any Apify plan. For very large multi-country sweeps, a proxy with more exit rotation can help.

# Actor input Schema

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

How to select apps. 'search' resolves app names to apps via Google Play; 'url' takes Google Play app links directly.

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

App names or keywords to look up (for example 'Instagram', 'Spotify'). Each term resolves to the top matching app(s) and its reviews are collected. Ignored in URL mode.

## `appsPerQuery` (type: `integer`):

How many top matching apps to take for each search term. Use 1 for the single best match. Ignored in URL mode.

## `urls` (type: `array`):

Google Play app links, for example https://play.google.com/store/apps/details?id=com.instagram.android . A bare package id (com.instagram.android) also works. Multiple URLs supported. The country in the link (gl=) is used only if you leave Countries empty.

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

ISO 2-letter country codes to collect reviews from (for example us, gb, de, jp). Each country is a separate review stream. Leave empty to use the country from each URL (URL mode) or 'us' (search mode). Use 'all' to sweep a broad built-in set of major countries.

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

Review language / locale (ISO code, for example en, es, de, ja). Controls the language of returned reviews and metadata.

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

Order reviews are returned in per country.

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

Keep only reviews with at least this star rating (1 to 5). Leave empty for all.

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

Keep only reviews with at most this star rating (1 to 5). Leave empty for all.

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

Also attach app metadata (developer, average score, ratings count, installs, category, icon, price, description) to each review. Adds an extra lookup per app.

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

Maximum number of reviews to collect in total. 0 means no limit (stops at Max pages per country across all countries). Predictable, keeps the first run cheap.

## `maxPages` (type: `integer`):

Optional per-country safety bound on review pages read (about 40 reviews per page). Leave empty to walk every result page Google Play serves. This does NOT cap the number of reviews — the run stops at Max reviews.

## `resumeFromRunId` (type: `string`):

Run id (or dataset id) of an earlier run of this Actor. Reviews it already collected are skipped, so this run only fetches new ones. Leave empty for a normal run. Do not combine with Incremental mode unless this is the very first incremental run for a new State key — an established incremental baseline already tracks what was seen.

## `incrementalMode` (type: `boolean`):

For scheduling this Actor to run repeatedly against the SAME app/country/filter set. The Actor remembers what it saw last time (keyed by State key below, or an automatic hash of your search/filter settings) and marks each review NEW, UPDATED, UNCHANGED, REAPPEARED, or EXPIRED. Unlike Resume from a previous run, you never need to paste a run id. Off by default so normal runs are unaffected.

## `stateKey` (type: `string`):

Name this monitoring campaign so Incremental mode's memory is explicit and stable, instead of an automatic hash of your search/filter settings. Use different State keys for different campaigns you want tracked separately. Leave empty to let the Actor derive one from mode, queries/urls, countries, language, sortBy, minRating/maxRating, and fetchDetails.

## `emitUnchanged` (type: `boolean`):

Only used with Incremental mode. When off (default), a review identical to what the previous run saw is skipped — it is not returned and not billed. Turn this on to also return those unchanged reviews (changeType: "UNCHANGED") — this returns, and bills for, extra rows.

## `emitExpired` (type: `boolean`):

Only used with Incremental mode. When on, a review the previous run saw but this run no longer finds (deleted, or no longer reachable) is returned once as a tombstone row (changeType: "EXPIRED") — this returns, and bills for, an extra row per disappeared review. Only reported after a run that fully re-walked every tracked app/country stream (no Max reviews cap hit, no Resume from a previous run); a partial run never reports EXPIRED, since it cannot tell "deleted" apart from "not reached yet".

## `proxy` (type: `object`):

Connection settings. The default works on any Apify plan.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write. Leave empty to skip; never changes the dataset output. Supported: Notion, Linear, Airtable, Apify.

## `notionParentPageUrl` (type: `string`):

URL or id of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "Instagram"
  ],
  "appsPerQuery": 1,
  "urls": [
    "https://play.google.com/store/apps/details?id=com.instagram.android"
  ],
  "countries": [
    "us"
  ],
  "language": "en",
  "sortBy": "newest",
  "fetchDetails": false,
  "maxItems": 20,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true
  },
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

# 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 = {
    "mode": "search",
    "queries": [
        "Instagram"
    ],
    "urls": [
        "https://play.google.com/store/apps/details?id=com.instagram.android"
    ],
    "countries": [
        "us"
    ],
    "proxy": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/google-play-reviews-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 = {
    "mode": "search",
    "queries": ["Instagram"],
    "urls": ["https://play.google.com/store/apps/details?id=com.instagram.android"],
    "countries": ["us"],
    "proxy": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/google-play-reviews-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 '{
  "mode": "search",
  "queries": [
    "Instagram"
  ],
  "urls": [
    "https://play.google.com/store/apps/details?id=com.instagram.android"
  ],
  "countries": [
    "us"
  ],
  "proxy": {
    "useApifyProxy": true
  }
}' |
apify call abotapi/google-play-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/google-play-reviews-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/quCpbQUudPA7qkGCF/builds/0oOBwBaoZHN5tKVe6/openapi.json
