# Google Play Store Reviews Scraper: Android App Reviews (`hyperbach/google-play-reviews-scraper`) Actor

Scrape Google Play reviews by app URL or package name, language and country: rating, text, reviewer nickname, date, app version, thumbs-up, developer reply and review id, newest first, most relevant or highest rating. Several apps per run, a cap per app, a free app row with a completeness verdict.

- **URL**: https://apify.com/hyperbach/google-play-reviews-scraper.md
- **Developed by:** [Hyperbach](https://apify.com/hyperbach) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.05 / 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?

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 Reviews Scraper: Android App Reviews

**Google Play reviews by app, language and country, logged out: Play URLs or package names in, one row per review out — the star rating, the text, the reviewer's nickname and avatar, the date, the app version the reviewer used, the thumbs-up count, the developer's reply with its date, the criteria ratings and the review id — newest first, most relevant or highest rating first, several apps in one run, a cap per app.** One free `app` row per app carries a completeness verdict: COMPLETE (the list's own end), CAPPED (your limit) or PARTIAL. An unknown package, an app with no review in the language: free typed rows. No login, no Google account, no API key, proxies handled for you.

### Why this Google Play reviews scraper

- **Several apps in one run.** Give Play URLs or package names: each app gets its own cap (`maxReviewsPerApp`), and the run's cap sits on top (`maxItems`). A URL's own `hl` and `gl` are used when `language` and `country` are empty.
- **Three orders.** `newest`, `mostRelevant` (Google Play's own order) and `rating` (5 stars first, then 4, 3, 2, 1). The first pages of newest and most relevant shared no review on our test. Each row carries its `rank` in the order read.
- **Every field Google Play shows.** The star rating and the text, the reviewer's nickname and avatar, the date (UTC), the app version the review was written on, the thumbs-up count, the developer's reply with who wrote it and when, the criteria ratings some apps ask for (`criteria`), and Google Play's review id with a link to the review; every row carries the app, the language, the country and the input it answers.
- **Deep walks.** The reviews come 200 a call in the order asked, page after page, to the end of Google Play's list: our test read 12,000 of WhatsApp's newest English reviews with no end in sight (2026-10-22). The free `app` row says how many reviews were delivered from how many pages and why the walk stopped: the list's own end (COMPLETE), your cap (CAPPED), or a refusal or the time limit (PARTIAL).
- **Language is the set.** Google Play lists the reviews written in the language asked (`language`, the store's `hl`): French reviews are another set from English ones. The `country` (the store's `gl`) is on every row; on our test it did not change the reviews listed.
- **Honest runs.** A package Google Play does not know is a free `not_found` row (its app page answered 404 on two exits), an app with no review in the language a free `empty` row, a value that is no Play app a free `invalid` row, a repeated app a free `duplicate` row. Every input gets a row; a review is charged once a run.
- **Fast and light.** Plain HTTP on the call Google Play's own web page makes: 200 reviews a call, about 30 KB on the wire for the newest 200. A run of 3 apps × 260 reviews took 4 seconds of reading on our test.

### Who it's for

- **App developers and product teams** — the newest reviews of your Android app in every language you ship, with the app version per review to see what a release changed, and your team's replies beside them.
- **ASO and market researchers** — the reviews of a list of competitor apps in one run, newest, most relevant or highest rated first, with the criteria ratings some categories carry.
- **Data and analytics teams** — typed rows keyed by `app_id`, `language` and `review_id`, a free row per app with the status and verdict, a run summary to check a pipeline against.

### Quick start

**The newest reviews of two apps (a Play URL and a package name)**

```json
{
  "appIds": [
    "https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US",
    "com.instagram.android"
  ],
  "maxReviewsPerApp": 50
}
```

**One app's reviews in German, up to 1,000**

```json
{
  "appIds": [
    "com.spotify.music"
  ],
  "language": "de",
  "country": "DE",
  "maxReviewsPerApp": 1000
}
```

**The most relevant reviews of an app**

```json
{
  "appIds": [
    "com.duolingo"
  ],
  "sort": "mostRelevant",
  "maxReviewsPerApp": 200
}
```

### Output

One row per review (and one free app row per app):

| field | meaning |
|---|---|
| `row_type` | `review` (one review, charged) or `app` (the app's row with the walk's verdict; free). |
| `input_index` | The 1-based position in `appIds` of the input this row answers. |
| `input_value` | The input as you gave it (the URL or package name). |
| `app_id` | The app's package name (`com.whatsapp`). |
| `language` | The language read (Google Play's `hl`: `en`, `pt-BR` …). |
| `country` | The store country sent (Google Play's `gl`: `US`, `GB` …). |
| `sort` | The order read: `newest`, `mostRelevant` or `rating`. |
| `status` | An app row: `ok`, `empty`, `not_found`, `invalid`, `duplicate`, `failed` or `not_read` (see the FAQ). A review row: `ok`. |
| `status_detail` | Why, in words, when the status is not `ok`. |
| `scraped_at` | When the row was read (ISO 8601, UTC). |
| `review_id` | Google Play's review id. |
| `rank` | The review's position in the order read (from 1). |
| `rating` | The star rating, 1 to 5. |
| `text` | The review's text. |
| `reviewer_name` | The reviewer's nickname, as Google Play shows it. |
| `reviewer_image` | The reviewer's public avatar image. |
| `published_at` | When the review was written or last edited (ISO 8601, UTC). |
| `app_version` | The app version the review was written on (null when Google Play does not show one). |
| `thumbs_up` | How many users marked the review helpful (thumbs up). |
| `reply_by` | Who wrote the developer's reply (the developer's name), null when there is no reply. |
| `reply_text` | The developer's reply to the review, null when there is none. |
| `replied_at` | When the developer replied (ISO 8601, UTC). |
| `criteria` | The criteria ratings Google Play asks for in some categories, as `{criterion, rating}` pairs (`vaf_app_quality_visual_appeal` …); null when the review has none. |
| `url` | A link to the review on Google Play. |
| `app_url` | The app's Google Play link in the language and country read (app row). |
| `reviews_delivered` | Review rows delivered for this app (app row). |
| `pages_read` | Pages of Google Play's review list read for this app (app row). |
| `verdict` | COMPLETE (the list's own end), CAPPED (your limit) or PARTIAL (a refusal or the time limit) (app row). |
| `verdict_detail` | The verdict in words, with the numbers (app row). |

Example record:

```json
{
  "row_type": "review",
  "input_index": 2,
  "input_value": "com.spotify.music",
  "app_id": "com.spotify.music",
  "language": "en",
  "country": "US",
  "sort": "newest",
  "status": "ok",
  "status_detail": null,
  "scraped_at": "2026-10-22T20:03:24Z",
  "review_id": "7e211cf7-901d-448b-a5f5-41e0649d4b2d",
  "rank": 3,
  "rating": 1,
  "text": "too much annoying ads after every song, atleast let us play 4-5 songs then give ads it will be still better 😡",
  "reviewer_name": "nickname-redacted",
  "reviewer_image": "https://play-lh.googleusercontent.com/a/avatar-redacted",
  "published_at": "2026-10-21T20:01:44Z",
  "app_version": "9.1.92.3058",
  "thumbs_up": 0,
  "reply_by": "Spotify AB",
  "reply_text": "Hi. Thanks for bringing this up. We'll pass this on to our ads team. If you're seeing more ads than usual, can you try a quick reinstall? If that doesn't help, reach out to our Customer Service in support.spotify.com and search \"Contact us\". They'll help you report this.",
  "replied_at": "2026-10-21T20:29:44Z",
  "criteria": [
    {
      "criterion": "vaf_app_quality_ads_frequency",
      "rating": 1
    }
  ],
  "url": "https://play.google.com/store/apps/details?id=com.spotify.music&reviewId=7e211cf7-901d-448b-a5f5-41e0649d4b2d",
  "app_url": null,
  "reviews_delivered": null,
  "pages_read": null,
  "verdict": null,
  "verdict_detail": null
}
```

### Pricing

**Pay for the reviews a run delivers**, plus one run start; no platform usage is billed to you. The review price falls with your Apify plan.

| event | unit | Free | Starter | Scale | Business |
|---|---|---|---|---|---|
| Review (`review`) | per 1,000 | $0.10 | $0.085 | $0.07 | $0.05 |
| Run start (`start`) | per run | $0.001 | $0.001 | $0.001 | $0.001 |

A review is **$0.10 per 1,000** on the Free plan ($0.0001 a review). Each run that delivers a charged review also pays the **run start, $0.001 once**; a run that delivers no charged review pays nothing at all. So the 500 newest reviews of one app cost $0.0510 on the Free plan and $0.0435 on Starter, and 1,000 reviews $0.1010 / $0.0860; several apps in one run share one start. The free `app` row of every app, an unknown package (`not_found`), an app with no review in the language (`empty`), a value that is no Google Play app (`invalid`), a repeated app (`duplicate`), an app left at the cap or the time limit (`not_read`) and a refusal the run could not pass (`failed`) are never charged. A review is charged once a run. A run that stops early (a timeout, an abort) is charged for the reviews it delivered. `maxItems` counts the charged rows; before the first request the log names the price of each row at your plan and the most the run can cost. Enterprise plans have their own rates: the Actor's Pricing tab shows the price for your plan.

### Usage patterns

- **Apps, language and country** — `appIds` takes Google Play URLs (`https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US`; `market://details?id=` links too) or package names (`com.whatsapp`). `language` takes Google Play language codes (`en`, `de`, `pt-BR`, `es-419` …) and `country` two-letter codes (`US`, `GB`, `DE` …). Each app is read in the `language` and `country` given; when they are empty, in its URL's own `hl` and `gl`, else in `en` and `US`.
- **What a row is** — `row_type` = `review`: one review with Google Play's record; `rank` is its position in the order read; `sort` the order. `row_type` = `app`: the free row of one app in one language and country — `reviews_delivered`, `pages_read`, the `verdict` with its `verdict_detail`, and the `status` with its reason.
- **How far an app goes** — `maxReviewsPerApp` = 0 (the default) reads every review Google Play lists for the app, language and order — a big app lists millions, so set a cap, `maxItems` or a run timeout. `maxReviewsPerApp` = 100 is the first 100 in the order asked. `maxItems` caps the charged rows of the whole run. The app row's `verdict` says where the walk stopped and why.

### Input configuration

| field | type | default | what it does |
|---|---|---|---|
| `appIds` | `array` |  | The apps to read: Google Play URLs (`https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US`) or package names (`com.whatsapp`). A URL's own `hl` and `gl` are used when `language` and `country` are empty. Up to 1,000 apps a run; a value that is no Google Play app gets a free `invalid` row, an app given twice for the same language and country a free `duplicate` row. |
| `language` | `string` |  | The reviews' language as Google Play codes it (`en`, `de`, `fr`, `pt-BR`, `es-419` …). Google Play lists the reviews written in this language, so each language is a different set of reviews. Empty: the app URL's `hl`, else `en`. |
| `country` | `string` |  | The two-letter Google Play country (`US`, `GB`, `DE`, `IN` …; `UK` is read as `GB`), sent as the store's `gl` and written on every row. Empty: the app URL's `gl`, else `US`. On our test the country did not change which reviews Google Play lists; the language does. |
| `sort` | `newest` / `mostRelevant` / `rating` | `"newest"` | `newest`: the newest reviews first. `mostRelevant`: Google Play's own most-relevant order. `rating`: the highest star rating first. |
| `maxReviewsPerApp` | `integer` | `0` | Stop each app after this many review rows (0 = every review Google Play lists for the app, language and order; a big app lists millions, so set a cap or `maxItems`). |
| `maxItems` | `integer` | `0` | Stop the run after this many review rows across every app (0 = no limit). App rows are free and not counted; an app left unread at the cap gets a free `not_read` row. |

### Errors

**A run does not fail because of your input.** When an input cannot be used — a date that is not a date, an id in the wrong form, two settings that contradict each other — the run ends **Succeeded**, charges nothing, and says what to change:

- the status message starts with `INPUT REJECTED`;
- the dataset holds one row, `{"error": true, "code": "…", "message": "…"}`, and no results;
- the key-value store record `ERROR` holds the same object.

From code, check `error` on the first row before reading results.

An app with no review in the language asked is not an error: it gets one free `app` row with `status` empty, and the run goes on with the others. A run that ends **Failed** is a fault on our side, never your input; it charges nothing, and we are alerted.

### FAQ

**Which status is which?**

An app row's `status` is `ok` (reviews served), `empty` (the app exists and has no review in that language), `not_found` (Google Play does not know the package; its app page answered 404 on two exits), `invalid` (a value that is no Google Play app), `duplicate` (the same app, language and country twice: read once), `failed` (a refusal or an error; free) or `not_read` (left at `maxItems`, the charge limit or the run's time limit). A review row's `status` is `ok`.

**Is there a review title?**

No. A Google Play review has a star rating and a text, no title; the row carries what Google Play shows.

**Does it read ratings without a text?**

No. Google Play lists the reviews with a written text; a star rating given without one is not in the list, so the review count of an app on its page (the ratings) is larger than what any review scraper returns.

**Is the reviewer's nickname on the row?**

The nickname and the avatar are on every review row as Google Play shows them publicly. They are personal data: process them under your own lawful basis.

**Does it need a Google account or API key?**

No. Everything is read from Google Play's public web pages, logged out.

### Integration

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('hyperbach/google-play-reviews-scraper').call({"appIds": ["https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US", "com.instagram.android"], "maxReviewsPerApp": 50});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('hyperbach/google-play-reviews-scraper').call(run_input={'appIds': ['https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US', 'com.instagram.android'], 'maxReviewsPerApp': 50})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

#### CLI

```bash
apify call hyperbach/google-play-reviews-scraper --input '{"appIds": ["https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US", "com.instagram.android"], "maxReviewsPerApp": 50}'
```

#### REST

```bash
curl -X POST "https://api.apify.com/v2/acts/hyperbach~google-play-reviews-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' -d '{"appIds": ["https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US", "com.instagram.android"], "maxReviewsPerApp": 50}'
```

### Support

apify@hyperbach.com

*This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.*

# Actor input Schema

## `appIds` (type: `array`):

The apps to read: Google Play URLs (`https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US`) or package names (`com.whatsapp`). A URL's own `hl` and `gl` are used when `language` and `country` are empty. Up to 1,000 apps a run; a value that is no Google Play app gets a free `invalid` row, an app given twice for the same language and country a free `duplicate` row.

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

The reviews' language as Google Play codes it (`en`, `de`, `fr`, `pt-BR`, `es-419` …). Google Play lists the reviews written in this language, so each language is a different set of reviews. Empty: the app URL's `hl`, else `en`.

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

The two-letter Google Play country (`US`, `GB`, `DE`, `IN` …; `UK` is read as `GB`), sent as the store's `gl` and written on every row. Empty: the app URL's `gl`, else `US`. On our test the country did not change which reviews Google Play lists; the language does.

## `sort` (type: `string`):

`newest`: the newest reviews first. `mostRelevant`: Google Play's own most-relevant order. `rating`: the highest star rating first.

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

Stop each app after this many review rows (0 = every review Google Play lists for the app, language and order; a big app lists millions, so set a cap or `maxItems`).

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

Stop the run after this many review rows across every app (0 = no limit). App rows are free and not counted; an app left unread at the cap gets a free `not_read` row.

## Actor input object example

```json
{
  "appIds": [
    "https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US",
    "com.instagram.android"
  ],
  "sort": "newest",
  "maxReviewsPerApp": 50,
  "maxItems": 100
}
```

# Actor output Schema

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

All scraped records in the default dataset. One row per review (and one free app row per app):

## `runSummary` (type: `string`):

How the run went: review rows charged, free rows, apps by status, the walk per app (reviews delivered, pages read, repeated ids skipped, the verdict), the source counters (calls, served, refused, exits, rotations, bytes) and the memory watch.

# 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 = {
    "appIds": [
        "https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US",
        "com.instagram.android"
    ],
    "maxReviewsPerApp": 50,
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("hyperbach/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 = {
    "appIds": [
        "https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US",
        "com.instagram.android",
    ],
    "maxReviewsPerApp": 50,
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("hyperbach/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 '{
  "appIds": [
    "https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US",
    "com.instagram.android"
  ],
  "maxReviewsPerApp": 50,
  "maxItems": 100
}' |
apify call hyperbach/google-play-reviews-scraper --silent --output-dataset

```

## MCP server setup

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