If you are trying to scrape Google Play for Android ASO research, keyword tracking, or competitor app data, you already know it is not the same problem as the iOS side: Google does not publish anything close to Apple's iTunes Search API. There is a Play Console Developer API, but it only serves data for apps you own - it will not let you look up a competitor's rating or search for who ranks on a keyword. This post walks through what scraping Google Play actually involves without an official API, and how to get clean structured app data without reverse-engineering a page yourself. Whether you searched "google play scraper", "android aso" or "google play api" to get here, the tradeoffs below apply either way.
Why scraping Google Play is harder than it looks
There is no public, documented API for reading someone else's app data on Google Play. Everything that library authors and scrapers rely on comes from the Play Store's own web page: when you load play.google.com/store/apps/details?id=... in a browser, the rating, install count, and description are not returned as clean JSON from an endpoint - they are embedded inside a <script> tag as a deeply nested array, addressed by numeric position rather than named fields (AF_initDataCallback blobs, in the terminology scraping libraries use for this pattern). There is no schema, no versioning, and no changelog when Google alters it.
That has a few concrete consequences:
-
Positional, not named, fields. You are not reading
app["rating"]- you are readingdata[1][2][51][0]and hoping that index still means "rating" after the next Play Store frontend deploy. -
Silent breakage. When the blob structure shifts, you don't get an error - you get
Noneor the wrong value in a field that used to work, and nothing tells you which index moved. - Search has no clean equivalent either. The Play Store search results page renders similarly - keyword ranking has to be scraped from the same kind of embedded blob, in results order, with no separate "ranking API" to call.
- No official rate limit guidance, because there's no official API. You are making requests against a consumer-facing web page, not a developer product, so there is no documented ceiling - only the point where you start getting blocked or served degraded HTML.
Approach 1: DIY with Python
Attempt 1: plain requests against the app detail page
import requests, re, json
def fetch_playstore_app_raw(package_name: str, country="us", lang="en"):
url = f"https://play.google.com/store/apps/details?id={package_name}&gl={country}&hl={lang}"
r = requests.get(url, timeout=10)
# the app's data lives inside a script tag as a JS array literal,
# not as a JSON endpoint response
match = re.search(r"AF_initDataCallback\(({.*?})\);", r.text, re.DOTALL)
if not match:
return None
return match.group(1) # still needs JS-literal parsing, not just json.loads
This gets you the HTML, but the payload is a JavaScript object literal embedded in a script tag, not valid JSON on its own - you need a parser tolerant of JS syntax, and then you need to know which numeric index in that nested array holds rating, which holds installs, and so on. That mapping is not documented anywhere; it is discovered by diffing known values against the blob and is exactly the part that breaks on a frontend redesign.
Attempt 2: search mode (same problem, ranked results)
def search_playstore_raw(term: str, country="us", lang="en"):
url = f"https://play.google.com/store/search?q={term}&c=apps&gl={country}&hl={lang}"
r = requests.get(url, timeout=10)
match = re.search(r"AF_initDataCallback\(({.*?})\);", r.text, re.DOTALL)
return match.group(1) if match else None
# results are embedded in the same positional-array format,
# in the order the Play Store ranks them for the keyword
Same wall, applied to search: the ranking order is real and useful, but extracting appId, title and developer per result means navigating the same undocumented nested structure, and there is no separate "give me just the ranking" endpoint to fall back on.
The real cost here is the reverse-engineering, not the request itself
A single lookup you can eyeball and fix by hand is fine to hand-roll once. The cost shows up when the blob structure shifts and every app in your pipeline returns wrong or missing fields at the same time, with no error telling you why.
| DIY (reverse-engineered blob parsing) | Managed scraper (API) | |
|---|---|---|
| Upfront cost | Free (your time) | Pay per result returned |
| Official API | None exists for third-party app data | N/A - handled regardless |
| Field mapping | You maintain a positional index map | Comes back as named JSON fields |
| Breakage on redesign | Silent - wrong values, no error | Maintained on the provider's side |
| Search ranking | Same blob format, no separate endpoint | Same schema as lookup mode |
| Cross-platform (+iOS) | A second, unrelated scraper to write | Pairs with an App Store ASO actor on the same model |
Neither column is objectively "right." A one-off check on a single app is genuinely fine to hand-parse once, and the snippets above will get you the raw blob. Recurring monitoring across a competitor set is where a silent index shift becomes an expensive surprise.
Approach 2: a ready-made Google Play scraper (API)
This is the part where I show you the shortcut. Google Play Scraper is an Apify actor that does the blob parsing and index mapping for you and returns named, typed JSON fields - no reverse engineering, no proxy, no login.
Input
| Field | Type | Description | Example |
|---|---|---|---|
mode |
string |
search (apps by keyword) or lookup (apps by package name) |
search |
term |
string | Keyword to search for (search mode) | fitness tracker |
appIds |
array | Android package names to look up (lookup mode) | ["com.whatsapp", "com.spotify.music"] |
country |
string | Two-letter country code for the storefront |
us, br, de
|
language |
string | Two-letter language code |
en, pt, de
|
maxResults |
integer | Max apps to return in search mode (up to 30) | 30 |
{
"mode": "lookup",
"appIds": ["com.whatsapp", "com.spotify.music"],
"country": "us",
"language": "en"
}
Output
A real lookup result for WhatsApp Messenger (com.whatsapp):
{
"appId": "com.whatsapp",
"title": "WhatsApp Messenger",
"score": 4.66,
"ratings": 237058214,
"reviews": 1962977,
"installs": "10,000,000,000+",
"minInstalls": 10000000000,
"price": 0,
"free": true,
"currency": "USD",
"genre": "Communication",
"developer": "WhatsApp LLC",
"released": "Oct 18, 2010",
"updated": "Jun 10, 2026",
"version": "Varies with device",
"contentRating": "Everyone",
"adSupported": false,
"summary": "Simple. Reliable. Private. Message and call for free.",
"descriptionPreview": "WhatsApp from Meta is a free messaging and video calling app...",
"icon": "https://play-lh.googleusercontent.com/.../icon.png",
"url": "https://play.google.com/store/apps/details?id=com.whatsapp"
}
minInstalls comes back as an integer you can sort and filter on directly, instead of parsing "10,000,000,000+" yourself. Search mode returns a lighter subset of these fields, in Play Store ranking order.
Calling it from JavaScript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('plum_spear/aztec-googleplay-aso').call({
mode: 'search',
term: 'fitness tracker',
country: 'us',
language: 'en',
maxResults: 30,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, 'apps');
console.log(items[0]);
Calling it from Python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("plum_spear/aztec-googleplay-aso").call(run_input={
"mode": "lookup",
"appIds": ["com.whatsapp", "com.spotify.music"],
"country": "us",
"language": "en",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["title"], item["score"], item["minInstalls"])
Calling it from the CLI or plain REST
apify call plum_spear/aztec-googleplay-aso --input '{"mode": "search", "term": "fitness tracker", "country": "us", "maxResults": 30}'
curl "https://api.apify.com/v2/acts/plum_spear~aztec-googleplay-aso/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"mode": "lookup", "appIds": ["com.whatsapp"], "country": "us"}'
What people actually build with this
- ASO managers run search mode against target keywords to see who currently ranks, then switch to lookup mode to track ratings and install counts of specific competitors over time.
- App marketers quantify a category before spending on user acquisition - who dominates a keyword, how big their install base is, and whether the niche skews free or paid.
- Market researchers measure category size, pricing distribution, and developer concentration across hundreds of apps for reports and trend analysis.
- Indie Android developers validate an app idea against real data - keyword saturation, competitor pricing, install base - before writing a line of code.
- Cross-platform teams pair this with an App Store ASO scraper to build one consistent iOS + Android dataset instead of maintaining two unrelated pipelines.
Pricing
Pay-per-event: $0.15 per 1,000 results returned, plus a minimal actor-start event. No subscription, no monthly minimum - you only pay for the app records you actually get. Apify's free monthly platform credits are enough to test a real query before deciding whether it's worth it.
For context: the DIY route above means maintaining a positional index map into an undocumented blob that can shift without notice on any Play Store frontend update. $0.15 per 1,000 named, typed results is a small price for not carrying that maintenance risk yourself, especially once you're tracking more than one or two apps.
Using it from an AI agent (MCP)
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, which exposes them as callable tools for MCP-compatible clients. Same mode / term / appIds input, no separate integration to write.
Wrap-up
Scraping a single Google Play app yourself is doable - the snippets above will get you the raw blob, and for a one-off check that's enough. What they won't do on their own is survive the next frontend redesign, or hand you named, typed fields you can pipe straight into a spreadsheet or dashboard without maintaining a positional index map by hand. That gap is what Google Play Scraper on Apify closes: pick a mode, get back clean JSON with rating, installs, price and developer already parsed, priced at $0.15 per 1,000 results with no monthly commitment.
💡 Precisa monitorizar a sua marca nestas plataformas?
O GEO Tracker analisa a presença da sua empresa no ChatGPT, Gemini, Perplexity, Claude e mais — relatório PDF em 24h.
Top comments (0)