# App Review Scraper (Apple App Store + Google Play) (`sonarcue/app-review-scraper`) Actor

Scrapes app reviews from BOTH the Apple App Store and Google Play in one Actor and normalizes them to a single schema. Pure HTTP -- no browser, no login. Covers a gap the incumbent (epctex/appstore-scraper, App Store only, subscription pricing) has stopped serving.

- **URL**: https://apify.com/sonarcue/app-review-scraper.md
- **Developed by:** [hsienchu chen](https://apify.com/sonarcue) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## App Review Scraper (Apple App Store + Google Play)

Scrapes app reviews from **both** the Apple App Store and Google Play in a
single Actor and normalizes them to one output schema — reviewer, rating,
title/text, app version, date, helpfulness count, and a link back to the
review. Built for ASO agencies, product managers, and support/QA teams who
want a scheduled feed of competitor or own-app reviews without stitching
together two separate scrapers.

**Pure HTTP — no browser automation, no login, no CAPTCHA-solving.** Both
data sources are public endpoints/pages that don't require an account.

### Why this Actor

The existing App Store review scraper on Apify Store
(`epctex/appstore-scraper`) is a well-known name but has aged: 1,046 total
users and only 9 monthly active users (≈0.9% retention) as of September
2026, on a $25/month subscription-style pricing model that covers App
Store only. A newer pay-per-event entrant in the same category
(`automation-lab/apple-app-store-scraper`) already has 96 monthly active
users on roughly half the total user count — proof the pay-per-event model
and fresher execution both work here. This Actor targets the same gap and
adds Google Play coverage in one product, which none of the current
App-Store-only or Play-only incumbents offer together.

### What it does

Given an app identifier for one or both stores, the Actor fetches recent
reviews and pushes one normalized record per review to the dataset:

- **Apple App Store**: paginates the public iTunes Customer Reviews
  RSS/JSON feed (`itunes.apple.com/.../rss/customerreviews/...`), 50
  reviews per page, up to 10 pages (500 reviews) per run. Supports both
  "most recent" and "most helpful" server-side sort.
- **Google Play**: fetches the app's Play Store page and parses the batch
  of real reviews Google embeds server-side in the page itself (see
  "Known issues" for the pagination limitation of this approach).

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `store` | enum | `appstore` | `appstore`, `googleplay`, or `both`. |
| `appId` | string | — | Apple numeric track id (e.g. `284882215`), or a Google Play package name (e.g. `com.whatsapp`) if `store` is `googleplay`. |
| `appUrl` | string | — | Full App Store or Play Store URL. Store/id auto-detected from the URL. |
| `googlePlayId` | string | — | Only needed when `store` = `both`: the Google Play package name (Apple app comes from `appId`/`appUrl`). Not needed when `store` = `googleplay`. |
| `country` | string | `us` | Two-letter store country/region code. |
| `lang` | string | `en` | Language hint passed to Google Play (`hl`). Does not filter Apple. |
| `maxReviews` | integer | `100` | Cap per store (each store capped independently when `store` = `both`). |
| `sort` | enum | `recent` | `recent` or `helpful`. Fully honored for the App Store; ignored for Google Play in this version (see Known issues). |

#### Input examples

**App Store only (Facebook):**

```json
{ "store": "appstore", "appId": "284882215", "country": "us", "maxReviews": 100, "sort": "recent" }
```

**Google Play only (WhatsApp), by URL:**

```json
{ "store": "googleplay", "appUrl": "https://play.google.com/store/apps/details?id=com.whatsapp", "country": "us", "lang": "en" }
```

**Both stores (WhatsApp):**

```json
{
  "store": "both",
  "appId": "310633997",
  "appUrl": "https://apps.apple.com/us/app/whatsapp-messenger/id310633997",
  "googlePlayId": "com.whatsapp",
  "country": "us",
  "maxReviews": 50
}
```

### Output (dataset item)

One record per review, same shape for both stores:

```json
{
  "store": "appstore",
  "appId": "310633997",
  "appName": "WhatsApp Messenger",
  "country": "us",
  "reviewId": "14502577449",
  "userName": "Blanco Perla 🃏",
  "rating": 4,
  "title": "Por favor. Actualicen. Los teclados de colores. Y permitir letras distintas",
  "text": "Actualización",
  "version": "26.34.72",
  "date": "2026-09-02T10:15:37-07:00",
  "thumbsUp": 0,
  "url": "https://itunes.apple.com/us/review?id=310633997&type=Purple%20Software"
}
```

```json
{
  "store": "googleplay",
  "appId": "com.whatsapp",
  "appName": "WhatsApp Messenger",
  "country": "us",
  "reviewId": "d40bb75a-d1a5-44b3-863d-c8624445efe9",
  "userName": "Jonathan Spencer",
  "rating": 1,
  "title": null,
  "text": "The most useless app I've encountered since apps were invented. I have it on my phone, and whenever I try to link it to another device - PC or tablet, using their app - the account is placed under review, during which time I can't use it, without explanation. There is no customer support as such, just what seems to be a beta version of 1st-generation AI. Don't waste your time.",
  "version": "2.26.33.74",
  "date": "2026-08-28T20:40:15+00:00",
  "thumbsUp": 1,
  "url": "https://play.google.com/store/apps/details?id=com.whatsapp&reviewId=d40bb75a-d1a5-44b3-863d-c8624445efe9"
}
```

A real sample pulled live on 2026-09-04 — 25 App Store reviews for
WhatsApp, 10 App Store reviews for Facebook, and 20 Google Play reviews for
WhatsApp, 55 rows total — is checked into this repo as
[`sample_output.json`](./sample_output.json).

### Pricing

Built for **pay-per-event (PPE)** pricing. The code charges via
`Actor.push_data(..., charged_event_name="review-item")` per review and
`Actor.charge("actor-start")` once per run — the actual price per event is
set later in the Apify Console, not in this repo (see
[`NEXT_STEPS.md`](./NEXT_STEPS.md)). Until that console step is done, these
calls are harmless no-ops.

### Known issues

- **Google Play pagination is limited in this version.** There is no
  public Google Play review RSS/JSON feed. Full pagination on the web app
  goes through an undocumented POST RPC
  (`play.google.com/_/PlayStoreUi/data/batchexecute`, rpcid `UsvDTd`) that
  is versioned and can change without notice; reverse-engineering it
  correctly and keeping it stable is a bigger effort than this MVP's
  scope. Instead, this Actor parses the batch of reviews Google **already
  embeds server-side on the app's own Play Store page** — a real, verified
  data source (confirmed live 2026-09-04 against `com.whatsapp`, returning
  20 distinct real reviews per fetch), just capped at however many reviews
  Google chooses to embed there (observed ~20 for a very high-traffic
  app — smaller apps may return fewer). `maxReviews` above that count will
  not be reached for Google Play until batchexecute pagination is added in
  a future version.
- **`sort` is not honored for Google Play** in this version — the embedded
  batch comes back in whatever order Google's own page rendering uses
  (approximately relevance-based), not a strict recent/helpful sort. It is
  fully honored for the App Store (`mostRecent` / `mostHelpful` RSS sort
  parameters).
- **Google Play review permalinks are synthetic.** Google Play has no
  official public deep link to a single review, so the `url` field for
  Google Play rows is a best-effort constructed link
  (`.../details?id=...&reviewId=...`), not an official permalink like the
  App Store's.
- **Apple RSS feed has no official rate-limit documentation.** This Actor
  makes one request per page of 50 reviews (max 10 pages/run); no proxy or
  retry/backoff beyond a single request per page is implemented in this
  MVP. If Apple starts rate-limiting heavy usage, that would be the first
  thing to add.

### No credentials required

Neither data source needs a login, API key, or session token — both are
public pages/endpoints exactly as loaded by an anonymous browser.

# Actor input Schema

## `store` (type: `string`):

Which store(s) to scrape.

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

Apple App Store numeric track id (e.g. "284882215" for Facebook, "310633997" for WhatsApp), OR a Google Play package name (e.g. "com.whatsapp") if store is set to Google Play only. Leave empty if you supply appUrl instead.

## `appUrl` (type: `string`):

Full app page URL, e.g. https://apps.apple.com/us/app/facebook/id284882215 or https://play.google.com/store/apps/details?id=com.whatsapp. The store and id are auto-detected from the URL. Overrides appId for that store if both are given.

## `googlePlayId` (type: `string`):

Only needed when store = "both": the Apple app is identified via appId/appUrl above, and the Google Play app is identified separately here (package name, e.g. "com.whatsapp", or a full play.google.com URL). Not needed when store = "googleplay" -- use appId/appUrl for that case.

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

Two-letter store country/region code, e.g. "us", "gb", "tw", "jp". Applies to both stores.

## `lang` (type: `string`):

Two-letter UI/review language hint passed to Google Play (hl parameter). Apple reviews are returned in whatever language the reviewer used; this does not filter Apple results.

## `maxReviews` (type: `integer`):

Maximum number of reviews to fetch PER STORE (if store = both, this cap applies to each store independently). Apple: paginates the RSS feed in pages of 50, up to 10 pages (500 max). Google Play: capped by how many reviews Google embeds on the app page in this MVP (typically 20-40) -- see README Known issues.

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

Review sort order. Fully supported for the Apple App Store (recent -> sortBy=mostRecent, helpful -> sortBy=mostHelpful). Google Play in this MVP always returns Google's own default order regardless of this setting -- see README Known issues.

## Actor input object example

```json
{
  "store": "appstore",
  "country": "us",
  "lang": "en",
  "maxReviews": 100,
  "sort": "recent"
}
```

# Actor output Schema

## `reviews` (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 = {
    "appId": "",
    "appUrl": "",
    "googlePlayId": "",
    "country": "us",
    "lang": "en"
};

// Run the Actor and wait for it to finish
const run = await client.actor("sonarcue/app-review-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 = {
    "appId": "",
    "appUrl": "",
    "googlePlayId": "",
    "country": "us",
    "lang": "en",
}

# Run the Actor and wait for it to finish
run = client.actor("sonarcue/app-review-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 '{
  "appId": "",
  "appUrl": "",
  "googlePlayId": "",
  "country": "us",
  "lang": "en"
}' |
apify call sonarcue/app-review-scraper --silent --output-dataset

```

## MCP server setup

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