# How to Scrape App Store & Google Play Reviews (with Sentiment Analysis) 2026

> Source: <https://dev.to/robertokerber/how-to-scrape-app-store-google-play-reviews-with-sentiment-analysis-2026-27e2>
> Published: 2026-08-26 10:48:55+00:00

If you are trying to **scrape app reviews** - from the Apple App Store, Google Play, or both - to feed a product feedback loop, watch a competitor's rating, or build a RAG pipeline on top of real user complaints, you already know the two-part problem: getting the raw review text out is only half the job, and turning thousands of reviews into "here's what users are actually saying" is a whole separate pipeline you now have to build and maintain. This post walks through what a DIY App Store and Google Play reviews scraper actually costs you to build and keep alive, including the sentiment analysis layer, and how to get pre-enriched review data without any of it. Whether you searched "app reviews scraper", "google play reviews api" or "app store reviews sentiment analysis" to get here, the tradeoffs below apply either way.

The two stores are not equally hard, which is worth knowing before you start.

**Apple App Store** exposes a public, unauthenticated RSS feed for reviews (`itunes.apple.com/.../rss/customerreviews/...`

). No headless browser, no proxy - a plain HTTP request gets you JSON. The catch: it is capped at roughly the 500 most recent reviews per country per app, so covering more means looping over country codes.

**Google Play** has no such feed. There is no public, documented API for reading another app's reviews. The data lives behind Google's internal `batchexecute`

RPC endpoint that the Play Store web page itself calls - undocumented, unversioned, and returning a deeply nested JSON array addressed by numeric position instead of named keys. Reverse-engineering it works, until Google ships a frontend change and the index you were reading `review.author`

from now holds something else, silently.

On top of fetching the raw text, there is the part most scrapers skip: **making sense of it**. Raw review text is not actionable on its own - you need sentiment, a category (bug vs. feature request vs. praise), and a short summary, per review, at volume. Building that yourself means picking an LLM or NLP approach, tuning a prompt, batching calls without blowing through rate limits, and handling malformed responses - a second project stacked on top of the scraper itself.

``` python
import requests

def fetch_appstore_reviews(app_id: str, country: str = "us"):
    url = f"https://itunes.apple.com/{country}/rss/customerreviews/id={app_id}/sortBy=mostRecent/json"
    r = requests.get(url, timeout=10)
    entries = r.json().get("feed", {}).get("entry", [])
    return [
        {
            "title": e.get("title", {}).get("label"),
            "text": e.get("content", {}).get("label"),
            "rating": e.get("im:rating", {}).get("label"),
        }
        for e in entries[1:]  # first entry is the app itself, not a review
    ]
```

This genuinely works with no proxy and no browser. The limit is the feed itself: roughly 500 most recent reviews per country, and Apple does not document a hard rate limit, so you find it the hard way if you hammer this across many apps and countries.

``` python
import requests, re, json

def fetch_playstore_reviews_raw(package_name: str):
    # Google Play has no public reviews API. The web page calls an
    # internal batchexecute RPC endpoint that returns a deeply nested,
    # positionally-indexed array - not a documented JSON schema.
    resp = requests.post(
        "https://play.google.com/_/PlayStoreUi/data/batchexecute",
        data={"f.req": build_batchexecute_payload(package_name)},  # illustrative
        headers={"content-type": "application/x-www-form-urlencoded"},
    )
    # response is prefixed with ")]}'" and wrapped in nested arrays
    # that must be unwrapped by array index, not by key name
    raw = json.loads(resp.text.split("\n", 1)[1])
    return raw  # index-hunting for author/text/rating starts here
```

`build_batchexecute_payload`

above is illustrative - the real payload is an opaque encoded string that community scraping libraries maintain by trial and error, and it changes without notice. This is the wall: it works today, but there is no contract with Google saying it will work tomorrow, and when it breaks you get an empty or malformed response with no error message pointing at why.

Getting text out is still not the deliverable. Turning "the new update is so laggy, please add dark mode back" into `{sentiment: negative, type: bug, topics: [performance]}`

at volume means calling an LLM per review, writing a prompt that reliably returns the same JSON shape, and retrying malformed completions - infrastructure most scraper projects never get around to, so the review text just sits there unread.

Pulling twenty App Store reviews for a one-off check is genuinely fine to hand-roll. The cost shows up when you need Google Play coverage plus real sentiment classification, running continuously.

| DIY (Python + your own infra) | Managed scraper (API) | |
|---|---|---|
| App Store reviews | Free public RSS, capped ~500/country | Same feed, handled and paginated across countries |
| Google Play reviews | Reverse-engineered internal endpoint, breaks silently | Maintained on the provider's side |
| Sentiment / topics / type | You build and pay for an LLM pipeline | Comes back pre-attached on every review |
| Output format | Raw text you still have to enrich | Structured JSON, already classified |
| Scheduling | You wire up cron + monitoring | Native scheduler on the platform |

