kestrel actors docs

kestrel actors › Apps

Google Play Reviews Scraper — Android App Reviews & Ratings

A Google Play reviews scraper that returns every user review of any Android app as structured rows: the 1–5 star rating, review text, reviewer name, thumbs-up count, app version, date and the developer's public reply — google play reviews for every language and country you name, in a single run, each row tagged with its language and country. It makes the same review call the Play Store's own web page makes, follows the continuation token to the end of the list, and works for android app reviews in the hundreds of thousands per app.

Run Google Play Reviews Scraper on Apify →

Google play reviews scraper pricing: what one row costs

EventWhat it isPriceCharged for
reviewReview row$0.004One Google Play review: 1-5 rating, review text, reviewer, thumbs-up count, app version, date and the developer's reply. App rows, status rows, filtered reviews and apps with no reviews are free.

You pay per delivered review row — $0.004 a review — and nothing else: app rows, status rows, filtered reviews, apps with no reviews, unknown packages and failed jobs are free. A run that reads 300 reviews per locale for two locales and keeps the 1–2★ ones with text typically delivers 60–120 rows, so it costs 25–50 cents. The full corpus of a big app is priced by the row too: 100,000 reviews are $400, and maxReviewsPerApp or a spending limit on the run caps it wherever you like.

Compute is tiny: one review call returns up to 100 reviews in about a second, so a thousand-review job is ten calls.

Google play reviews scraper input reference

Every field of Google Play Reviews, straight from the actor's input schema.

FieldTypeDefaultWhat it does
appIdsarray[]Android package names, e.g. com.whatsapp — the id= part of any Google Play URL, and what an earlier run's app_id gives you.
appUrlsarray[]App pages, e.g. https://play.google.com/store/apps/details?id=com.whatsapp — the package name is read from the URL for you.
languagesarray["en"]Play language codes (hl), one per line: en, de, fr, es, pt-BR, ja, zh-TW… Google Play lists reviews per language, so a global app needs several. Every review row carries its language.
countriesarray["us"]Two-letter Play country codes (gl), one per line: us, gb, de, in, br… Every review row carries its country; a review Google lists under two locales is delivered and billed once.
maxReviewsPerAppinteger1000 = every review Google pages out (big apps run into the hundreds of thousands). N = the first N in the order below. The main cost control. Range 0-100000.
sortstring (enum)"most_recent"Most recent is the one to schedule; most relevant is Google Play's own default order; rating puts the 5-star reviews first. One of: most_recent, most_relevant, rating.
minRatinginteger00 = keep every review. 1-5 = keep only reviews at or above this many stars. Filtered reviews are never charged; when minRating and maxRating name the same star value Google filters server-side and the others are never even fetched. Range 0-5.
maxRatinginteger00 = keep every review. 1-5 = keep only reviews at or below this many stars — set 2 for a complaints feed that pays nothing for the happy ones. Range 0-5.
sinceDatestring""Leave empty for all. YYYY-MM-DD, or relative so a schedule never goes stale: "7 days", "2 weeks", "3 months". Combine with "Most recent" for a daily new-reviews feed.
requireTextbooleanfalseDrop reviews that are a star rating with no words, so you deliver and pay for only the ones that say something.
includeAppRowbooleantrueAlso emit one free row per app and locale: name, developer, category, content rating, average rating, rating count, price and icon.
sessionsinteger4How many proxy sessions (egress IPs) run in parallel. More is faster; each is paced separately. Range 1-16.
perIpnumber1Pace for each session. Google answers about 1/s per IP; faster earns refusals and retries, not speed. Range 0.1-3.
proxyConfigurationobject{"useApifyProxy": true}Google Play's review call is public and needs no login, but it throttles each IP at about one request a second. Apify Proxy (the default datacentre group) spreads a large run over several IPs; small runs work without it.

Example input

