# App Store & Google Play Reviews Scraper (Both Stores) (`ninhothedev/app-reviews-scraper`) Actor

Scrape app reviews from Apple App Store and Google Play in one run: rating, title, text, author, date, version, thumbs up, developer replies, plus app metadata. Multi-country.

- **URL**: https://apify.com/ninhothedev/app-reviews-scraper.md
- **Developed by:** [ninhothedev](https://apify.com/ninhothedev) (community)
- **Categories:** Developer tools, E-commerce, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

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

### What does App Store & Google Play Reviews Scraper do?

**Collect public Apple App Store and Google Play reviews in one dataset.** Enter app URLs, Apple numeric IDs or Android package names to export review text, star ratings, dates, app versions and developer replies where available. Optional app rows contain public metadata such as publisher, overall rating and price.

Use the data for app feedback research, competitor comparisons and repeated review snapshots. No store login or store API key is required. The Actor does not provide sentiment analysis, send alerts, or guarantee every review. Check its coverage report before using a run in an automated workflow.

### How to scrape app reviews

1. Enter your **App URLs or IDs**, then select countries and Google Play languages.
2. Set **Max reviews per app**, optional star ratings and **Reviews since**. Set an Apify maximum charge as well.
3. Click **Start**. Open **Reviews** and **Run summary**, then export the dataset as JSON, HTML, CSV or Excel.

Start with two reviews from each store:

```json
{
  "apps": ["com.spotify.music", "324684580"],
  "countries": ["us"],
  "languages": ["en"],
  "maxReviewsPerApp": 2,
  "ratings": ["1", "2"],
  "includeAppDetails": false
}
```

This requests up to four reviews, not four guaranteed matches. Apple reviews are collected per country storefront. Google Play reviews are requested with both country and language; identical review IDs found through multiple countries or languages are saved once per app within a run.

For discovery, `searchTerms`, `searchStore` and `searchResultsPerTerm` can resolve app IDs using the first selected country/language. Search with no resolved IDs is reported as unverified rather than a confirmed empty market. Use explicit IDs when you already know the apps.

### What data can you extract?

| Field | Meaning |
|---|---|
| `record_type`, `store`, `app_id`, `id` | Row type (`review` or `app`), source store and stable identifiers. |
| `app_name`, `rating`, `title`, `text` | App name, review stars and feedback. Google Play review titles are null. |
| `date`, `version` | Review timestamp and app version when supplied. |
| `author`, `author_image`, `thumbs_up` | Public display name, image URL and helpful votes when available. |
| `developer_reply`, `developer_reply_date` | Public response and its timestamp when returned by the source. |
| `country`, `language` | Requested storefront parameters; not a verified reviewer's location. Apple language is null. |
| `url`, `app_url`, `scraped_at` | Public links and extraction timestamp. |

With `includeAppDetails: true`, app rows also include available developer, overall rating, ratings count, installs, price, currency, category, current version and update date. They use the same flat columns as reviews; unavailable fields remain null. One app metadata row is saved per app and country, even when multiple Google Play languages are requested.

Example selected fields from an earlier test; the text is omitted here:

```json
{
  "record_type": "review",
  "id": "5e9459ad-6ca7-4171-a29d-9b0f6a6a251d",
  "store": "google_play",
  "app_id": "com.spotify.music",
  "rating": 1,
  "date": "2026-10-14T19:50:33Z",
  "version": "9.1.90.2270",
  "country": "us",
  "language": "en"
}
```

### How much does App Reviews Scraper cost?

Current base pricing is **$0.25 per 1,000 saved dataset rows**, plus **$0.00005 per start** at the default 512 MB. **Reviews and optional app metadata rows both count.** Four reviews plus one start cost $0.00105. Adding one metadata row for each of the two apps makes six rows and $0.00155. These are event-charge examples, not subscriptions or guaranteed result counts. Check the live [Pricing tab](https://apify.com/ninhothedev/app-reviews-scraper/pricing) for applicable rates.

The synthetic start event is charged once per GB of allocated memory, with a minimum of one. A start charge can apply even when no rows are returned. Platform usage for standard runs is included in the event pricing; Apify subscriptions and post-run storage access are separate.

`maxItems` caps **all** saved rows, including metadata; zero means no additional global row cap. `maxReviewsPerApp` limits reviews for each app/country/language job, subject to cross-job deduplication. The Actor checks its remaining event budget before new app jobs and review requests. A global cap or exhausted budget can leave later jobs unattempted; inspect the report.

### Coverage, filters and source limits

`RUN_SUMMARY` in the run's key-value store includes `status`, `deliveredItems`, per-job `coverage`, `searches`, `unprocessedJobs` and `sourceRequests`:

- **complete** means requested jobs reached their per-app limit, a verified source boundary or a valid date cutoff. It does not mean every review on the store was collected.
- **partial** means useful data may exist, but a budget/global limit, failed job, unresolved app search or missing requested metadata prevented full coverage.
- **incomplete** means execution failed. Storage and billing failures propagate; already stored rows may remain available.

A source-confirmed empty review page or a filter with no matches can complete with zero reviews. Unreadable responses and repeated pagination tokens are reported as failures. Check coverage even if the platform run says SUCCEEDED.

`reviewsSince` accepts an ISO date/time such as `2026-10-01T00:00:00Z` or a relative duration such as `7 days`. Invalid dates are rejected. Relative months mean 30 days and years mean 365 days. Dates are evaluated in UTC; missing dates cannot satisfy a requested date filter.

Apple's newest-first path reads up to ten RSS pages before using relevance-ordered review pages. Deep pages are **not globally chronological**, so an older review there does not stop the date-filtered scan. Safety bounds are 600 deep Apple pages and 2,000 Google Play pages per rating bucket; reaching a bound is partial, not exhaustive. Google Play `helpful` currently uses the same relevance sort as `relevant`; Apple `rating` also falls back to relevance. Source availability and review depth can change.

### Repeating and resuming runs

A new run creates a fresh snapshot. To monitor changes, schedule your own task, retain previous exports and compare review IDs downstream. Repeated time windows overlap; deduplicate before creating alerts. This Actor does not retain cross-run history or send messages.

Resurrecting the same run with the **same input** reuses saved rows and completed-job checkpoints. It does not save or charge existing rows again. Interrupted jobs may refetch source pages; relative date filters retain their original cutoff. Changed input or incompatible old storage requires a fresh run. Explicitly empty app/search lists are rejected; genuinely omitted app/search inputs use a small two-store demo.

### Python API example

Install the tested client with `python -m pip install apify-client==3.2.0`. Set your own `APIFY_TOKEN` in the environment. This example uses a $0.01 event ceiling, 512 MB and a three-minute timeout, and rejects incomplete coverage:

```python
import os
from datetime import timedelta
from decimal import Decimal
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("ninhothedev/app-reviews-scraper").call(
    run_input={
        "apps": ["com.spotify.music", "324684580"],
        "countries": ["us"], "languages": ["en"],
        "maxReviewsPerApp": 2, "ratings": ["1", "2"],
        "includeAppDetails": False,
    },
    memory_mbytes=512,
    run_timeout=timedelta(seconds=180),
    max_total_charge_usd=Decimal("0.01"),
)
if run is None or run.status != "SUCCEEDED":
    raise RuntimeError("Run failed; inspect its log and RUN_SUMMARY.")
record = client.key_value_store(run.default_key_value_store_id).get_record("RUN_SUMMARY")
if record is None or record["value"].get("status") != "complete":
    raise RuntimeError("Coverage is partial or missing; do not treat this as a complete snapshot.")
rows = list(client.dataset(run.default_dataset_id).iterate_items())
if len(rows) != record["value"]["deliveredItems"]:
    raise RuntimeError("Export count does not match the coverage report.")
for row in rows:
    print(row["store"], row["app_id"], row["id"], row["rating"], row["date"])
```

See the **API** tab for REST and other languages, or connect exports to Make, n8n, Zapier, Sheets or your database. The snippet itself creates no schedule or notification.

### FAQ and related tools

**Do I need proxies?** The small examples use direct public endpoints. Optional proxy configuration is available. If a requested proxy cannot be initialized, the run fails rather than silently switching routing.

**Why are replies or versions missing?** Apple RSS reviews can include version information but omit developer replies. Deeper Apple responses can include replies while omitting versions. Source fields are not available for every review.

**How do I report a problem?** Open the Actor's Issues tab with the run ID and a minimal input. Avoid credentials. Review text and display names are public user-generated data; collect and retain only what your intended use needs.

For discovery, use [Google Play Search Scraper](https://apify.com/ninhothedev/google-play-search-scraper) or [App Store Search Scraper](https://apify.com/ninhothedev/app-store-search-scraper), then supply the selected IDs here.

# Actor input Schema

## `apps` (type: `array`):

App Store or Google Play app URLs, Apple numeric IDs (324684580 / id324684580) or Google Play package names (com.spotify.music). The store is detected automatically.

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

Find apps by keyword instead of (or in addition to) URLs, e.g. 'notion'. The top results from the selected store(s) are scraped.

## `searchStore` (type: `string`):

Which store(s) to search for the search terms.

## `searchResultsPerTerm` (type: `integer`):

How many top search results per store to scrape for each search term.

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

Maximum reviews for each app/country/language job, subject to deduplication and source availability. This is a cap, not a guaranteed count.

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

Total dataset row cap including optional app metadata. Zero adds no global cap. Budget/cap exhaustion can leave later jobs unattempted; inspect RUN_SUMMARY.

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

Apple newest starts with RSS then deep relevance pages; deep output is not globally date ordered. Google Play helpful uses relevance; Apple rating uses relevance.

## `ratings` (type: `array`):

Only return reviews with these star ratings (empty = all). Example: 1 and 2 stars for complaint analysis.

## `reviewsSince` (type: `string`):

UTC ISO date/time (2026-10-01 or 2026-10-01T00:00:00Z), or duration such as 7 days. Invalid values fail. Relative months are 30 days; years 365 days.

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

Two-letter country codes (us, gb, de, fr, jp…). Each app is scraped for every country. App Store reviews are per country storefront.

## `languages` (type: `array`):

Google Play language parameters (en, de, pt-BR). Review IDs are deduplicated across all requested countries/languages for the same app.

## `includeAppDetails` (type: `boolean`):

Add a charged metadata row per app and country when available (record_type=app). Missing requested metadata marks coverage partial.

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

Usually not needed - both stores serve public JSON endpoints. Enable Apify Proxy only for very large runs.

## Actor input object example

```json
{
  "apps": [
    "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
    "https://play.google.com/store/apps/details?id=com.spotify.music"
  ],
  "searchStore": "both",
  "searchResultsPerTerm": 1,
  "maxReviewsPerApp": 20,
  "maxItems": 0,
  "sort": "newest",
  "ratings": [],
  "countries": [
    "us"
  ],
  "languages": [
    "en"
  ],
  "includeAppDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `reviews` (type: `string`):

No description

## `summary` (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 = {
    "apps": [
        "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
        "https://play.google.com/store/apps/details?id=com.spotify.music"
    ],
    "maxReviewsPerApp": 20,
    "countries": [
        "us"
    ],
    "languages": [
        "en"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ninhothedev/app-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 = {
    "apps": [
        "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
        "https://play.google.com/store/apps/details?id=com.spotify.music",
    ],
    "maxReviewsPerApp": 20,
    "countries": ["us"],
    "languages": ["en"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("ninhothedev/app-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 '{
  "apps": [
    "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580",
    "https://play.google.com/store/apps/details?id=com.spotify.music"
  ],
  "maxReviewsPerApp": 20,
  "countries": [
    "us"
  ],
  "languages": [
    "en"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call ninhothedev/app-reviews-scraper --silent --output-dataset

```

## MCP server setup

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