L logiover
data · Jul 9, 2026 · 7 min read

How to Scrape SteamSpy: Game Sales, Owners & Analytics

Extract Steam ownership estimates, sales data, CCU, ratings and playtime from SteamSpy. How its public API works, its rate limits, and how to pull it in bulk.

Valve doesn’t publish sales numbers, so if you want to know roughly how many people own a Steam game — or benchmark a genre before launch — you end up at SteamSpy, which estimates ownership from Steam achievement and purchase-history modeling. Its API is public and keyless, which is great, but it’s also aggressively rate-limited in ways that will trip a naive scraper: one request per minute for the paginated catalog, one per second for detail calls. This guide covers what SteamSpy exposes, how its modes and rate limits work, and how to pull it in bulk without getting throttled.

What’s worth extracting

The scraper returns one row per game with all 19 analytics fields SteamSpy provides:

  • Identityappid (Steam application ID), name, developer, and publisher.
  • OwnershipownersRange (the raw estimate string, e.g. "2,000,000 .. 5,000,000"), plus ownersMin and ownersMax as parsed numeric bounds.
  • PricingpriceUsd (current price), initialPrice (original before discount), and discount (0–100%).
  • Engagementccu (peak concurrent users, all-time), positive and negative (review counts), and positivePercent (0–100).
  • PlaytimeaveragePlaytimeForever (mean minutes across all players) and medianPlaytime (median minutes).
  • Categorizationgenre (primary genre(s)), languages (supported languages), and tags (comma-separated community tags).

The tags field is the richest categorization signal — it’s community-voted, so it’s more granular than genre alone. And the split between ownersMin/ownersMax matters: SteamSpy never gives an exact owner count, only a range, so the numeric bounds are what you actually model against.

How the SteamSpy API works

The scraper connects directly to the public SteamSpy API at steamspy.com/api.php and supports six modes, each mapping to a different query the API exposes:

  • top100forever — the top 100 games by all-time owners. Exactly 100 rows.
  • top100in2weeks — the top 100 by recent popularity (last two weeks). Exactly 100 rows.
  • all — the full paginated catalog (request=all), ~1,000 games per page, looping until maxResults or an empty page. Tens of thousands of games in one run.
  • byGenre — every game in a genre you name. Typically 50–2,000 depending on how populated the genre is.
  • byTag — every game with a specific community tag. Same variable size as genre.
  • appDetails — on-demand detail lookups for a list of App IDs. Unlimited — as many IDs as you pass.

The two quickest starts need no configuration beyond the mode:

{
  "mode": "top100forever"
}

For category research, name a genre or tag:

{
  "mode": "byGenre",
  "genre": "RPG"
}

And for competitor tracking, feed a curated list of App IDs — you find an ID in a game’s Steam URL, store.steampowered.com/app/<APPID>/:

{
  "mode": "appDetails",
  "appIds": ["730", "570", "440", "271590", "1172470"]
}

The engine uses got-scraping with optional Apify datacenter proxy, and pushes each row directly to the dataset in a flat, numeric-typed schema.

Run the SteamSpy Scraper on Apify — pick a mode (top-100, by genre, by tag, or a list of App IDs) and get ownership estimates, CCU, reviews, pricing and playtime per game. No API key, no login. Export to CSV, JSON or Excel.

The access reality: rate limits are the whole game

SteamSpy has no anti-bot wall, but it enforces documented rate limits that dominate how you plan a run, and the scraper respects them so you don’t get throttled:

  • Paginated catalog (all mode): one request per minute, per page. The actor waits 62 seconds between pages automatically. That’s the big one — with maxResults=5000 (5 pages) expect ~5 minutes; 10,000 games is ~10 minutes. This isn’t slowness in the scraper, it’s SteamSpy’s rule, and ignoring it gets you blocked.
  • Detail calls (appDetails): one request per second. The actor fetches App IDs sequentially with a 1.1-second delay between each.

So the practical strategy is: use the top-100 and genre/tag modes for fast snapshots (they’re single or few requests), and reserve all mode for when you genuinely need broad catalog coverage and can wait. For most market research, top100forever for a snapshot then byGenre for a niche deep-dive gets you there without ever touching the slow path.

Two data-freshness facts worth internalizing: SteamSpy refreshes roughly every 24 hours, so running more than once a day yields identical data — weekly or bi-weekly is enough for trend monitoring. And this is the SteamSpy API, not Valve’s official Steam Web API — the numbers are third-party estimates, reliable for ballpark market research but not officially endorsed by Valve.

Example output

One representative record:

