Logo of «2Captcha»To home page
Captcha bypass tutorials

Was this helpful?

How to Scrape Google Play

Gregory Fisher
Gregory Fisher

Technical engineer

Google Play has no public API for store data, and its numbers are trickier than they look: the same app has a different star rating in every country, while its rating count is global. In this guide you'll use the open-source 2scraper/googleplay-scraper to export apps, search results, developer catalogs and full review histories to JSON or CSV, with the country and language stamped on every row.

It runs locally with Playwright and needs no account or key for basic use. You'll also see what to switch on when Google Play starts refusing requests: proxies, reCAPTCHA solving with 2Captcha, or a hosted browser.

What you'll build

One command-line tool with five modes:

Mode What it returns Typical use
listing Apps from a genre grid, following the page's shelves Category tracking, top charts
search Apps for a keyword ASO and keyword research
app Full details for one package ID: exact installs, rating histogram, version, developer contacts Competitor monitoring
developer A developer's whole catalog Publisher research
reviews Review history, newest first, paged with continuation tokens Sentiment analysis, support triage

Each run writes one file with up to 46 fields per app, including title, brand (developer), category, price, currency, installs, installs_min, installs_exact, content_rating, iap_price_range, rating, rating_raw, review_count, review_text_count, histogram_1 to histogram_5, version, updated_at, gl and hl. Some fields exist only in app mode; see Limitations below.

Requirements

  • Python 3.9 to 3.13.
  • Playwright with Chromium (recommended). Selenium and pyppeteer versions ship for parity; pyppeteer is effectively unmaintained upstream.
  • One virtual environment per engine: Playwright and pyppeteer pin conflicting pyee versions, and pyppeteer and Selenium conflict on urllib3.
  • Optional: a 2Captcha API key for CAPTCHA solving and fingerprints, proxies, or a Browser API endpoint. New to the API? Start with the Quick start guide.

Project setup

bash Copy
git clone https://github.com/2scraper/googleplay-scraper.git
cd googleplay-scraper
python3 -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate

pip install -r requirements.txt -r requirements-playwright.txt
playwright install chromium

cp .env.example .env
python3 env_config.py   # shows which settings were loaded, without printing secrets

Keep keys and proxy credentials in .env, not on the command line. Leave it empty for a plain local run.

Quick start

bash Copy
# Listing: a genre grid, 3 shelf pages
python3 playwright_scraper.py --mode listing --category GAME --pages 3

# Search: apps for a keyword
python3 playwright_scraper.py --mode search --query "password manager"

# App: full details in the German store
python3 playwright_scraper.py --mode app --app-id com.whatsapp --gl DE --hl de

# Developer: a full catalog
python3 playwright_scraper.py --mode developer --developer 5700313618786177705

# Reviews: 10 pages, newest first, without reviewer names and avatars
python3 playwright_scraper.py --mode reviews --app-id com.whatsapp --pages 10 --no-authors

# Output: JSON, CSV or both
python3 playwright_scraper.py --mode search --query vpn --format both --out vpn_apps

Defaults: --gl US, --hl en, --format json, --out googleplay_apps, --pages 1, --retries 2. Reviews come newest first, the only sort order implemented; --reviews-per-call sets the batch size (default 200).

How the scraper works

Ratings are per country. Google Play computes an app's star rating separately for each market, but the rating count is global. In the repository's test on 2026-09-22, one app (com.rovio.baba) was fetched from the same IP with only the country changed:

Country (gl) Rating Currency Ratings count
BR 4.49 BRL 6,312,298
IN 4.13 INR 6,312,298
US 4.08 USD 6,312,298
JP 3.96 JPY 6,312,298
DE 3.90 EUR 6,312,298

That's why every row carries gl and hl. Never average ratings from different markets, and always pass --gl explicitly when you compare apps.

Ratings are not reviews. review_count is the number of star ratings; review_text_count is the number of written reviews, a much smaller figure. rating is the rounded value shown in the store (4.7), rating_raw the full-precision number from the page data (4.714563).

