DEV Community

Roberto Kerber
Roberto Kerber

Posted on

How to Scrape the Apple App Store for ASO & Keyword Research (2026)

If you are trying to scrape the Apple App Store for ASO research, keyword tracking, or competitor monitoring, here is the honest version most posts skip: this one is genuinely easier than most scraping targets, because Apple exposes a public search API and public chart feeds that need no login and no proxy. The real cost isn't getting the data out - it's normalizing three different response shapes (search, lookup, charts) into one schema, handling Apple's undocumented rate limits, and building the scheduling and history layer on top so a snapshot becomes a trend. This post covers both. Whether you searched "app store scraper", "aso tool" or "app store keyword api" to get here, the tradeoffs below apply either way.

Why this is easier than most scrapers, and where it still bites you

Apple's iTunes Search API (itunes.apple.com/search) and the App Store's public RSS chart feeds are unauthenticated, documented (loosely), and return JSON directly - no headless browser, no residential proxy, no client-side rendering to fight. For search-by-keyword and lookup-by-ID, a plain requests.get() gets you real data on the first try. That's the good news, and it's worth saying plainly instead of manufacturing a horror story that isn't there.

Where it still costs you real engineering time:

  • Undocumented rate limits. Apple does not publish a request-per-minute ceiling for the Search API. Hit it too hard and you start getting empty results or throttled responses with no clear error explaining why - you find the limit by tripping it.
  • Three different response shapes. Search and lookup return the same iTunes JSON schema, but the RSS chart feeds (topfreeapplications, topgrossingapplications, etc.) come back in a different structure entirely, so "one App Store scraper" is really three code paths you have to keep in sync.
  • No native ranking history. Every response is a snapshot. Trend detection (a rating that moved, a competitor climbing the charts) only exists if you build the storage and diffing layer yourself - the API gives you no memory.
  • Country and entity matrix. Multiply keywords by storefronts (us, gb, br, de, ...) and entity types (software, iPadSoftware, macSoftware, ...) and you are running and merging a lot of small requests, not one big one.

Approach 1: DIY with Python

Attempt 1: keyword search and ID lookup (this actually works)

import requests

def search_apps(term: str, country: str = "us", limit: int = 50):
    r = requests.get(
        "https://itunes.apple.com/search",
        params={"term": term, "country": country, "entity": "software", "limit": limit},
        timeout=10,
    )
    return r.json()["results"]

def lookup_apps(app_ids: list[str], country: str = "us"):
    r = requests.get(
        "https://itunes.apple.com/lookup",
        params={"id": ",".join(app_ids), "country": country},
        timeout=10,
    )
    return r.json()["results"]
Enter fullscreen mode Exit fullscreen mode

No proxy, no browser, no anti-bot fight. Run this and you get real ratings, prices and metadata back in one request. The catch shows up once you scale it: run this in a tight loop across many keywords and countries and you start hitting undocumented throttling, with nothing in the response body explaining that you've been rate-limited versus that zero apps genuinely matched.

Attempt 2: Top Charts (a different shape entirely)

def get_top_charts(feed: str = "topfreeapplications", country: str = "us", limit: int = 100):
    url = f"https://itunes.apple.com/{country}/rss/{feed}/limit={limit}/json"
    r = requests.get(url, timeout=10)
    entries = r.json()["feed"]["entry"]
    return [
        {
            "rank": i + 1,
            "id": e["id"]["attributes"]["im:id"],
            "name": e["im:name"]["label"],
            "developer": e["im:artist"]["label"],
        }
        for i, e in enumerate(entries)
    ]
Enter fullscreen mode Exit fullscreen mode

This is a completely different response shape from search/lookup - im:-prefixed field names, a different nesting level, no rating or price data at all (charts feeds give you ranking, not metadata; you have to lookup_apps() afterward if you want ratings for the ranked IDs). Two API surfaces, one mental model you have to build to keep them consistent.

The real cost here isn't fetching data, it's normalizing and remembering it

Because the raw fetch is genuinely free and reliable, DIY is a fair choice for a single check. The cost shows up when you need one consistent schema across modes, historical trend data, and scheduled runs.

DIY (Python, direct API calls) Managed scraper (API)
Upfront cost Free (your time) Pay per result returned
Fetching data Genuinely easy - public JSON endpoints Same endpoints, wrapped for you
Rate limit handling You find the ceiling by tripping it Handled on the provider's side
Search / lookup / charts schema Three shapes you normalize yourself One consistent output schema
Country x entity matrix You loop and merge manually One input, same schema every time
History / scheduling You build storage + diffing Native scheduler, append to a dataset

Neither column is objectively "right." A one-off keyword check is genuinely fine to write yourself with the snippets above - that's the point of showing they work. Recurring, cross-country, cross-mode monitoring is where the normalization and scheduling work adds up.

