# Google Play Reviews Scraper: App Reviews, Ratings & Replies (`arman-bd/google-play-reviews-scraper`) Actor

Scrape Google Play app reviews by package name or store URL: reviewer, star rating, full text, date, app version, thumbs-up count, per-aspect ratings and the developer's reply. Sort three ways, filter by stars, language, country and date. No API key, no proxies, no browser.

- **URL**: https://apify.com/arman-bd/google-play-reviews-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Marketing, Business, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.09 / 1,000 review scrapeds

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

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

## Google Play Reviews Scraper: App Reviews, Ratings & Replies

![Google Play Reviews: Every review as a clean row, stars, text, app version, helpful votes and the developer's reply](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/google-play-reviews-scraper.jpg)

**Google Play Reviews Scraper** reads the user reviews of any Google Play app and returns one clean row per review: reviewer, star rating, full text, exact timestamp, app version, helpful-vote count, per-aspect ratings and the developer's reply with its own timestamp.

Point it at a package name or paste a store URL. Sort three ways, keep only chosen star ratings, read any language edition and storefront, and window by date. Every row carries `reviewId`, the review's stable identifier, so results join across runs and diff cleanly. **No API key, no billing account, no proxy setup and no browser.**

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/google-play-reviews-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/google-play-reviews-scraper.md
```

### What you get

| Output field | Meaning |
|---|---|
| `appId` | The app's package name, e.g. `com.spotify.music` |
| `reviewId` | The review's stable identifier. Use it as your join and diff key |
| `url` | A store link straight to this review |
| `userName` | Reviewer's display name |
| `userImage` | Reviewer's avatar URL |
| `rating` | Stars, 1–5, always a whole number |
| `text` | The full review text. `null` for rating-only reviews |
| `date` | When it was written, ISO 8601 with millisecond precision |
| `appVersion` | The app version the review was written against, where the store records it |
| `thumbsUp` | How many people marked the review helpful |
| `replyText`, `replyDate` | The developer's public reply and when it was posted, or `null` |
| `criteria` | Per-aspect ratings where the store collects them (ad frequency, battery use and similar), as name and rating pairs |
| `language`, `country` | The language edition and storefront the run read |
| `sort` | Which sort order produced the row |
| `position` | The row's rank within its app for this run |
| `scrapedAt` | Run timestamp |

A `RUN_SUMMARY` record in the key-value store holds per-app counts: pages read, reviews fetched, saved, duplicates collapsed, rows outside the date range, whether the app ran out of reviews or hit your cap, and any field that failed its shape check.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `appIds` | array | required | Package names (`com.spotify.music`), store URLs, or `market://` links, in any mix. Each app is read independently |
| `sort` | string | `newest` | `newest`, `mostRelevant` (the store's own default view), or `rating` (highest first) |
| `starRatings` | array | all | Keep only these ratings, e.g. `["1", "2"]` for complaints. Filtered at the source, so a 1-star pull does not page through 5-star reviews to find them |
| `maxReviewsPerApp` | integer | `200` | Cap per app, and the main cost control since charging is per review. Popular apps carry millions |
| `language` | string | `en` | The store's language code: `en`, `de`, `ja`, `pt-BR`. Selects the store's language grouping of reviews |
| `country` | string | `us` | Two-letter storefront code. The review pool shifts with it |
| `dateFrom` | string | | Oldest review to keep: `2026-01-15`, or relative `30 days`, `36 hours`. With `sort: "newest"` the run stops paging at the boundary |
| `dateTo` | string | | Newest review to keep, same formats. Ignored unless `dateFrom` is set |

```json
{
 "appIds": ["com.headway.books"],
 "sort": "newest",
 "starRatings": ["1", "2"],
 "maxReviewsPerApp": 500,
 "dateFrom": "30 days"
}
```

### Output

One record per review. Every field is present on every record; `null` means the store has nothing there, never that it was skipped.

```json
{
 "appId": "com.headway.books",
 "reviewId": "b2c0180e-16f5-4384-8ee9-c6e8d36eb5dd",
 "url": "https://play.google.com/store/apps/details?id=com.headway.books&reviewId=b2c0180e-16f5-4384-8ee9-c6e8d36eb5dd",
 "userName": "Rakesh",
 "userImage": "https://play-lh.googleusercontent.com/a-/ALV-UjUteCu2Qkv-bX4YLgMqx1pqn7OIjos_X_SUgZHUj3cDhA5CeXs7",
 "rating": 1,
 "text": "too many ads and asking premium for every thing",
 "date": "2026-08-06T02:40:06.701Z",
 "appVersion": null,
 "thumbsUp": 0,
 "replyText": "Hi! Thanks for being direct. Please, send a few details to support+appstore@get-headway.com from the email linked to your Headway account — it reaches our support team on priority. Specific feedback is what we can actually act on.",
 "replyDate": "2026-08-06T11:06:05.208Z",
 "criteria": [{ "name": "vaf_app_quality_ads_frequency", "rating": 1 }],
 "language": "en",
 "country": "us",
 "sort": "newest",
 "position": 12,
 "scrapedAt": "2026-08-09T14:02:11.000Z"
}
```

### Use cases

- **Complaint monitoring.** `starRatings: ["1", "2"]` with `dateFrom: "7 days"` on a schedule is a weekly digest of what is going wrong, with the app version attached to each report.
- **Release quality tracking.** Filter rows by `appVersion` to see whether the update that shipped on Tuesday is the reason ratings moved on Wednesday.
- **Competitor research.** Read the most helpful reviews of the apps in your category: `sort: "mostRelevant"` returns the reviews users actually see and upvote.
- **Support quality auditing.** `replyText` and `replyDate` show which complaints a developer answers and how fast, yours or a competitor's.
- **Review corpora for analysis.** Tens of thousands of dated, versioned, star-labelled texts per app, in any of the store's languages, for sentiment and topic work.

### Limits and behaviour

- **`reviewId` is the stable identity of a review.** Diff runs on it. `position` is this run's ordering, and the store reshuffles its rankings continuously.
- **Reviews arrive edited.** When a user updates their review, the store shows only the latest version, under the same `reviewId` with a new `date`. A schedule catches the history that a single pull cannot.
- **The language setting selects an edition, not a detection.** `language: "en"` returns the store's English-edition review stream; a reviewer whose device is set to English but who wrote in another language still appears in it.
- **A date range is cheap only with the newest sort.** That is the one order where the run can stop at the boundary. The other sorts apply the same filter exactly, but read on to the cap to apply it.
- **`appVersion` is missing on roughly a third of rows.** Rating-only reviews and reviews from before the store recorded versions carry `null`. Filter on it rather than expecting it.
- **Star-filtered pulls are fetched directly**, not sifted client-side, so they cost what they return. The per-app cap applies to the app as a whole, not per star.
- **A typo'd package name fails loudly.** It is named in the log and `RUN_SUMMARY.failures` as not found; an app with genuinely no reviews returns zero rows and a clean exit instead.
- **Public data only.** What the store shows every visitor, and nothing that gets around access controls.

### FAQ

**Do I need a Google account or API key?** No. You supply no credentials.

**Why did I get fewer reviews than `maxReviewsPerApp`?** The app ran out of matching reviews. Check `perApp` in `RUN_SUMMARY`: `exhausted: true` means the store had no more; `ceilingHit: true` means there were more and the cap stopped the run.

**Can I get all reviews of a big app?** Up to 100,000 per app per run — raise `maxReviewsPerApp` and expect volume: the run pages steadily and charging is per review saved. Apps with more reviews than that are better pulled in shaped slices: one run per star rating, or date windows over `dateFrom`/`dateTo`, each slice with its own 100,000 ceiling.

**How do I read reviews in another language?** Set `language` (and usually `country` to match): `language: "de", country: "de"` returns the German-edition stream. Runs are per edition; two languages are two runs.

**What are the `criteria` names?** The store's own per-aspect rating tags, such as ad frequency or battery use, exactly as it records them. Coverage varies by app and review; most rows carry an empty list.

**Does it return the app's overall star average?** No — this Actor returns the reviews themselves. The overall rating belongs to an app-details scraper.

**Can I plug it into something else?** Yes. Apify API, the client libraries, webhooks, scheduled runs, dataset exports to JSON, CSV or Excel, or MCP. The output is structured JSON.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~google-play-reviews-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "appIds": ["com.duolingo"],
 "sort": "newest",
 "maxReviewsPerApp": 200
 }'
```

### JavaScript example

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/google-play-reviews-scraper').call({
    appIds: ['com.duolingo', 'com.babbel.mobile.android.en'],
    starRatings: ['1', '2'],
    dateFrom: '30 days',
    maxReviewsPerApp: 500,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(`${items.length} reviews`);
```

# Actor input Schema

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

The apps to read reviews from, one per line. Accepts a package name (com.spotify.music), any store URL (https://play.google.com/store/apps/details?id=com.spotify.music), or an Android market:// link. Each app is read independently, so two apps cost roughly twice one. A typo'd package name is reported as "app not found" in the run summary rather than silently returning nothing.

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

The order reviews are read in, which decides what a capped run contains. "Newest first" reads the most recent reviews and is the one order where a date range stops the run early at the boundary. "Most relevant" is the store's own default ranking - detailed, heavily-upvoted reviews first, which is what a person sees on the app page. "Rating" reads highest-rated first.

## `starRatings` (type: `array`):

Keep only reviews with these exact star ratings, e.g. just 1-star to read complaints, or 1 and 2 to monitor problems. The filtering happens at the source, so a 1-star pull from a well-loved app does not page through thousands of 5-star reviews to find them - each selected star is fetched directly. Leave empty for all ratings. The cap on reviews per app applies to the app as a whole, not per star.

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

Cap on reviews saved for each app, and the main cost control since charging is per review. Popular apps carry millions of reviews, so a run without a sensible cap can be long and expensive. The run stops early when an app has fewer reviews than the cap, and the per-app numbers in the run summary say which happened.

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

Which language edition of the reviews to read, as the store's own language code: en, de, fr, ja, pt-BR and so on. This selects the store's language grouping rather than detecting the language of each text - reviewers who wrote in another language from a device set to this one still appear.

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

Two-letter storefront code such as us, gb, de or in. Availability and the review pool shift with it, so reading the same app from two storefronts returns overlapping but not identical sets.

## `dateFrom` (type: `string`):

Oldest review to keep. Absolute as 2026-01-15, or relative as "30 days", "36 hours", "6 months". Applied exactly to each review's own timestamp. With sort "Newest first" the run also stops paging the moment it crosses this boundary, which makes it the cheap way to say "reviews from the last month"; the other sorts apply the same filter but read on to the cap to apply it.

## `dateTo` (type: `string`):

Newest review to keep, in the same formats. A bare date like 2026-06-01 keeps the whole of that day. Ignored unless "Written after" is also set, because a range needs a start; the run says so in the log when that happens.

## Actor input object example

```json
{
  "appIds": [
    "com.spotify.music",
    "https://play.google.com/store/apps/details?id=com.duolingo"
  ],
  "sort": "newest",
  "starRatings": [],
  "maxReviewsPerApp": 200,
  "language": "de",
  "country": "de",
  "dateFrom": "30 days",
  "dateTo": "2026-06-01"
}
```

# Actor output Schema

## `items` (type: `string`):

Every review the run produced.

## `runsummary` (type: `string`):

The RUN\_SUMMARY record from the run's key-value store.

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

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

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/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": [
    "com.spotify.music"
  ]
}' |
apify call arman-bd/google-play-reviews-scraper --silent --output-dataset

```

## MCP server setup

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