Data comes from page payloads, not CSS. The scraper reads the structured data Google Play embeds in its pages, so a redesign of the visible layout doesn't break it. sku is the package ID, which stays the same across markets; category is display text and changes with --hl.

Pagination differs by mode:

  • Listing: grid shelves have their own URLs. --pages N follows them and stops when a page adds no new package IDs.
  • Search: does not paginate. Measured result sizes were 22 apps for "game", 30 for "vpn" and 50 for "a". --pages above 1 prints a warning.
  • Reviews: strictly sequential with continuation tokens, so they can't run in parallel. Batches up to 2,000 reviews per call returned the full requested count in tests; reviews are deduplicated by review_id.

Anti-bot protection and CAPTCHA on Google Play

Google Play is open by default. In the repository's tests on 2026-09-22, all five routes returned HTTP 200 from a single datacenter IP with no cookies and no key, and no CAPTCHA appeared in twelve captures. A daily canary run from a bare GitHub runner confirms this. Proxies and paid tools add volume and geography; you need them when you scrape at scale or from many countries.

When Google does push back, the scraper has four tools, all off unless you enable them.

1. reCAPTCHA solving with 2Captcha. The scraper detects reCAPTCHA v2 and reCAPTCHA v3 on the page, sends a proxyless task to 2Captcha and injects the returned token. --solve-captcha controls when: never, when-blocked (default) or always. Each page gets one solve attempt.

bash Copy
python3 playwright_scraper.py --mode listing --category GAME --pages 5 \
  --twocaptcha-key "$TWOCAPTCHA_KEY" --solve-captcha when-blocked --min-score 0.7

--min-score (default 0.3) is the minimum score requested for reCAPTCHA v3. Task parameters are described in the reCAPTCHA v2 docs and reCAPTCHA v3 docs.

2. Proxies. Pass --proxy or --proxy-file. By default the scraper rotates per page (--proxy-rotate per-page): each grid page opens a fresh browser on the next proxy. Use per-run to keep one IP for the whole run; reviews always stay in one session because continuation tokens are tied to it. A refused page moves to the next proxy and spends --proxy-block-retries; a broken proxy (ERR_PROXY, ERR_TUNNEL, HTTP 407) spends the normal --retries. 2Captcha residential proxies cover the countries you pass in --gl; see the Proxy API docs for generating lists. Selenium can't authenticate proxies and strips credentials with a warning.

3. Hosted browser: 2Captcha Browser API. Browser API runs Chromium for you over CDP. Pass its endpoint with --cdp-endpoint and don't combine it with --proxy or --fingerprint, since the remote browser handles IP and fingerprint itself. Setup is in the Browser API docs. Only Playwright supports authenticated CDP endpoints.

4. Fingerprints. --fingerprint pulls a real browser profile from 2Captcha Browser fingerprints; --fp-tags and --fp-country narrow it. It requires --twocaptcha-key.

Building your own integration? See the API docs, the debugging sandbox and the error codes.

Output files and exit codes

Each run writes the results file plus a .meta.json sidecar with status (complete, partial or failed), stop_reason, pages requested, completed and failed, row count, start and final URL, finish time, gl and hl. Files are written atomically: the scraper writes a temporary file in the same folder, syncs it to disk and renames it over the target, so an interrupted run never leaves half a file. A run that gathers nothing writes nothing, so yesterday's good file survives.

The exit code tells your scheduler what happened without parsing logs:

Code Meaning What to do
0 Complete Data written.
1 Crash Unexpected error; check the traceback.
2 Bad usage Fix the command: missing flag, --concurrency above 1 for reviews, negative --retries, --proxy together with --cdp-endpoint, --fingerprint without a key. An unsupported country also lands here: Google Play answers HTTP 400 and the scraper stops at once without retrying.
3 Blocked A challenge page before parsing. Enable --solve-captcha, add proxies or use --cdp-endpoint.
4 Zero apps The run finished but found nothing. A package ID that doesn't exist ends here (HTTP 404, not_found), not in 3.
5 Content not obtained Pages never loaded, or the remote browser refused the connection. No file written.
6 Partial Some pages succeeded. Check stop_reason in .meta.json.