Approach 2: a ready-made App Store ASO scraper (API)

This is the part where I show you the shortcut. App Store Scraper & ASO Tool is an Apify actor that wraps search, lookup and charts behind one input and one output schema, with rate limiting, country handling and dataset history already built in.

Input

Only the fields relevant to your chosen mode are required.

Field Description Example
mode search, lookup, or charts search
term Keyword to search for (search mode) fitness tracker
appIds One or more App Store IDs (lookup mode) ["310633997"]
feed Chart to fetch (charts mode) topfreeapplications
entity App type (search mode) software
country Two-letter storefront code us, gb, br
maxResults Maximum results to return (up to 200) 50
{
  "mode": "search",
  "term": "fitness tracker",
  "entity": "software",
  "country": "us",
  "maxResults": 50
}
Enter fullscreen mode Exit fullscreen mode
{
  "mode": "charts",
  "feed": "topfreeapplications",
  "country": "gb",
  "maxResults": 100
}
Enter fullscreen mode Exit fullscreen mode

Output

The same shape whether the record came from search, lookup, or charts:

{
  "appId": 389801252,
  "name": "Instagram",
  "developer": "Instagram, Inc.",
  "price": 0.0,
  "currency": "USD",
  "rating": 4.7,
  "ratingCount": 28000000,
  "genre": "Photo & Video",
  "version": "350.1",
  "releaseDate": "2010-10-06T19:12:14Z",
  "updatedDate": "2026-06-10T08:31:00Z",
  "contentRating": "12+",
  "url": "https://apps.apple.com/us/app/id389801252",
  "icon": "https://is1-ssl.mzstatic.com/image/.../512x512bb.jpg",
  "description": "Little moments lead to big friendships..."
}
Enter fullscreen mode Exit fullscreen mode

In charts mode, each record also includes a rank field - so you get ranking and metadata in the same response, no separate lookup call needed.

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-appstore-aso').call({
  mode: 'search',
  term: 'fitness tracker',
  country: 'us',
  maxResults: 50,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, 'apps');
console.log(items[0]);
Enter fullscreen mode Exit fullscreen mode

Calling it from Python

from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("plum_spear/aztec-appstore-aso").call(run_input={
    "mode": "lookup",
    "appIds": ["389801252", "310633997"],
    "country": "us",
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["name"], item["rating"], item["version"])
Enter fullscreen mode Exit fullscreen mode

Calling it from the CLI or plain REST

apify call plum_spear/aztec-appstore-aso --input '{"mode": "charts", "feed": "topfreeapplications", "country": "us", "maxResults": 100}'
Enter fullscreen mode Exit fullscreen mode
curl "https://api.apify.com/v2/acts/plum_spear~aztec-appstore-aso/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"mode": "search", "term": "fitness tracker", "country": "us", "maxResults": 50}'
Enter fullscreen mode Exit fullscreen mode

What people actually build with this

  • ASO managers run search mode against target keywords to see who owns them, then lookup mode on a fixed competitor list to track rating, version and update-date shifts week over week.
  • App marketers monitor Top Charts daily to catch a rising competitor before it breaks into the top ranks.
  • Market researchers quantify an entire category in one run - who ranks, how they price, how well they're rated - for sizing studies or reports.
  • Indie developers validate an app idea by checking how saturated a keyword already is and what the top apps charge, before writing a line of code.
  • Growth and data teams use it as a building block: scheduled runs feed a data warehouse or alerting pipeline for continuous market monitoring.

Pricing

Pay-per-event: $0.15 per 1,000 results returned, plus a minimal actor-start event. No subscription, no monthly minimum, no hidden proxy costs - you only pay for the app records you actually get. Apify's free monthly platform credits are enough to try a real query before deciding whether it's worth it.

For context: the raw API calls above are free, so a single keyword check is genuinely fine to do yourself. Once you need one schema across search, lookup and charts, plus scheduled history so a snapshot becomes a trend, $0.15 per 1,000 results is a small price for not building and maintaining that normalization layer yourself.

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 / feed input, no separate integration to write.

Wrap-up

Pulling raw App Store data yourself is genuinely easy - no proxy, no browser, just public JSON endpoints, and the snippets above will get you there for a one-off check. What they won't do on their own is give you one consistent schema across search, lookup and charts, handle Apple's undocumented rate limits gracefully, or turn a snapshot into a trend over time. That gap is what App Store Scraper & ASO Tool on Apify closes: pick a mode, get back the same clean JSON schema every time, priced at $0.15 per 1,000 results with no monthly commitment.


💡 Precisa monitorizar a sua marca nos assistentes de IA?
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)