Architecture & Data-Flow Reference
Google Play + Apple App Store ยท discovery โ scraping โ combined CSV ยท scaled across 2 PCs
Self-contained document โ open in any browserThere is no "all apps" endpoint in either store. So the system works in two phases: first it discovers a big list of app IDs, then it scrapes the details for every app on that list into one CSV. Everything is free โ no API keys, no paid services.
discover.py writes apps.json.
GET data โ scraper.py reads apps.json and writes output/app_data.csv.
End-to-end, data flows left to right. The same flow runs on each PC; only the final merge is shared.
| Step | Script | Reads | Writes | Purpose |
|---|---|---|---|---|
| Find | discover.py | search APIs + charts | apps.json | build a big list of app IDs |
| Get | scraper.py | apps.json | output/app_data.csv | fetch metadata + ratings for each app |
| Merge | merge_csv.py | shard CSVs | output/app_data.csv | combine the two PCs' results, de-duped |
| File | Role |
|---|---|
discover.py | Phase 1 Finds app IDs and fills apps.json |
scraper.py | Phase 2 Fetches per-app data into the CSV (threaded, shardable, resumable) |
netutil.py | Shared safety Error classification + adaptive throttle / circuit breaker |
merge_csv.py | Helper Merges the two PCs' shard CSVs into one |
apps.json | Data Settings + the list of app IDs (the contract between the two phases) |
english_words.txt | Data ~9,868 common words used as deep search terms (path to 100k) |
requirements.txt | Dependencies: google-play-scraper, requests |
output/app_data.csv | The final dataset |
Because no full catalog exists, discovery queries many sources with many terms and keeps only the unique app IDs. Three sources feed one de-duplicated set per store.
| Source | Store | How | Yield / call | Role |
|---|---|---|---|---|
| Keyword search | Google Play | google_play_scraper.search(term) | ~30 (hard cap) | breadth via many terms |
| iTunes Search API | App Store | itunes.apple.com/search?term= | ~180 | main volume engine |
| RSS top charts | App Store | .../rss/{feed}/genre={id} (free/grossing/paid ร 23 genres) | up to 200 | popular apps, fast |
The function build_terms() assembles the query list from up to four tiers:
--deep (676)--terms-file (~9,868)
More terms = more apps. The wordlist (--terms-file english_words.txt) is the single
biggest lever toward 100k. --deeper (aaaโzzz, 17,576 terms) exists but is very slow.
--countries or settings).seen dictionary โ duplicates are ignored automatically.total unique = N / target.--target is reached (or terms are exhausted).apps.json (keeps your settings + anything already listed), then write the file.merge() drops duplicates against what's already in
apps.json. Re-running only adds new apps โ it never loses or doubles existing ones.
If apps.json is missing, a fresh one is created automatically.
This file is the contract between the two phases: discovery writes it, scraping reads it.
{
"settings": {
"country": "us", // default single country
"countries": ["us","gb","in","ca","au"], // scrape/discover across these
"lang": "en", // language (Google Play)
"review_count": 100,
"delay_seconds": 1.0 // polite pause; rate โ workers / delay
},
"google_play": [
{ "app_id": "com.whatsapp" } // the id= value from the Play URL (string)
],
"app_store": [
{ "app_id": 310633997, "name": "WhatsApp" } // numeric id from the App Store URL
]
}
| Store | ID type | Where it comes from |
|---|---|---|
| Google Play | string | play.google.com/store/apps/details?id=com.whatsapp |
| App Store | number | apps.apple.com/us/app/.../id310633997 |
Scraping turns the ID list into real data. For every app ร every country it makes one "detail" call and writes one CSV row. Two stores, two API calls, one unified row schema.
google_play_scraper.app(app_id, lang, country)
Scrapes the public listing page โ title, developer, genre, price, score, ratings count, review count, last-updated, version, URL.
itunes.apple.com/lookup?id=&country=
Official lookup API โ trackName, seller, genre, price, average rating, rating count, release date, version, URL.
output/app_data.csv| Column | Meaning |
|---|---|
store | google_play or app_store |
country | which region this row was fetched for |
app_id, title, developer, category | identity |
price, currency, free | pricing |
avg_rating | final average star rating |
total_ratings | total number of ratings |
text_review_count | number of written reviews (Play only) |
last_updated | date the app was last updated |
version | latest version |
url | store listing link |
CSV is UTF-8 with BOM โ opens cleanly in Excel. The same columns apply to both stores.
| Signal | Meaning | Reaction |
|---|---|---|
| 429 / 403 "too many requests", "quota" | rate-limited | Trip circuit breaker, wait it out, retry generously |
| 408 / 5xx, timeout, connection | transient hiccup | short exponential backoff, a few retries |
| 404 / not-found | healthy answer | skip immediately โ no wasted retries |
THROTTLING: pausing all workers ~60s is the protection working, not an error.
If it fires constantly, that IP is at its limit โ lower --workers on that machine.
These are network-wait tasks (most time is spent waiting for the server, not computing),
which is the ideal case for a thread pool. With --workers N, up to N requests are
in flight at once.
Effective request rate โ workers รท delay_seconds. The throttle sits above all workers,
so one rate-limit signal pauses the whole pool.
Two machines with separate IPs double throughput and the safe request budget
(each store rate-limits per IP). You don't split the file by hand โ both PCs read the same
apps.json and each takes a different shard.
All jobs are flattened to a list, then each PC takes jobs[shard::shards] โ i.e. every Nth job.
This interleaves Play/App-Store/country evenly, so neither PC gets a lopsided slice.
--shard 0 --shards 2
does jobs 0, 2, 4, 6 โฆ
writes app_data_shard0of2.csv
--shard 1 --shards 2
does jobs 1, 3, 5, 7 โฆ
writes app_data_shard1of2.csv
discover.py, then copy the resulting apps.json to both machines.output/ folder and run merge_csv.py.A 100k run can't depend on a single perfect execution. Two mechanisms make interruptions harmless:
Each row is written and flushed to the CSV the instant it's fetched. A crash, block, or Ctrl-C never loses completed work.
On startup the scraper reads the existing CSV and skips apps already done (keyed by store + country + id). Just re-run the same command to continue.
--no-resume only if you deliberately want to re-scrape everything.
| Flag | Default | Purpose |
|---|---|---|
--target N | 0 (all terms) | stop each store after N unique apps |
--terms-file PATH | โ | extra search terms (best lever to 100k) |
--deep | off | add 2-letter terms aaโzz (676) |
--deeper | off | add 3-letter terms aaaโzzz (17,576; slow) |
--countries a,b,c | settings | regions to discover across |
--skip-play / --skip-appstore | off | run only one store |
--search-limit / --chart-limit | 200 / 200 | results per iTunes term / per chart |
--hits | 30 | Play results per term (server caps ~30) |
--delay | 0.5 | polite pause between discovery requests |
| Flag | Default | Purpose |
|---|---|---|
--workers N | 10 | concurrent requests (thread pool) |
--shard i | 0 | this machine's slice index |
--shards N | 1 | total machines splitting the work |
--countries a,b,c | settings | regions to scrape (one row each) |
--no-resume | off | ignore existing CSV, scrape all again |
--out DIR / --config PATH | output / apps.json | output folder / input file |
# fastest route to 1 lakh โ App Store only, using the wordlist
python discover.py --skip-play --target 100000 --terms-file english_words.txt --countries us
# include Google Play too (adds ~15โ30k; can't reach 100k on its own)
python discover.py --target 100000 --terms-file english_words.txt --countries us,gb
# --- PC #1 (IP A) ---
python scraper.py --shard 0 --shards 2 --workers 10
# --- PC #2 (IP B) ---
python scraper.py --shard 1 --shards 2 --workers 10
# ...interrupted? just run the same command again โ it resumes.
# copy both app_data_shard*of2.csv into one output/ folder, then:
python merge_csv.py
python scraper.py --workers 10
| Reality | Detail |
|---|---|
| No full catalog | Neither store lists "all apps"; you only get what discovery finds. |
| Google Play search cap | ~30 results per query โ Play tops out ~15โ30k regardless of terms. |
| App Store is the volume | iTunes search ~180/term โ this is what reaches 100k. |
| Per-IP rate limits | Both stores throttle per IP; 2 PCs/IPs โ 2ร safe budget. Proxies extend further. |
| Per-country data | Ratings & pricing differ by country โ one row per app per country. |
| Terms of Service | Both stores restrict automated scraping. Fine for internal research at modest scale; get sign-off before commercial redistribution. |
--workers 10. If you see constant THROTTLING lines, lower workers
(e.g. 6) โ fewer cooldowns beats pushing too hard. Back up apps.json after a big discovery run;
it's the expensive artifact.