{ "appIds": ["com.whatsapp"], "languages": ["en", "es", "pt-BR", "hi"], "countries": ["us", "br", "in"], "maxReviewsPerApp": 500 }
{ "appIds": ["com.spotify.music"], "languages": ["en"], "countries": ["us", "gb"], "sort": "most_recent", "maxRating": 2, "requireText": true, "maxReviewsPerApp": 300 }

Google play reviews scraper output fields

Three row types share one dataset: review (one user review, charged), app (context for an app in a language and country, free) and status (per app, language and country, free).

Dataset views in the Apify Console: Overview, Reviews, Complaints, Apps, Status.

FieldTypeWhat it holds
typestringRow type: review, app or status.
app_idstringAndroid package name, e.g. com.whatsapp.
app_namestringApp name in that language.
languagestringPlay language the row was read in (hl), e.g. en, pt-BR.
countrystringPlay country the row was read in (gl), two-letter code.
review_idstringGoogle's id for the review.
ratingintegerStars the reviewer gave, 1-5.
textstringReview text; null for a star-only review.
authorstringReviewer's display name.
author_idstringGoogle's id for the reviewer.
thumbs_upintegerHow many users marked the review helpful.
app_versionstringApp version the review was written against.
review_datestringWhen the review was posted, ISO 8601 UTC.
review_daystringReview date as YYYY-MM-DD.
reply_textstringThe developer's public reply, when there is one.
reply_datestringWhen the developer replied, ISO 8601 UTC.
urlstringGoogle Play page deep-linked to the review (reviewId).
developerstringDeveloper name (app rows).
developer_urlstringDeveloper website (app rows).
categorystringPlay category, e.g. COMMUNICATION (app rows).
content_ratingstringContent rating, e.g. Everyone (app rows).
rating_avgnumberAverage rating on Google Play (app rows).
rating_countintegerHow many ratings the app has (app rows).
pricenumberList price, 0 for free apps (app rows).
currencystringCurrency of the price (app rows).
iconstringApp icon URL (app rows).
descriptionstringShort description (app rows).
app_urlstringGoogle Play page URL (app rows).
reviews_fetchedintegerReviews read for this app and locale before filtering (app rows).
statusstringok, no_reviews, not_found or error (status rows).
targetstringThe package name you gave (status rows).
reviewsintegerReview rows delivered for this job (status rows).
filteredintegerReviews dropped by minRating, maxRating, sinceDate or requireText — never billed (status rows).
pagesintegerReview calls made for this job (status rows).
duplicatesintegerReviews already delivered under another locale — never billed twice (status rows).
errorstringWhy the job failed, when it did (status rows).
fetched_atstringWhen the row was read, ISO 8601 UTC.

Example row from a real run

Taken from the actor's README — this is the shape your dataset holds.

{
  "type": "review",
  "review_id": "22aacbd1-267e-445b-948c-629ad3d87a2a",
  "app_id": "com.whatsapp",
  "app_name": "WhatsApp Messenger",
  "language": "en",
  "country": "us",
  "rating": 5,
  "text": "so strict",
  "author": "Emmanuel Shimison",
  "author_id": "105709602236647094141",
  "thumbs_up": 0,
  "app_version": "2.26.21.75",
  "review_date": "2026-08-28T06:25:25Z",
  "review_day": "2026-08-28",
  "reply_text": null,
  "reply_date": null,
  "url": "https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=us&reviewId=22aacbd1-267e-445b-948c-629ad3d87a2a",
  "fetched_at": "2026-08-29T10:25:00Z"
}

Google play reviews scraper in Python, JavaScript and curl

Python

