App Reviews
Fetch reviews, search apps, and look up metadata from the Apple App Store and Google Play Store.
Why App Reviews?
Apple and Google use completely different APIs, formats, and auth methods. App Reviews handles all of this behind a simple Python API -- no API keys required.
from app_reviews import AppStoreSearch, GooglePlayReviews, Country
# Search for apps
results = AppStoreSearch().search("whatsapp", country=Country.US, limit=5)
print(results[0].name, results[0].icon_url)
# Fetch reviews
reviews = GooglePlayReviews().fetch("com.whatsapp", countries=[Country.US])
for review in reviews:
print(f"{review.rating}* {review.body[:80]}")
Highlights
| Both stores | Apple App Store + Google Play in one package |
| Search & lookup | Find apps by keyword, look up metadata by ID |
| No API keys | Works out of the box using public endpoints |
| 155 countries | Fetch across regions in a single call |
| Official APIs | Optionally use App Store Connect or Google Play Developer API |
| Export | JSON, JSONL, CSV |
| Minimal deps | Just cryptography for JWT + stdlib urllib |
Install
pip install app-reviews
Or with uv:
uv add app-reviews
Quick Start
Apple App Store
from app_reviews import AppStoreReviews, Country
client = AppStoreReviews()
result = client.fetch("324684580", countries=[Country.US, Country.GB])
for review in result:
print(f"[{review.country}] {review.rating}* {review.title}")
Google Play Store
from app_reviews import GooglePlayReviews, Country
client = GooglePlayReviews()
result = client.fetch("com.instagram.android", countries=[Country.US])
for review in result:
print(f"[{review.country}] {review.rating}* {review.body[:80]}")
Review
fetch() returns a FetchResult containing Review objects:
| Field | Type | Description |
|---|---|---|
id |
str |
Raw identifier assigned by the source (see below) |
store |
Store |
"appstore" or "googleplay" |
app_id |
str |
App Store ID or package name |
country |
str |
Two-letter country code |
rating |
int |
Star rating (1 -- 5) |
title |
str |
Review title (may be empty for Google Play) |
body |
str |
Review text |
author_name |
str |
Reviewer display name |
app_version |
str | None |
App version at time of review |
created_at |
datetime |
When the review was posted |
updated_at |
datetime | None |
When the review was last edited |
is_edited |
bool |
Whether the review was edited |
source |
Source |
Provider (e.g. "appstore_scraper", "googleplay_official") |
language |
str | None |
Review language code |
fetched_at |
datetime | None |
When the review was fetched |
Review IDs
id is the raw identifier assigned by the source, passed through unchanged. Treat it as unique within a (store, source) pair rather than globally, use source to tell provenance apart, and key any deduplication on (store, source, id).
Ids are not necessarily comparable across sources. On the App Store they definitely differ -- RSS ids are numeric while Connect ids are opaque, with no mapping between them -- so the same review fetched via appstore_scraper and appstore_official carries two different ids. Google Play appears to use a single identifier space for both providers.
For App Store Connect specifically, customerReviewResponses requires a Connect customerReviews.id. RSS ids are numeric and Connect ids are opaque, with no mapping between them, so an appstore_scraper id cannot be used to reply.
Search & Lookup
Find apps by keyword and look up app metadata -- no authentication required.
Search
from app_reviews import AppStoreSearch, GooglePlaySearch, Country, AppMetadata
# App Store -- returns list[AppMetadata]
results: list[AppMetadata] = AppStoreSearch().search("fitness tracker", country=Country.US, limit=10)
for app in results:
print(f"{app.name} by {app.developer} ({app.rating}*)")
# Google Play -- returns list[AppMetadata]
results: list[AppMetadata] = GooglePlaySearch().search("fitness tracker", country=Country.US, limit=10)
for app in results:
print(f"{app.name} by {app.developer}")
Lookup
# Look up by bundle ID (App Store) or package name (Google Play)
# Returns AppMetadata | None
app = AppStoreSearch().lookup("com.burbn.instagram")
if app:
print(f"{app.name} - {app.icon_url}")
app = GooglePlaySearch().lookup("com.whatsapp")
if app:
print(f"{app.name} - {app.rating}*")
AppMetadata
Both search() and lookup() return AppMetadata objects:
| Field | Type | Description |
|---|---|---|
app_id |
str |
Bundle ID (App Store) or package name (Google Play) |
name |
str |
App display name |
developer |
str |
Developer or publisher name |
category |
str |
Primary category (e.g. "Social Networking") |
price |
str |
Formatted price (e.g. "Free", "$4.99") |
version |
str |
Current version string |
rating |
float |
Average star rating (0.0 -- 5.0) |
rating_count |
int |
Total number of ratings |
url |
str |
Store page URL |
icon_url |
str | None |
App icon image URL |
Authentication (Optional)
For higher limits and more data, use the official APIs with your developer credentials.
Requires an Apple Developer Program membership ($99/year).
from app_reviews import AppStoreReviews, AppStoreAuth, Country
auth = AppStoreAuth(
key_id="ABC123DEF4",
issuer_id="12345678-1234-1234-1234-123456789012",
key_path="/path/to/AuthKey.p8",
)
client = AppStoreReviews(auth=auth)
result = client.fetch("324684580", countries=[Country.US, Country.GB])
Requires a Google Play Developer account ($25 one-time).
from app_reviews import GooglePlayReviews, GooglePlayAuth, Country
auth = GooglePlayAuth(service_account_path="/path/to/service-account.json")
client = GooglePlayReviews(auth=auth)
result = client.fetch("com.instagram.android", countries=[Country.US])
Advanced Usage
from app_reviews import AppStoreReviews, RetryConfig
retry = RetryConfig(
max_retries=5, # default: 3
backoff_factor=1.0, # default: 0.5
timeout=60.0, # default: 30.0
retry_on=[429, 503], # default: [500, 502, 503, 504, 429]
)
client = AppStoreReviews(retry=retry, proxy="http://proxy.example.com:8080")
result = client.fetch("324684580", countries=["us"])
from app_reviews import GooglePlayReviews
from app_reviews.exporters.json import export_json
from app_reviews.exporters.csv import export_csv
from app_reviews.exporters.jsonl import export_jsonl
client = GooglePlayReviews()
result = client.fetch("com.instagram.android")
export_json(result.reviews) # JSON array string
export_csv(result.reviews) # CSV string with headers
export_jsonl(result.reviews) # one JSON object per line
Limitations
-
Free scrapers have limits:
- App Store RSS: ~500 most recent reviews.
- Google Play scraper: rate-limited and may not return all reviews.
-
No historical data:
- Only the most recent reviews from public endpoints
-
Official APIs require developer accounts:
- Apple ($99/year), Google ($25 one-time)
Documentation
Read the full docs includes guides on the Python API, authentication, export formats, and architecture.
Contributing
git clone https://github.com/firattamurcw/app-reviews.git
cd app-reviews
uv sync --group dev
make test
See CONTRIBUTING.md · Security Policy
Acknowledgements
The Google Play scraping logic (parsing AF_initDataCallback datasets, field index paths) is based on the work done in google-play-scraper by JoMingyu. We re-implemented it on top of our own HTTP layer to support retries and proxies, but the data-structure knowledge originates from that project.
License
Metadata
Release files for app-reviews 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| app_reviews-0.5.0.tar.gz | 141.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| app_reviews-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 186.0 kB
Release files / app_reviews-0.5.0.tar.gz
| Download URL | app_reviews-0.5.0.tar.gz |
|---|---|
| Size | 141.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f75d77bc9c5fcdf6704fa01813cec63c0555388b83c31a118c7188146421a11f
|
|
BLAKE2b-256 checksum How to use checksums |
1e9fbe7d928bfa66201e44222d60be27d59a40b8f7214cefd61c2b2408d6811d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 29, 2026.
Transparency logRelease files / app_reviews-0.5.0-py3-none-any.whl
| Download URL | app_reviews-0.5.0-py3-none-any.whl |
|---|---|
| Size | 44.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
28946935d52b485dbccb6b91d6b2363ee289465452eda9e1b2fd75b7b8cba17a
|
|
BLAKE2b-256 checksum How to use checksums |
417161ef447bc4c2e8122afa1edf91875ba9d5fb64ef468c745ce5e1b14bfe05
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 29, 2026.
Transparency log