{
  "appid": "1091500",
  "name": "Cyberpunk 2077",
  "developer": "CD PROJEKT RED",
  "publisher": "CD PROJEKT RED",
  "ownersRange": "20,000,000 .. 50,000,000",
  "ownersMin": 20000000,
  "ownersMax": 50000000,
  "priceUsd": 59.99,
  "initialPrice": 59.99,
  "discount": 0,
  "ccu": 238084,
  "positive": 620000,
  "negative": 135000,
  "positivePercent": 82.1,
  "averagePlaytimeForever": 3720,
  "medianPlaytime": 1560,
  "genre": "Action, RPG",
  "tags": "Cyberpunk, Open World, RPG, Action, Story Rich, Sci-fi"
}

Two things to read here. The owner estimate is a range with a 2.5x spread (20M .. 50M) — that width is inherent to the method, so lean on ownersMin for conservative analysis. And playtime is in minutes: averagePlaytimeForever of 3720 is 62 hours, while the median of 1560 (26 hours) is lower — the gap between them is the outlier skew.

Typical use cases

  • Indie market research — pull the all-time top games and analyze which genres dominate by owner count vs. review score before positioning a new title.
  • Genre benchmarking — extract all RPGs or all Roguelikes and compare average price, playtime, and review ratios to set your own.
  • Trend spotting — monitor weekly shifts in top100in2weeks to catch breakout titles and viral indie hits.
  • Competitive intelligence — feed a list of competitor App IDs to appDetails and export a comparison spreadsheet.
  • Investment & publishing — evaluate studios and track competitor performance across a portfolio of titles.
  • Ecosystem research — study genre trends, player-behavior patterns, or retention signals across the Steam catalog.

A useful analyst move from the data: high CCU relative to owner count signals strong retention and an active playerbase — a quality signal you can’t get from owner count alone.

Cost / effort math

Calling SteamSpy yourself is free, but the rate limits are what make a DIY scraper tedious to get right. You have to build a per-mode request scheduler that waits 62 seconds between catalog pages and 1.1 seconds between detail calls — and back off gracefully when SteamSpy throttles anyway — while parsing the owner-range strings into numeric bounds and handling the games with missing genre/language/pricing data. It’s not complex, but it’s exactly the kind of pacing logic that’s easy to get subtly wrong and get blocked for.

The managed actor runs pay-per-result: you pay per game row, a fraction of a cent each, which suits the occasional-pull nature of market research. A top-100 snapshot is 100 rows and costs almost nothing; a broad all pull costs a bit more but is still cheap because you’re billed on output, not on the minutes spent waiting out rate limits. The pacing, pagination, and range-parsing are already handled.

Common pitfalls

  • Owner counts are ranges, never exact. ownersRange is a band and ownersMin/ownersMax can differ 2–3x. Don’t quote a single owner number as fact — model the range, and prefer ownersMin when you need a floor.
  • Playtime is in minutes. Divide by 60 for hours. And prefer medianPlaytime over averagePlaytimeForever — the mean is dragged up by players with tens of thousands of hours.
  • all mode is slow by rule. 62 seconds per page is SteamSpy’s limit, not a bug. Budget ~1 minute per 1,000 games and don’t set an enormous maxResults unless you mean it.
  • Tag names must match exactly. byTag needs the tag string to match SteamSpy’s internal naming (e.g. "Battle Royale", "Pixel Graphics"). A near-miss returns nothing.
  • Missing fields are normal. Niche, very old, or freshly released titles may lack genre, language, or pricing; those fields come back null. Don’t treat null as zero.
  • A near-zero playtime on a paid game is a signal. averagePlaytimeForever near 0 for a paid title often means high refund rates — worth flagging when you evaluate quality.
  • Don’t over-schedule. SteamSpy updates ~once a day; more frequent runs waste credit on identical data.

Wrapping up

SteamSpy is the practical source for Steam ownership and sales estimates, and its API is free and keyless — but the rate limits (62s per catalog page, 1.1s per detail call) are the thing that separates a working bulk pull from a blocked one. Use the fast top-100 and genre/tag modes for snapshots, reserve all for genuine broad coverage, and always read owner counts as ranges and playtime as minutes. If you’d rather not hand-build the pacing logic, the managed actor already respects the limits and parses the numbers for you.

Open the SteamSpy Scraper on Apify — one run returns per-game ownership estimates, CCU, reviews, pricing, and playtime across a mode, genre, tag, or App ID list. No API key, no login, export to CSV / JSON / Excel. Start on Apify’s free monthly credit.

Related guides