from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("kestrel/google-play-reviews-scraper").call(run_input={
    "appIds": ["com.whatsapp"],
    "languages": ["en", "de"],
    "countries": ["us", "de"],
    "sort": "most_recent",
    "maxRating": 2,
    "requireText": True,
    "maxReviewsPerApp": 200,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row["type"] == "review":
        print(row["language"], row["country"], row["rating"], row["review_day"], (row["text"] or "")[:80])

JavaScript (Node)

import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('kestrel/google-play-reviews-scraper').call({
    appIds: ['com.spotify.music'], languages: ['en'], countries: ['us', 'gb'],
    sort: 'most_recent', sinceDate: '7 days', maxReviewsPerApp: 500,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const reviews = items.filter(r => r.type === 'review');
console.log(reviews.length, 'new reviews this week;', reviews.filter(r => r.reply_text).length, 'answered');

curl

curl -X POST "https://api.apify.com/v2/acts/kestrel~google-play-reviews-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"appIds": ["com.whatsapp"], "languages": ["en"], "countries": ["us"], "maxReviewsPerApp": 50}'

The same three calls are runnable files in the examples repository: examples/google-play-reviews-scraper.py|.js|.sh.

Limits and honest notes

  • Locale is a list, not a filter. Google decides which reviews it shows under a given hl/gl; a review can appear under several locales (delivered once) and a few may appear under none. For a complete corpus, name every language the app has meaningful traffic in.
  • most_relevant is Google's ranking, not a date order. It surfaces the reviews Play itself promotes — good for sentiment, wrong for "what changed since yesterday". Schedule most_recent.
  • Text can be empty. Many Android reviews are a star rating with no words; requireText drops them before billing.
  • The reviewer's name is what they chose to display, and author_id is Google's opaque id for that account — useful for de-duplicating across apps, not for identifying people.
  • Throttling. Google answers refusals to bursts from one IP. The fetcher paces each session at perIp requests a second, treats an answer without a review payload as a refusal — never as "no reviews" — rotates the session and backs off. Large runs finish; they do not fail silently.
  • Not supported: app names as input (Play has no open search API; use the package name from the URL), review images, and the per-version breakdown of ratings.

FAQ

Does it need a Google account or API key?

No. Google Play has no public google play review api for reading reviews; this actor uses the public review call that the Play Store web page itself makes, without a login. You only need an Apify account.

Can I download Google Play reviews as CSV or Excel?

Yes. Every run writes an Apify dataset; open the Reviews view and export it as CSV, Excel, JSON or XML, or fetch it from the API. Google play reviews csv exports are the most common use — the columns are flat, so nothing needs cleaning.

How do I get only Google Play negative reviews?

Set maxRating to 2 (or minRating and maxRating both to 1 for one-star only, which Google filters server-side), add requireText: true so star-only reviews are dropped, and sort by most_recent. You pay only for the rows that survive — google play negative reviews for a fraction of the price of reading everything.

Can I scrape reviews in many languages and countries at once?

Yes — languages and countries are lists, every combination is one job, and each row carries language and country. That is the difference between an English-only export and google play reviews by language that reflect the whole user base.

How many reviews can one app return?

Everything Google pages out. There is no fixed cap: the actor follows the continuation token until the list ends, which for the biggest apps is hundreds of thousands of reviews per locale. maxReviewsPerApp and a spending limit on the run are the two ways to stop earlier.

Does it include the developer's reply?

Yes. reply_text and reply_date carry the google play developer reply when there is one — the basis of a response-rate report or a "which complaints went unanswered" view.

Can I use it for competitor app reviews?

Yes. Give several package names, one language and country set, and sort: "most_relevant"; you get competitor app reviews side by side with the same columns, ready for app review analysis — theme counting, sentiment, feature requests — in the tool of your choice.

Can I scrape Google Play reviews without an API?

That is exactly what this does: scrape google play reviews through the store's own web call, no SDK, no quota to apply for, no browser to run.

What does bulk work cost?

$0.004 a review row, nothing for anything else. 10,000 reviews are $40; a scheduled feed that delivers 20 new reviews a day is $2.40 a month.

Which order should I schedule?

most_recent with a relative sinceDate. most_relevant for a one-off portrait of what users think; rating when you want the praise first.

How to use Google Play Reviews Scraper

Run Google Play Reviews Scraper

The actor lives on the Apify Store, where the pricing tab carries the live per-row price and the input form has every field above: apify.com/kestrel/google-play-reviews-scraper