Neither column is objectively "right." If you need a handful of App Store reviews once, the RSS snippet above is all you need. If you need Google Play coverage and per-review sentiment running continuously, the reverse-engineering plus the LLM pipeline is the real cost - not the initial script.

This is the part where I show you the shortcut. [ App Store & Google Play Reviews Scraper](https://apify.com/plum_spear/aztec-apify-reviews) is an Apify actor that pulls reviews from both stores and attaches sentiment, topics, a summary, and a bug/feature/praise/complaint label to every single review - with zero setup required, and an optional bring-your-own-LLM mode for deeper analysis.

| Field | Description | Example |
|---|---|---|
`store` |
`appstore` or `googleplay`
|
`appstore` |
`appId` |
App Store numeric ID or Google Play package name |
`389801252` / `com.whatsapp`
|
`url` |
Full store URL (alternative to `appId` ) |
`https://apps.apple.com/us/app/id389801252` |
`country` |
Two-letter store country code |
`us` , `br` , `de`
|
`language` |
Language code (Google Play) |
`en` , `pt`
|
`maxReviews` |
Maximum number of reviews to fetch | `100` |
`enrich` |
Enable AI analysis on each review | `true` |
`llmBaseUrl` / `llmModel` / `llmApiKey`
|
Optional OpenAI-compatible endpoint for richer analysis | `https://api.openai.com/v1` |

```
{
  "store": "appstore",
  "appId": "389801252",
  "country": "us",
  "maxReviews": 100,
  "enrich": true
}
```

Leave the LLM fields empty and the AI fields still populate, via built-in keyword analysis - no external API key required to get sentiment out of the box.

```
{
  "store": "appstore",
  "appId": "389801252",
  "country": "us",
  "title": "Please bring back the old feed",
  "text": "I think Instagram is generally great but the new feed is...",
  "rating": 2,
  "version": "350.1",
  "author": "user_handle",
  "ai": {
    "sentiment": "negative",
    "topics": ["feed algorithm", "user experience"],
    "summary": "User dislikes the new feed and wants the old one back.",
    "type": "feature_request",
    "method": "llm"
  }
}
```

Every review comes back with the `ai`

block already attached - `sentiment`

, `topics`

, `summary`

, `type`

(bug / feature_request / praise / complaint / question / other), and `method`

telling you whether it ran through your LLM or the built-in analyzer. No separate classification step to write.

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });

const run = await client.actor('plum_spear/aztec-apify-reviews').call({
  store: 'googleplay',
  appId: 'com.whatsapp',
  country: 'us',
  maxReviews: 200,
  enrich: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
const negative = items.filter((r) => r.ai.sentiment === 'negative');
console.log(negative.length, 'negative reviews out of', items.length);
python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("plum_spear/aztec-apify-reviews").call(run_input={
    "store": "googleplay",
    "appId": "com.whatsapp",
    "country": "us",
    "maxReviews": 200,
    "enrich": True,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["ai"]["type"] == "bug":
        print(item["rating"], item["ai"]["summary"])
apify call plum_spear/aztec-apify-reviews --input '{"store": "appstore", "appId": "389801252", "maxReviews": 100}'
curl "https://api.apify.com/v2/acts/plum_spear~aztec-apify-reviews/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"store": "appstore", "appId": "389801252", "maxReviews": 100}'
```

`rating <= 2`

, and let `ai.type`

separate bugs from feature requests automatically - pipe new negatives to Slack so the team sees them within hours.`appId`

on a schedule to see exactly what their users praise and complain about, by country.Pay-per-event: **$0.15 per 1,000 reviews scraped**, plus a small optional fee per review enriched with AI analysis, and a minimal actor-start event. No subscription, no monthly minimum. Apify's free monthly platform credits are enough to run a real test against your own app before deciding whether it's worth it.

For context: if the DIY route costs you reverse-engineering the Google Play RPC endpoint plus standing up and paying for your own LLM pipeline, $0.15 per 1,000 reviews with sentiment already attached is the kind of number that stops being a debate fast. For a single App Store check, the RSS snippet above is genuinely fine on its own.

If you're wiring this into an agent instead of a script, actors published on Apify, including this one, are reachable through [Apify's MCP server](https://mcp.apify.com), which exposes them as callable tools for MCP-compatible clients. Same `store`

/ `appId`

/ `enrich`

input, no separate integration to write.

Pulling App Store reviews yourself is genuinely easy - the RSS feed is public and needs no proxy. Google Play is a different story, and sentiment classification at volume is a project of its own either way. That combination is what [App Store & Google Play Reviews Scraper](https://apify.com/plum_spear/aztec-apify-reviews) on Apify closes: point it at a store and an `appId`

, get back reviews with sentiment, topics, a summary and a bug/feature/praise label already attached, priced at $0.15 per 1,000 reviews with no monthly commitment.
