# Google Play Scraper — Reviews, Ratings & Sentiment Analysis (`scrapier/google-play-scraper`) Actor

Google Play Scraper extracts app reviews, ratings, reviewer details, dates, versions, and feedback, with sentiment analysis to identify positive, negative, and neutral opinions. Analyze app performance, user feedback, competitors, and market trends at scale.

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

## Pricing

from $5.99 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 Scraper — Reviews, Ratings & Sentiment Analysis

The Google Play Scraper — Reviews, Ratings & Sentiment Analysis pulls Google Play Store reviews, star ratings and app records for any Android app on the platform, in bulk. It returns full review text, star scores, developer replies and reply latency, plus — optionally — an AI-generated sentiment score, emotion, topic list, spam flag and one-line summary for every review, alongside per-app rating analytics. Every response is structured JSON, ready to pass directly to an LLM, index into a vector store, or feed a monitoring pipeline. No Google account or API key is needed to scrape; an AI key is only required if sentiment analysis is switched on, and it is always your own key.

***

### What is Google Play Scraper — Reviews, Ratings & Sentiment Analysis?

It is a Google Play data actor built around review collection: give it a list of package IDs or Play Store links (or search keywords to discover apps automatically) and it returns every matching app's user reviews as structured rows, filtered the way you specify. The reviews-focused job is what the name says — the actor also keeps three original app-data modes (app records, keyword search, similar apps) for teams that already depend on them. What sets this actor apart is the built-in **sentiment analysis** pass: reviews can be classified for sentiment, emotion, topics, category and spam directly in the run, using your own AI provider key, with nothing invented when no key is supplied. No Google account or Play Console login is required for any of the four modes.

Key capabilities:

- ⭐ **Bulk review collection** — list any number of apps in `appTargets`; every one is collected in the same run
- 🔀 **Review ordering and pagination control** — most relevant / newest / by rating, with a configurable page size
- 🧪 **Server-side review filtering** — star levels, date window, keyword include/exclude, app version, minimum helpful votes, developer-reply-only, de-duplication
- 🤖 **Optional AI sentiment and topic classification** — 20 selectable models across 7 providers, on your own key
- 📊 **Per-app ratings analytics** — star mix in percent, ratings-per-1,000-installs, average score and reply rate over the reviews actually collected
- 🌍 **83 Google Play storefront countries**, since reviews and ratings are storefront-specific

***

### What data can you get with Google Play Scraper — Reviews, Ratings & Sentiment Analysis?

The actor returns four result types, selected with the `mode` parameter: reviews (with optional AI sentiment), per-app ratings-analytics summaries, app search results, and similar-app records.

| Result Type | Extracted Fields | Primary Use Case |
| --- | --- | --- |
| Reviews (`type: "review"`) | `id`, `userName`, `date`, `score`, `text`, `thumbsUp`, `version`, `replyText`, `replyDate`, `hasDeveloperReply`, `replyLatencyDays`, `criterias`, `wordCount`, `url`, `storefront` | Review monitoring, bug/complaint triage, ASO research |
| Sentiment analysis (AI, added to review rows) | `sentiment`, `sentimentScore`, `emotion`, `topics`, `reviewCategory`, `isSpam`, `aiSummary`, `aiLanguage` | Triaging thousands of reviews without reading each one |
| Ratings analytics (`type: "app"`, summary rows) | `ratingDistributionPct`, `reviewToInstallRatio`, `reviewsCollected`, `avgScoreCollected`, `collectedStarCounts`, `developerReplyRate`, `sentimentBreakdown` | ASO and competitor rating benchmarking |
| App records (`type: "app"`, search / URL / similar-apps modes) | `title`, `installs`, `score`, `ratings`, `price`, `developer`, `genre`, `screenshots`, `histogram`, `categories` and 40+ more | App metadata research, discovery, catalogue building |

#### Sentiment analysis

Turning on `enableSentimentAnalysis` sends each collected review's text to the AI model you selected in `aiModel`, using your own `aiApiKey` (or the matching environment variable). The model returns a sentiment label, a −1…1 sentiment score, the dominant emotion, up to 5 topics, a review category (bug report / feature request / praise / complaint / question / spam / other), a spam flag, a one-line summary capped at 300 characters, and the detected language. Every value is validated before it is stored — an out-of-range score is clamped to −1…1, an unrecognised category or sentiment label is dropped rather than guessed, and any review the model does not classify keeps `null` in every AI field. Nothing about these fields is ever invented when no key is configured.

```json
{
  "sentiment": "negative",
  "sentimentScore": -0.9,
  "emotion": "frustration",
  "topics": ["crash", "playlist", "update"],
  "reviewCategory": "bug_report",
  "isSpam": false,
  "aiSummary": "App crashes when opening a playlist after the latest update.",
  "aiLanguage": "en"
}
```

#### Ratings analytics rows

