Steam Reviews Scraper
Pricing
from $1.26 / 1,000 results
Steam Reviews Scraper
User reviews for any Steam game, with the playtime, purchase source and vote counts Steam attaches to each. Never stops on a short page - Steam serves 99-row pages mid-stream, and a walker that treats one as the end returns 1,499 of 5,236,483 reviews and reports success.
Pricing
from $1.26 / 1,000 results
Rating
0.0
(0)
Developer
Ibnu Adzim
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Every user review for any Steam game, with the playtime, purchase source,
language and vote counts Steam attaches to each one. Straight from
store.steampowered.com/appreviews — no login, no API key, no browser.
The thing that makes this different: it doesn't stop early
Steam serves short pages in the middle of the stream. Walking Counter-Strike 2 with 100 reviews per request:
page 15 -> 99 reviews, and ALL 99 WERE NEWpage 29 -> 99 reviews, and ALL 99 WERE NEW... the stream kept going well past page 30
The obvious stop condition — "fewer rows came back than I asked for, so that was the last page" — is wrong here, and it fails silently and catastrophically: a scraper using it stops at page 15 with 1,499 reviews of the 5,236,483 the same response says exist, and reports success. The first draft of this actor's own recon walker did exactly that.
This actor never stops on a short page. It counts them (shortPagesSeen) and
keeps going, stopping only on an empty page, a cursor that stops advancing, a
page with no new IDs, or your own maxReviewsPerApp. Which one ended the run
is reported in stopReason.
Steam's own default hides 46.7% of the reviews
purchase_type defaults to steam upstream, which excludes keys activated
outside Steam. Measured on CS2 in one minute:
| purchase_type | total_reviews |
|---|---|
| (omitted — Steam's default) | 5,236,483 |
steam | 5,236,483 |
non_steam_purchase | 4,580,072 |
all | 9,816,519 |
So "all the reviews for CS2" is 53.3% of them unless you say otherwise, and
nothing in the response says so. This actor defaults to all, and every
summary also reports steamDefaultPurchaseTypeTotal so the gap is a number in
your data rather than a footnote.
(The parts don't sum to the whole either: 5,236,483 + 4,580,072 = 9,816,555
against all's 9,816,519 — upstream's arithmetic disagrees with itself by 36.)
Other traps it handles
query_summaryis complete on the first page only. Later pages return{num_reviews}and nothing else, so totals read off page 5 are null. Captured once, from page one, and kept.- An unknown filter value doesn't error — it silently answers a different
question.
filter=bogusreturned the same cursor asfilter=all, byte for byte;review_type=bogusreturnedall's totals;purchase_type=bogusfell back tosteam. Every enum is validated here before the request goes out. - Filtering to positive or negative destroys the totals.
review_type=positive|negativemakes upstream stop reportingtotal_reviews,total_positiveandtotal_negativeentirely. The rows still come; the denominator doesn't. Reported astotalsUnavailableReasonrather than printed as a zero. - An omitted language means English, not everything — 1,498,594 of CS2's
5,236,483. Defaults to
allhere. An unknown language honestly returns zero. - Off-topic ("review bombing") reviews are excluded by default, and on most
games you can't tell: Cyberpunk 2077 measured 878,232 with the filter on
against 884,470 with it off, while CS2, Valheim and Overwatch 2 were
identical. Turn on
reportOfftopicGapto get the hidden count as a number. day_rangeis deliberately not offered. It filters the rows but leavestotal_reviewsat the unfiltered figure, so a run reads as "5.2M reviews in the last 30 days". The explicitdateFrom/dateTowindow moves both.- HTTP 200 carries no information. Every outcome is a 200; the verdict is
in the body's
successfield.success: 2means Steam couldn't parse the request (App ID0or non-numeric). And a nonexistent App ID is indistinguishable from a real game with no reviews yet — both returnsuccess: 1withtotal_reviews: 0.
Output
One dataset, recordType tells the rows apart:
SEARCH_SUMMARY— one per game: upstream's totals and score, pages fetched, short pages survived, stop reason, reachable fraction, what Steam's default would have said, and the off-topic gap.REVIEW— one per review, with upstream's object passed through verbatim alongside normalised fields. Playtime is kept as two figures —playtimeAtReviewMinutesandplaytimeForeverMinutes— because "how long had they played when they wrote this" and "how long have they played" are different questions, and collapsing them loses the only signal that says whether a review came after 2 hours or 2,000.ERROR— one per input that failed, so every input maps to at least one row.
Every row carries the filter context it was produced under (purchaseTypeApplied,
languageApplied, filterApplied, …) — without it a row isn't interpretable,
since the same game yields a different corpus per setting.
Modes
reviews— walk the full stream for each game.summary— one request per game for score and totals only. A cheap way to compare hundreds of games' ratings without pulling their reviews.
Anti-bot and limits
No WAF. Seven TLS profiles (chrome124, chrome131, chrome136, safari17_0, edge101, firefox133, chrome99_android) returned 200 cold with byte-identical 17,977-byte bodies. A proxy is optional and off by default.
num_per_page above 100 is silently clamped by Steam with no error, so it is
capped in code. A 5,000-page backstop per game (~500k reviews) stops an
uncapped run on a 9.8-million-review title from paging forever; it is reported
as pageCapHit if reached.
Policy
store.steampowered.com/robots.txt places no restriction on /appreviews/
and names no AI crawler. Note that api.steampowered.com is Disallow: /
— the Web API host is closed, which is worth knowing before anyone reaches for
GetAppList to enumerate App IDs. This actor does not touch it.
Steam reviews are user-submitted content; check Valve's terms before redistributing them.