CSV output escapes cells that look like spreadsheet formulas, so a hostile app title can't run in Excel; the sidecar records how many cells were escaped.

Example output

A listing row from the repository's sample_output.json (captured 2026-09-22; selected fields out of 46). Fields that only app mode fills are null here:

json Copy
{
  "source": "play.google.com",
  "scraped_at": "2026-09-22T09:50:58.688595+00:00",
  "url": "https://play.google.com/store/apps/details?id=com.amazon.clouddrive.photos&hl=en&gl=US",
  "sku": "com.amazon.clouddrive.photos",
  "title": "Amazon Photos: Photo & Video",
  "brand": "Amazon Mobile LLC",
  "price": 0.0,
  "currency": "USD",
  "rating": 4.7,
  "rating_raw": 4.714563,
  "review_count": null,
  "category": "Photography",
  "gl": "US",
  "hl": "en",
  "genre_id": null,
  "installs": "50,000,000+",
  "installs_min": 50000000,
  "installs_exact": null,
  "content_rating": "Everyone",
  "iap_price_range": "$0.99 - $11.99 per item",
  "offers_iap": true,
  "histogram_5": null,
  "version": null,
  "screenshot_count": 24,
  "shelf": "Take better photos",
  "shelf_rank": 2
}

Using the results: compare one app across countries

Because ratings differ by market, a useful first report is one app's rating in several countries. Run app mode once per country:

bash Copy
for gl in US DE JP IN BR; do
  python3 playwright_scraper.py --mode app --app-id com.whatsapp --gl $gl --out app_$gl
done

Then collect the rows into one table:

python Copy
import json
from pathlib import Path

rows = []
for path in sorted(Path(".").glob("app_*.json")):
    if path.name.endswith(".meta.json"):
        continue
    for row in json.loads(path.read_text(encoding="utf-8")):
        rows.append(row)

rows.sort(key=lambda r: r.get("rating_raw") or 0, reverse=True)

print(f"{'Market':<8}{'Rating':>8}{'Ratings':>16}{'Written':>12}")
for r in rows:
    print(
        f"{r['gl']:<8}"
        f"{(r.get('rating_raw') or 0):>8.2f}"
        f"{(r.get('review_count') or 0):>16,}"
        f"{(r.get('review_text_count') or 0):>12,}"
    )

The ratings count should match in every row; the rating should not. If you need to rank many apps, use app mode for each package: listing rows have no review_count.

Limitations

  • Listing rows have no genre_id, installs_exact or review_count: grid tiles don't show them. Use app mode for those fields.
  • Search returns one page of results only (22 to 50 apps in tests).
  • Reviews sort newest first only; there is no filter by star score.
  • /store/books and /store/movies are refused; the scraper covers apps and games.
  • Counts on popular apps move between runs, so two runs a minute apart can differ.

FAQ

Is it legal to scrape Google Play?
The scraper reads public store pages without logging in. Whether your use is allowed depends on your jurisdiction and Google Play's terms, so check both and keep request rates reasonable. This is not legal advice.

Does Google Play have an official API for this?
The Google Play Developer API covers only apps you publish yourself. For other apps' ratings, installs and reviews, there's no public API, which is why scrapers read the store pages.

Why is the rating different from what I see on my phone?
Ratings are calculated per country. Pass the same --gl as your phone's store country to match.

How many reviews can I get?
There's no ceiling found so far: reviews page sequentially until the store runs out. Raise --pages and --reviews-per-call (up to 2,000 per call returned in full in tests).

Do I need 2Captcha to scrape Google Play?
Not for small jobs. Add proxies, --solve-captcha or the Browser API when you scale up, scrape many countries, or start getting exit code 3.

Conclusion

Google Play is one of the easier stores to scrape and one of the easiest to misread. Keep gl and hl on every row, treat ratings and written reviews as different numbers, use app mode when you need exact installs, and let exit codes drive your automation. When volume grows, add proxies, reCAPTCHA solving or a hosted browser without changing your code.

Get started: create a 2Captcha account for an API key, or try the Browser API. The full code is on GitHub.

Documentation