When `includeAppSummary` is on (default), the reviews mode emits one extra row per app carrying the full app record plus analytics computed over the reviews actually collected in that run: `avgScoreCollected`, `collectedStarCounts`, `developerReplyRate`, and — with AI on — `sentimentBreakdown` (positive/neutral/negative counts). `ratingDistributionPct` and `reviewToInstallRatio` are derived from the app's public histogram and install count, not from the sample, so they stay accurate even on a small run.

***

### How does Google Play Scraper — Reviews, Ratings & Sentiment Analysis differ from the official Google Play API?

Google does publish an official endpoint for app reviews — `reviews.list` on the Google Play Developer API (Android Publisher API) — but it requires the `https://www.googleapis.com/auth/androidpublisher` OAuth scope, which Google grants only to a Play Console account that manages the specific app package (verified against Google's current API reference, checked 2026-08-15). In practice that means the official endpoint can return reviews only for apps you publish yourself — never a competitor's app, and never an app you have not registered in Play Console.

| Feature | Google Play Developer API (`reviews.list`) | This Actor |
| --- | --- | --- |
| Which apps it can read | Only apps you manage in Play Console | Any public app — package ID or Play Store link |
| Setup | Google Play Console account + OAuth/service-account credentials | Apify account, no Google login |
| Apps per call | One `packageName` per request | Any number of apps in one run (`appTargets`) |
| App metadata, search, similar apps | Not part of this endpoint | Included (`scrapeAppUrls`, `scrapeAppSearch`, `scrapeSimilarApps` modes) |
| Sentiment / topic classification | Not provided | Optional AI pass, 20 selectable models |
| Star / date / keyword / version filtering | Not provided by the endpoint | Built into the run |

If you already own and manage the app in Play Console and just want its reviews inside your existing publishing workflow, the official endpoint is the right tool. If you need reviews for apps you don't manage — competitors, market research, a whole category — or you want sentiment and topic classification without a separate pipeline, this actor is the fit.

***

### What is the difference between an app-data scrape and a review sentiment analysis?

An app-data scrape describes the app itself — install count, price, developer, genre, screenshots, the aggregate star histogram. A review sentiment analysis works one level down: it reads what individual users actually wrote, and classifies each opinion. The aggregate score on an app's listing tells you *that* users are unhappy; the review text and its sentiment classification tell you *why* — a specific crash, a pricing change, a missing feature — which is the input a support or product team can act on.

This actor returns both, as two distinct JSON object types in the same dataset: `type: "app"` rows carry the metadata and ratings-analytics fields, and `type: "review"` rows carry the individual review text plus, when AI is on, the `sentiment`, `emotion`, `topics` and `reviewCategory` fields. Use the app rows to track *what* the rating is doing over time, and the review rows to find out *why*.

***

### How to scrape Google Play with Google Play Scraper — Reviews, Ratings & Sentiment Analysis?

1. Open the actor on its Apify Store page and go to the **Input** tab.
2. Add your apps to `appTargets` — one package ID (`com.spotify.music`) or Play Store link per line. Leave it empty and add keywords to `searchTerms` instead to let the actor discover apps first.
3. Set the real query controls that matter for your run: `storefrontCountry`, `sortReviewsBy`, `maxReviewsPerApp`, and any filters under the review-filters section (`starRatings`, `reviewsPostedAfter`, `containsKeywords`, etc.).
4. Click **Start**. Rows appear in the dataset table while the run is still going.
5. Download the results as JSON, JSONL, CSV, Excel, XML, HTML or RSS from the **Export** button, or pull them with the Apify API / client.

```json
{
  "mode": "scrapeReviews",
  "appTargets": ["com.spotify.music"],
  "storefrontCountry": "us",
  "maxReviewsPerApp": 100,
  "sortReviewsBy": "newest"
}
```

#### How to run multiple queries in one job

`appTargets` and `searchTerms` are both arrays — list as many apps or keywords as you want and every one is processed inside the same run, apps handled one after another. `maxRecords` caps the total rows the whole run stores across all of them, and `maxReviewsPerApp` caps each app individually on top of that.

***

### ⬇️ Input

All parameters are optional — an empty input still runs, provided at least one of `appTargets` or `searchTerms` is filled in for the reviews, app-URL and similar-apps modes (`searchTerms` alone is enough for the keyword-search mode).

| Parameter | Type | Required | Default / Prefill | Description |
| --- | --- | --- | --- | --- |
| `mode` | string (enum) | No | prefill `scrapeReviews` | `scrapeReviews` | `scrapeAppUrls` | `scrapeAppSearch` | `scrapeSimilarApps`. Falls back to `scrapeReviews` if omitted entirely. |
| `appTargets` | array | No | — | Package IDs and/or Play Store links, one per line. Used by the reviews, app-records and similar-apps modes. |
| `searchTerms` | array | No | prefill `["habit tracker", "budget planner"]` | Keywords. Drive the keyword-search mode, and auto-discover apps in the other modes when `appTargets` is empty. |
| `storefrontCountry` | string (enum, 83 values) | No | prefill `us` | Which Google Play storefront to read. Reviews, ratings and app records are all storefront-specific. |
| `maxRecords` | integer | No | min `0`, prefill `60` | Run-wide ceiling on stored rows (reviews + app summaries). `0`/empty = no ceiling. |
| `maxReviewsPerApp` | integer | No | default `0`, min `0`, prefill `40` | Per-app ceiling on review rows, on top of `maxRecords`. `0` = no per-app ceiling. |
| `sortReviewsBy` | string (enum) | No | default `mostRelevant`, prefill `newest` | `mostRelevant` | `newest` | `rating`. |
| `reviewsPageSize` | integer | No | default `100`, min `10`, max `199` | Reviews requested per round trip. The actor clamps whatever value is entered into the 10–199 range. |
| `includeAppSummary` | boolean | No | default `true` | Adds one ratings-analytics row per app. |
| `resumeFromToken` | string | No | — | Continue a previous reviews run (`lastCursor` value from the key-value store). Applies to the **first app** in the target list only. |
| `starRatings` | array of `"1"`–`"5"` | No | — | Keep only these star levels. Each selected level is collected as its own pass. |
| `reviewsPostedAfter` | string (date) | No | — | Absolute date (`2026-01-31`) or relative window (`30 days`, `6 months`). |
| `reviewsPostedBefore` | string (date) | No | — | Absolute date or relative window. |
| `containsKeywords` | array | No | — | Keep a review only if its text contains at least one of these (case-insensitive). |
| `excludeKeywords` | array | No | — | Drop a review as soon as its text contains one of these (case-insensitive). |
| `appVersionFilter` | string | No | — | Exact version (`9.1.68.1888`) or prefix (`9.1`) to match a release line. |
| `minThumbsUp` | integer | No | default `0`, min `0` | Minimum helpful votes. `0` keeps everything. |
| `onlyWithDeveloperReply` | boolean | No | default `false` | Keep only reviews that carry a developer reply. |
| `deduplicateReviews` | boolean | No | default `true` | Drop a review already stored earlier in the same run. |
| `enableSentimentAnalysis` | boolean | No | default `false` | Turn on AI review classification. |
| `aiModel` | string (enum, 20 values) | No | default `claude-haiku-4-5` | Provider is inferred from the model name (`claude-*` = Anthropic, `gpt-*`/`o3-*` = OpenAI, `gemini-*` = Google, `grok-*` = xAI, `deepseek-*` = DeepSeek, `sonar*` = Perplexity, `mistral-*` = Mistral). |
| `aiApiKey` | string (secret) | No | — | Your own key for the chosen provider. Falls back to the matching environment variable (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`, `DEEPSEEK_API_KEY`, `PERPLEXITY_API_KEY`, `MISTRAL_API_KEY`). No key at all = AI columns stay empty. |
| `aiMaxReviews` | integer | No | default `0`, min `0`, prefill `60` | Cost guard: only the first N collected reviews are classified. `0` = classify every collected review. |
| `proxyConfiguration` | object | No | `{"useApifyProxy": true}` | Apify Proxy settings used by the automatic route ladder. |

**Legacy input keys** (still declared, still work): `action`, `scrapeReviews.appId`, `scrapeReviews.country`, `scrapeAppSearch.keywords`, `scrapeAppSearch.country`, `scrapeAppUrls.urls`, `scrapeSimilarApps.urls`, `count`, `cursor`. When both a legacy key and its modern equivalent are supplied, the modern one wins.

#### Example input

```json
{
  "mode": "scrapeReviews",
  "appTargets": [
    "com.spotify.music",
    "https://play.google.com/store/apps/details?id=com.duolingo"
  ],
  "storefrontCountry": "us",
  "maxRecords": 200,
  "maxReviewsPerApp": 80,
  "sortReviewsBy": "newest",
  "reviewsPageSize": 100,
  "includeAppSummary": true,
  "starRatings": ["1", "2"],
  "reviewsPostedAfter": "90 days",
  "containsKeywords": ["crash", "bug"],
  "excludeKeywords": ["great"],
  "onlyWithDeveloperReply": false,
  "deduplicateReviews": true,
  "enableSentimentAnalysis": true,
  "aiModel": "claude-haiku-4-5",
  "aiMaxReviews": 50
}
```

⚠️ **Common pitfall:** `resumeFromToken` only continues the **first** app in `appTargets`, not every app in a multi-app run. For a repeating multi-app monitoring job, use a rolling `reviewsPostedAfter` window (e.g. `"7 days"`) instead of the resume token.

***

### ⬆️ Output

Results are typed, normalized JSON — one dataset row per review or per app record, with a consistent field set for a given `type`. Export as JSON, JSONL, CSV, Excel, XML, HTML or RSS from the Apify Console, or read the dataset with the API / client. The dataset ships three preset table views — **⭐ Reviews & sentiment** (default), **📦 App records & ratings analytics**, and **💬 Reviews (original column set)** — and every review row is also mirrored, uncharged, into a per-run child dataset named `<runId>-reviews`.

#### Scraped results

Review rows (`type: "review"`) carry 32 fields:

| Field | Type | Description |
| --- | --- | --- |
| `id` | string | Review ID. |
| `appId`, `parentId` | string | The app the review belongs to (`parentId` mirrors `appId`). |
| `userName` | string | Reviewer display name (often "A Google user"). |
| `userImage` | string | Reviewer avatar URL. |
| `date` | string | ISO-8601 timestamp the review was posted. |
| `score` | number | Star rating, 1–5. |
| `scoreText` | string | Star rating as a string. |
| `text` | string | Full review text. |
| `title` | null | Google Play does not publish review titles; kept for schema compatibility. |
| `thumbsUp` | number | Helpful votes. |
| `version` | string | null | App version the review was written on. |
| `replyText`, `replyDate` | string | null | Developer reply text and its timestamp. |
| `hasDeveloperReply` | boolean | Whether a developer reply exists. |
| `replyLatencyDays` | number | null | Days between the review and the reply, when both dates are known. |
| `criterias` | array | Per-review sub-ratings (`{"criteria": ..., "rating": ...}`); empty for apps that don't expose them. |
| `textLength`, `wordCount` | number | Character and word count of the review text. |
| `url` | string | Deep link to the review. |
| `storefront` | string | Storefront country the review came from. |
| `type`, `isChild` | string / boolean | Row labels (`"review"` / `true`). |
| `scrapedAt` | string | ISO-8601 collection timestamp. |
| `sentiment` | string | null | AI only: `positive` | `neutral` | `negative`. |
| `sentimentScore` | number | null | AI only: −1…1, rounded to 3 decimals. |
| `emotion` | string | null | AI only: dominant emotion, lowercase. |
| `topics` | array | null | AI only: up to 5 lowercase topics. |
| `reviewCategory` | string | null | AI only: `bug_report` | `feature_request` | `praise` | `complaint` | `question` | `spam` | `other`. |
| `isSpam` | boolean | null | AI only: spam flag. |
| `aiSummary` | string | null | AI only: one-line summary, capped at 300 characters. |
| `aiLanguage` | string | null | AI only: detected language code. |

Without an AI key, all nine AI fields (`sentiment` through `aiLanguage`) are present but `null` — never guessed or filled in.

App rows (`type: "app"`) carry 57 fields: `title`, `description`, `descriptionHTML`, `summary`, `installs`, `minInstalls`, `maxInstalls`, `score`, `scoreText`, `ratings`, `reviews`, `histogram`, `price`, `free`, `currency`, `priceText`, `available`, `offersIAP`, `androidVersion`, `androidVersionText`, `androidMaxVersion`, `developer`, `developerId`, `developerEmail`, `developerWebsite`, `developerAddress`, `privacyPolicy`, `developerInternalID`, `genre`, `genreId`, `categories`, `icon`, `headerImage`, `screenshots`, `video`, `videoImage`, `previewVideo`, `contentRating`, `contentRatingDescription`, `adSupported`, `released`, `updated`, `version`, `recentChanges`, `comments`, `preregister`, `earlyAccessEnabled`, `isAvailableInPlayPass`, `appId`, `url`, `ratingDistributionPct`, `reviewToInstallRatio`, `type`, `isChild`, `parentId`, `storefront`, `scrapedAt`.

The reviews-mode ratings-summary row (`includeAppSummary: true`) adds 5 more analytics fields on top of those 57:

| Field | Type | Description |
| --- | --- | --- |
| `reviewsCollected` | number | Reviews stored for this app in this run. |
| `avgScoreCollected` | number | null | Average star score of those reviews. |
| `collectedStarCounts` | object | null | `{1: n, 2: n, 3: n, 4: n, 5: n}` count of each star level collected. |
| `developerReplyRate` | number | null | Percentage of collected reviews carrying a developer reply. |
| `sentimentBreakdown` | object | null | AI only: `{positive, neutral, negative, analyzed}` counts. |

```json
[
  {
    "type": "review",
    "isChild": true,
    "appId": "com.spotify.music",
    "parentId": "com.spotify.music",
    "id": "b6e8f0c1-1f3a-4a4e-9d2a-2b1f0f5c9a11",
    "userName": "A Google user",
    "userImage": null,
    "date": "2026-08-05T09:14:22.113Z",
    "score": 1,
    "scoreText": "1",
    "text": "App crashes every time I open a playlist since the last update.",
    "title": null,
    "thumbsUp": 12,
    "version": "9.1.68.1888",
    "replyText": null,
    "replyDate": null,
    "hasDeveloperReply": false,
    "replyLatencyDays": null,
    "criterias": [],
    "textLength": 65,
    "wordCount": 12,
    "url": "https://play.google.com/store/apps/details?id=com.spotify.music&reviewId=b6e8f0c1-1f3a-4a4e-9d2a-2b1f0f5c9a11",
    "storefront": "us",
    "sentiment": "negative",
    "sentimentScore": -0.9,
    "emotion": "frustration",
    "topics": ["crash", "playlist", "update"],
    "reviewCategory": "bug_report",
    "isSpam": false,
    "aiSummary": "App crashes when opening a playlist after the latest update.",
    "aiLanguage": "en",
    "scrapedAt": "2026-08-06T12:00:00.000Z"
  },
  {
    "type": "review",
    "isChild": true,
    "appId": "com.spotify.music",
    "parentId": "com.spotify.music",
    "id": "d2a9c7e4-5b6f-4e21-8c0a-71f3b2d9a441",
    "userName": "Jordan M.",
    "userImage": null,
    "date": "2026-08-04T18:02:10.000Z",
    "score": 5,
    "scoreText": "5",
    "text": "Great app, love the new playlist folders feature.",
    "title": null,
    "thumbsUp": 3,
    "version": "9.1.68.1888",
    "replyText": "Thanks for the kind words!",
    "replyDate": "2026-08-05T08:00:00.000Z",
    "hasDeveloperReply": true,
    "replyLatencyDays": 0.58,
    "criterias": [],
    "textLength": 50,
    "wordCount": 8,
    "url": "https://play.google.com/store/apps/details?id=com.spotify.music&reviewId=d2a9c7e4-5b6f-4e21-8c0a-71f3b2d9a441",
    "storefront": "us",
    "sentiment": null,
    "sentimentScore": null,
    "emotion": null,
    "topics": null,
    "reviewCategory": null,
    "isSpam": null,
    "aiSummary": null,
    "aiLanguage": null,
    "scrapedAt": "2026-08-06T12:00:00.000Z"
  },
  {
    "type": "review",
    "isChild": true,
    "appId": "com.spotify.music",
    "parentId": "com.spotify.music",
    "id": "9f1c2a3b-6d4e-4a90-b1c2-5e8f0a1b2c3d",
    "userName": "Priya S.",
    "userImage": null,
    "date": "2026-08-02T11:45:00.000Z",
    "score": 2,
    "scoreText": "2",
    "text": "Subscription price went up again with no new features to show for it.",
    "title": null,
    "thumbsUp": 27,
    "version": "9.1.60.1550",
    "replyText": null,
    "replyDate": null,
    "hasDeveloperReply": false,
    "replyLatencyDays": null,
    "criterias": [],
    "textLength": 72,
    "wordCount": 13,
    "url": "https://play.google.com/store/apps/details?id=com.spotify.music&reviewId=9f1c2a3b-6d4e-4a90-b1c2-5e8f0a1b2c3d",
    "storefront": "us",
    "sentiment": "negative",
    "sentimentScore": -0.6,
    "emotion": "disappointment",
    "topics": ["subscription", "pricing"],
    "reviewCategory": "complaint",
    "isSpam": false,
    "aiSummary": "User is unhappy about a subscription price increase.",
    "aiLanguage": "en",
    "scrapedAt": "2026-08-06T12:00:00.000Z"
  },
  {
    "type": "app",
    "isChild": false,
    "appId": "com.spotify.music",
    "parentId": null,
    "title": "Spotify: Music and Podcasts",
    "score": 4.3,
    "scoreText": "4.3",
    "ratings": 24831902,
    "reviews": 9812345,
    "histogram": { "1": 1200000, "2": 500000, "3": 1100000, "4": 3400000, "5": 18600000 },
    "ratingDistributionPct": { "1": 4.8, "2": 2.0, "3": 4.4, "4": 13.7, "5": 74.9 },
    "reviewToInstallRatio": 24.83,
    "reviewsCollected": 3,
    "avgScoreCollected": 2.67,
    "collectedStarCounts": { "1": 1, "2": 1, "3": 0, "4": 0, "5": 1 },
    "developerReplyRate": 33.3,
    "sentimentBreakdown": { "positive": 0, "neutral": 0, "negative": 2, "analyzed": 2 },
    "installs": "1,000,000,000+",
    "price": 0,
    "free": true,
    "currency": "USD",
    "developer": "Spotify AB",
    "genre": "Music & Audio",
    "contentRating": "Teen",
    "released": "Jul 25, 2011",
    "version": "9.1.68.1888",
    "url": "https://play.google.com/store/apps/details?id=com.spotify.music&hl=en&gl=us",
    "storefront": "us",
    "scrapedAt": "2026-08-06T12:00:00.000Z"
  }
]
```

***

### How can I use the data extracted with Google Play Scraper — Reviews, Ratings & Sentiment Analysis?

- 📱 **Product and mobile teams** — filter `starRatings: ["1","2"]` with `sortReviewsBy: "newest"` and an `appVersionFilter` prefix to catch a crash wave the day a release ships.
- 📊 **ASO specialists** — read `ratingDistributionPct`, `avgScoreCollected` and `developerReplyRate` on the summary rows to benchmark an app's rating trend and reply discipline against competitors.
- 🤖 **AI engineers and LLM developers** — an agent issues a run with `appTargets` and `enableSentimentAnalysis`, receives structured review JSON with `sentiment`/`topics`/`reviewCategory` already attached, and passes it straight to the model as grounded context — no separate classification step.
- 🎧 **Support / CX teams** — filter `onlyWithDeveloperReply: true` and check `replyLatencyDays` to audit how fast (or slowly) the team is answering users.

***

### How do you monitor Google Play sentiment and ratings over time?

Review sentiment isn't a one-time read — the useful signal is the *change* between runs: a sudden shift in `avgScoreCollected`, a spike in `bug_report`-categorized reviews, or `sentimentBreakdown.negative` climbing after a release. Repeated querying turns single-run snapshots into a trend: run the same `appTargets` on a schedule, and diff each app's ratings-summary row against the previous run's.

Fields worth diffing between runs: `avgScoreCollected`, `collectedStarCounts`, `developerReplyRate`, `sentimentBreakdown`, and `ratingDistributionPct` (drawn from the app's public histogram, so it moves even on runs where you don't re-collect every review). On the review rows themselves, `reviewCategory` and `topics` let you track which complaint types are trending up, not just whether the average score moved.

A practical loop: schedule a run across your `appTargets` with `reviewsPostedAfter: "7 days"` and `sortReviewsBy: "newest"` so each run only pulls what's new, store the run's `lastCursor` output for the first app, and compare the new summary row's fields to the last one — alert when `avgScoreCollected` drops or `sentimentBreakdown.negative` rises past what you expect. Set this up with an **Apify Schedule** on this actor; there is no built-in delta computation, so the comparison itself runs in your own pipeline against the stored dataset rows.

***

### Integrate Google Play Scraper — Reviews, Ratings & Sentiment Analysis and automate your workflow

Google Play Scraper — Reviews, Ratings & Sentiment Analysis works with any language or tool that can send an HTTP request through the Apify API.

#### REST API with Python

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_API_TOKEN>")

run = client.actor("<actor-id>").call(run_input={
    "mode": "scrapeReviews",
    "appTargets": ["com.spotify.music"],
    "storefrontCountry": "us",
    "maxReviewsPerApp": 50,
    "sortReviewsBy": "newest",
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item.get("type") == "review":
        print(item["score"], item["text"][:80])
```

#### Scheduled monitoring and delivery

There is no actor-specific webhook or push feature; use the platform's own scheduling and delivery instead. Set up an **Apify Schedule** to re-run the actor on a cron interval with a rolling `reviewsPostedAfter` window, and attach an **Apify webhook** on the run-succeeded event to trigger downstream processing once each run's dataset is ready.

***

### ⚖️ Is it legal to scrape Google Play reviews and ratings?

Yes — Google Play Scraper — Reviews, Ratings & Sentiment Analysis reads publicly accessible Play Store listing and review pages, the same pages any visitor's browser can load, without logging in or bypassing access controls. Reviews carry user-generated text and reviewer display names, which can constitute personal data; if you process them for individuals in the EU/UK or California you are acting as the data controller for that processing and should have a lawful basis, retain only what you need, and honour deletion requests (GDPR/CCPA framing applies to the review-author data). App metadata — installs, price, developer info, genre — is business/catalogue data governed by Google's own Terms of Service rather than personal-data law. Scraping for AI training and scraping for operational monitoring carry different risk profiles; consult your legal team for commercial use cases involving bulk data storage.

***

### ❓ Frequently asked questions

#### Which Google Play storefront countries does this scraper support?

83 storefronts, selected with `storefrontCountry` (default `us`) — reviews, star ratings and app records are all storefront-specific on Google Play, so the country you pick changes what comes back.

#### How do I control review order and volume?

`sortReviewsBy` picks `mostRelevant` (Play Store default), `newest`, or `rating`. `reviewsPageSize` sets how many reviews are requested per round trip (clamped to 10–199). `maxReviewsPerApp` and `maxRecords` cap volume per app and per run.

#### How does this scraper handle Google Play's anti-bot measures?

It runs requests through a route ladder that starts on the fastest available network path and escalates automatically — first to a shared route, then to a residential route — whenever Google Play returns an unusable response (HTTP 401/403/407/408/429 or 5xx), retrying with a short backoff before escalating further.

#### Does this scraper include sentiment analysis?

Yes — set `enableSentimentAnalysis: true` and supply `aiApiKey` (or the matching environment variable) for the provider behind your chosen `aiModel`. Each classified review gets `sentiment`, `sentimentScore`, `emotion`, `topics`, `reviewCategory`, `isSpam`, `aiSummary` and `aiLanguage`. Without a key, or if the model call fails, those fields stay `null` on that review.

#### How many reviews does this scraper return per app?

As many as the store exposes for that app, up to whichever of `maxReviewsPerApp` or `maxRecords` is reached first. `reviewsPageSize` is hard-clamped to 10–199 in the actor regardless of what you enter — there's no way to request a larger single page.

#### How do I monitor an app's ratings and sentiment over time?

Schedule the actor with `reviewsPostedAfter` set to a rolling window (e.g. `"7 days"`), then compare each run's ratings-summary row — `avgScoreCollected`, `collectedStarCounts`, `developerReplyRate`, `sentimentBreakdown` — against the previous run's. See "How do you monitor Google Play sentiment and ratings over time?" above.

#### Does this scraper work with Claude, ChatGPT, and AI agent frameworks?

It is not registered as an MCP tool. It is callable as a standard HTTP endpoint through the Apify API by any agent framework, so an agent can trigger a run, poll or wait for the dataset, and use the returned review and sentiment JSON as grounded context before answering.

#### Can I use this scraper without managing proxies or a Google account?

Yes. `proxyConfiguration` defaults to Apify Proxy (`{"useApifyProxy": true}`) and the route ladder escalates on its own; no Google account or Play Console login is needed for any of the four modes. The only credential you might supply is your own AI provider key, and only if sentiment analysis is switched on.

#### Does `resumeFromToken` resume every app in a multi-app run?

No — it applies only to the first app in `appTargets`. For repeating multi-app monitoring, use a rolling `reviewsPostedAfter` window instead so each scheduled run naturally picks up only what's new for every app.

***

### 💬 Your feedback

Found a bug, or missing a field you need in the review or app-record output? Let the Scrapier team know through the Apify Console's actor issue/support link on this actor's page — reports like this go directly into the next update.

# Actor input Schema

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

Reviews mode is the main job of this actor: it pulls user reviews for every app you list. The other three modes return app records instead of reviews.

## `appTargets` (type: `array`):

One app per line. Both a package ID (com.example.app) and a full Play Store link work. Bulk is supported — this is the big difference from a single-app reviews run: list 20 apps and every one of them is collected in the same run. Leave this empty to let the search terms below decide which apps are used.

## `searchTerms` (type: `array`):

One keyword per line, e.g. habit tracker. In keyword-search mode each term is searched on the Play Store and the matching apps are returned as app records. In the reviews, app-records and similar-apps modes these terms are also used to pick the apps automatically whenever the target-apps field above is left empty.

## `storefrontCountry` (type: `string`):

Which Google Play storefront to read. Reviews, ratings and app records are all storefront-specific, so this changes what comes back.

## `maxRecords` (type: `integer`):

Hard ceiling on how many rows the run stores in total (reviews plus app summaries). Leave empty or 0 to keep going until every target is exhausted.

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

Per-app ceiling, applied on top of the run-wide maximum. Example: 5 apps x 40 = up to 200 review rows. 0 means no per-app limit.

## `sortReviewsBy` (type: `string`):

Most relevant is what the Play Store shows by default. Newest returns the freshest reviews first, which is what review-monitoring runs usually want. Rating walks the ratings-ordered list.

## `reviewsPageSize` (type: `integer`):

How many reviews are asked for at a time (10-199). Larger values finish a big app in fewer round trips; smaller values give tighter control over the run-wide maximum.

## `includeAppSummary` (type: `boolean`):

Emits one extra row per app carrying the full app record plus the rating breakdown in percent, the ratings-per-1000-installs ratio, and stats over the reviews actually collected (average score, star mix, developer-reply rate, and the AI sentiment mix when analysis is on).

## `resumeFromToken` (type: `string`):

Continue a previous reviews run instead of starting over. Take the value from the run's key-value store key lastCursor. Applies to the first app in the list.

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

Pick one or more star levels, e.g. 1 and 2 to work only on unhappy users. Each selected level is collected on its own pass, so a 1-star-only run really returns 1-star reviews rather than filtering a mixed page down to a handful.

## `reviewsPostedAfter` (type: `string`):

Absolute date (2026-01-31) or a relative window such as 30 days or 6 months. Reviews older than this are dropped.

## `reviewsPostedBefore` (type: `string`):

Absolute date or relative window. Reviews newer than this are dropped — combine with the field above for a fixed window.

## `containsKeywords` (type: `array`):

One term per line. A review is kept only when its text contains at least one of them (case-insensitive), e.g. crash, refund, subscription.

## `excludeKeywords` (type: `array`):

One term per line. A review is dropped as soon as its text contains one of them (case-insensitive).

## `appVersionFilter` (type: `string`):

Keep reviews written on a specific release. Accepts an exact version (9.1.68.1888) or a prefix such as 9.1 to match a whole release line.

## `minThumbsUp` (type: `integer`):

Keep only reviews with at least this many thumbs-up. Useful for surfacing the complaints other users agreed with. 0 keeps everything.

## `onlyWithDeveloperReply` (type: `boolean`):

Keeps just the reviews that carry a developer reply — handy for auditing support quality and reply latency.

## `deduplicateReviews` (type: `boolean`):

Removes a review that was already stored earlier in the same run (can happen when several star levels or pages return the same entry).

## `enableSentimentAnalysis` (type: `boolean`):

Adds sentiment, a -1 to 1 sentiment score, the dominant emotion, up to 5 topics, a review category (bug report / feature request / praise / complaint / question / spam / other), a spam flag, a one-line summary and the detected language.

## `aiModel` (type: `string`):

The provider is taken from the model name: claude-\* = Anthropic, gpt-*/o1/o3 = OpenAI, gemini-* = Google, grok-\* = xAI, deepseek-\* = DeepSeek, sonar\* = Perplexity, mistral-\* = Mistral. A cheap mini/flash/haiku model is plenty for review classification.

## `aiApiKey` (type: `string`):

Your own key for the provider you picked above. It is stored as a secret and used only to classify the reviews of this run. Leave it empty to fall back to the matching environment variable (ANTHROPIC\_API\_KEY, OPENAI\_API\_KEY, GEMINI\_API\_KEY, XAI\_API\_KEY, DEEPSEEK\_API\_KEY, PERPLEXITY\_API\_KEY or MISTRAL\_API\_KEY). With no key at all the AI columns stay empty.

## `aiMaxReviews` (type: `integer`):

Cost guard: only the first N collected reviews of the run are classified, the rest keep empty AI columns. 0 means classify every collected review.

## `action` (type: `string`):

Legacy alias for the mode field.

## `scrapeReviews.appId` (type: `string`):

Legacy single-app reviews target. Merged into the target-apps list above.

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

Legacy storefront for the reviews mode.

## `scrapeAppSearch.keywords` (type: `array`):

Legacy search keywords. Merged into the search-terms list above.

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

Legacy storefront for the keyword-search mode.

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

Legacy app URL list. Merged into the target-apps list above.

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

Legacy source-app list for the similar-apps mode. Merged into the target-apps list above.

## `count` (type: `integer`):

Legacy run-wide row limit. Same meaning as the maximum-rows field above.

## `cursor` (type: `string`):

Legacy resume token. Same meaning as the resume-token field above.

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

Apify Proxy settings used by the automatic route ladder.

## Actor input object example

```json
{
  "mode": "scrapeReviews",
  "searchTerms": [
    "habit tracker",
    "budget planner"
  ],
  "storefrontCountry": "us",
  "maxRecords": 60,
  "maxReviewsPerApp": 40,
  "sortReviewsBy": "newest",
  "reviewsPageSize": 100,
  "includeAppSummary": true,
  "minThumbsUp": 0,
  "onlyWithDeveloperReply": false,
  "deduplicateReviews": true,
  "enableSentimentAnalysis": false,
  "aiModel": "claude-haiku-4-5",
  "aiMaxReviews": 60,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All scraped items (review rows and per-app summary rows) in the Actor's default dataset.

## `resumeTokens` (type: `string`):

Cursor(s) saved at the end of a reviews-mode run: key 'lastCursor' (the resume token for the first target app) and 'lastCursorByApp' (a map of app id to that app's resume token). Feed either value into the resumeFromToken input to continue a later run.

# 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": "scrapeReviews",
    "searchTerms": [
        "habit tracker",
        "budget planner"
    ],
    "storefrontCountry": "us",
    "maxRecords": 60,
    "maxReviewsPerApp": 40,
    "sortReviewsBy": "newest",
    "reviewsPageSize": 100,
    "aiMaxReviews": 60,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapier/google-play-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": "scrapeReviews",
    "searchTerms": [
        "habit tracker",
        "budget planner",
    ],
    "storefrontCountry": "us",
    "maxRecords": 60,
    "maxReviewsPerApp": 40,
    "sortReviewsBy": "newest",
    "reviewsPageSize": 100,
    "aiMaxReviews": 60,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapier/google-play-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": "scrapeReviews",
  "searchTerms": [
    "habit tracker",
    "budget planner"
  ],
  "storefrontCountry": "us",
  "maxRecords": 60,
  "maxReviewsPerApp": 40,
  "sortReviewsBy": "newest",
  "reviewsPageSize": 100,
  "aiMaxReviews": 60,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapier/google-play-scraper --silent --output-dataset

```

## MCP server setup

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