Steam Scraper - Game Prices, Reviews & Player Counts
Pricing
from $4.00 / 1,000 game scrapes
Steam Scraper - Game Prices, Reviews & Player Counts
Any Steam game by app id or search term: price, discount, genres, developers, Metacritic, Steam's review rollup and the live player count. The same game priced on every storefront you list, each in its own currency. Individual reviews carry the playtime behind the verdict.
Pricing
from $4.00 / 1,000 game scrapes
Rating
0.0
(0)
Developer
Eimantas V
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Steam Game, Price & Review Scraper
Full store metadata for any Steam game, the same game priced on every storefront you name, live player counts, and reviews carrying how long the author had played when they wrote it.
Cyberpunk 2077 $59.99 87% of 973,115 reviews 31,294 playing Metacritic 86Counter-Strike 2 free 86% of 9,800,649 582,930 playingDota 2 free 81% of 2,760,382 433,500 playingCyberpunk 2077 on five storefrontsus USD 59.99 gb GBP 49.99 de EUR 59.99br BRL 199.90 tr USD 44.99 <- cheapest of the two USD storefronts360 reviews median playtime at review 5,679 minutes (95 hours) 5 written under an hour
3 games and 360 reviews in 4.6 seconds, over 24 requests and 1.0 MB — three app ids, five storefronts, 120 reviews each. No API key, no login, no browser. These are the storefront endpoints Steam serves to its own web pages.
The same game, priced in five countries at once
Steam charges what each region will pay, and the gaps are not small. One game, one run:
| Storefront | Currency | Price |
|---|---|---|
| United States | USD | 59.99 |
| United Kingdom | GBP | 49.99 |
| Germany | EUR | 59.99 |
| Brazil | BRL | 199.90 |
| Turkey | USD | 44.99 |
Name the countries in countries and every one is read. The first is your home storefront and fills price, currency and discountPercent; the rest fill regionalPrices, each in its own currency.
No rate is ever applied. cheapestCountry and dearestCountry compare only storefronts quoting the same currency, and stay null when fewer than two of them do. The reason is in the table above: ranked by raw number, 199.90 BRL is the biggest figure on the page, so Brazil reads as the dearest storefront when it is about 37 dollars and genuinely the cheapest. Converting for you would mean picking a rate, and the rate would be wrong by the time you read it. You get every price, in the currency Steam quoted it, and the comparison is yours.
A review is worth what the reviewer played
Every review carries playtimeAtReviewMinutes: minutes on the clock at the moment it was written, not today. Across 360 reviews in one run the median was 5,679 minutes, about 95 hours, and 5 were written with under an hour played.
That single field separates a verdict from a first impression, and nothing on the store page shows it to you. Sort on it and the reviews that survive are from people who finished the game. Filter the other way and you have the refund-window complaints, which is its own signal when you are watching a launch.
Beside it: playtimeForeverMinutes for what they have played since, weightedVoteScore (Steam's own usefulness weighting, the thing that orders the store page), steamPurchase, receivedForFree, refunded, writtenDuringEarlyAccess, onSteamDeck, and the author's library size and review count.
What you get
Per game — name, type, isFree, developers, publishers, genres, categories, releaseDate + daysSinceRelease + comingSoon, reviewScoreDesc, totalReviews / totalPositive / totalNegative / positiveRate, metacriticScore, currentPlayers, currency / price / initialPrice / discountPercent / onSale, regionalPrices + cheapestCountry / dearestCountry, platforms, achievements, dlcCount, requiredAge, controllerSupport, headerImage, website, storeUrl, shortDescription, and the full description on request.
Per review — review, language, votedUp, votesUp, votesFunny, weightedVoteScore, commentCount, playtimeAtReviewMinutes and playtimeForeverMinutes, steamPurchase, receivedForFree, refunded, writtenDuringEarlyAccess, onSteamDeck, createdAt / updatedAt, authorSteamId, authorGamesOwned, authorReviewCount.
An app id Steam has no store page for gets its own row with recordType: "app-error" and an errorCode, so a gap in your list is visible rather than silent.
Who this is for
- Game developers watching competitors. Player counts, review velocity and discount behaviour on the games you are shipping against, run daily and diffed.
- Publishers setting regional prices. What your catalogue and everyone else's actually costs in each territory, in the local currency, without a spreadsheet of guessed conversions.
- Deal sites and price trackers.
discountPercent,initialPriceandonSaleacross every storefront you cover, on a schedule. - Market research and investment. Genre mix, Metacritic, positive rate, concurrent players and review sentiment, joined on one row per game.
- Anyone doing review analysis. 360 reviews with playtime attached beats 360 star ratings.
Input
{"appIds": ["1091500", "730", "https://store.steampowered.com/app/570/Dota_2/"],"countries": ["us", "gb", "de", "br", "tr"],"includeReviews": true,"maxReviewsPerApp": 120,"includePlayerCount": true}
That is the run at the top of this page: 3 games, 360 reviews, 5 storefronts, 4.6 seconds.
| Option | Default | Notes |
|---|---|---|
appIds | — | The number or the store URL. store.steampowered.com/app/730/ is 730 |
searchTerms | — | Steam's own store search; returns ids, which are then read in full |
maxSearchResults | 50 | Steam's search caps at 100 per term |
countries | ["us"] | Two-letter codes. The first is the home storefront and fills the top-level price |
includePlayerCount | true | One extra call per game, about 65 ms |
includeReviews | false | Off by default, because reviews are most of the payload |
maxReviewsPerApp | 200 | 100 per page, so 120 costs two pages |
reviewLanguage | all | Steam's own names: english, schinese, brazilian |
reviewFilter | recent | recent, updated or all |
genres / keywords | — | Substring, case-insensitive. Keywords also search developer and publisher |
freeOnly / onSaleOnly | false | |
minPrice / maxPrice | — | Home storefront. A floor drops unpriced games; a ceiling keeps them |
minMetacritic | — | Unscored games are dropped by this, because no score is not a low score |
minPositiveRate | — | 0.8 and 80 both mean 80 per cent |
releasedWithinDays | — | Unreleased games are kept only where Steam says "coming soon" |
includeDescription | false | Takes a game from 16 KB to 35 KB on the wire |
flattenOutput | false | Dot-notation columns for CSV |
maxConcurrency | 3 | Up to 8. See the rate limit note below |
Eight things worth knowing
Each was found by running against live Steam.
appdetails takes one app per request, and every batched form returns HTTP 400. Passing several ids is widely documented and does not work: appids=730,570 answers 400 with a country, without a country, with filters and without them. Confirmed again while writing this page. So there is no bulk query, one request per game is the floor, and every filter in this Actor runs after the fetch rather than inside a query that does not exist.
The basic filter does not include developers or publishers. Trimming the payload to the fields the record uses looked free and was not: basic is not the basics, and both fields came back empty on every game, with no error and no warning to say so. They are named explicitly now, along with price, Metacritic, genres, categories, platforms, achievements, release date and recommendations. Screenshots, movies and backgrounds stay out, which is what takes a game from 35 KB on the wire down to 16 KB.
Prices are never converted between currencies, so cheapest and dearest only compare within one. Ranking 44.99 USD against 199.90 BRL by raw number called Brazil the dearest storefront when it is about 37 dollars and actually the cheapest of the five. It is the same fault as taking a median across pounds and lei. Only storefronts sharing the home currency are ranked, and with fewer than two of them both fields stay null. In the five-country run above that left the United States against Turkey, which is how cheapestCountry came back as tr.
A storefront can quote a currency that is not its own. Turkey returns USD 44.99, not lira. That is Valve's pricing rather than a parsing slip, and it is the reason the comparison found anything at all in a run where three of the five countries quoted three different currencies. Read regionalPrices[].currency instead of assuming the country implies it.
Free games carry no price_overview at all, so price is null and not zero. Counter-Strike 2 and Dota 2 both come back with the field absent entirely. Writing zero would be an invention that reads as "this game costs nothing today", which is also what a 100 per cent discount looks like. isFree answers the question price: 0 was pretending to.
Steam repeats its review cursor at the end of the list. The last page hands back the cursor you sent it, so the obvious loop asks for the same 100 reviews until it reaches your cap and then reports a full set of duplicates as a result. The loop stops when the cursor repeats, when a page is empty, or when a page contains nothing new, and review ids are deduplicated regardless.
A delisted or non-existent app id answers success: false under HTTP 200. App 999999999 returns a perfectly healthy response containing a refusal. It becomes an app-error row with errorCode: "NOT_FOUND" rather than an empty game record, because an empty game and a game that is not there are different answers and only one of them means your input was wrong.
Steam writes release dates for humans. "Dec 9, 2020" is a date; "Q1 2025", "Coming soon" and a bare year are not, and Date.parse turns the last three into 1 January, which is a fabrication with a timestamp on it. Only strings carrying an actual day become releaseDate. Everything else stays null, and comingSoon carries what Steam actually said.
Output
{"recordType": "game","appId": "1091500","name": "Cyberpunk 2077","developers": ["CD PROJEKT RED"],"genres": ["RPG"],"releaseDate": "2020-12-09T00:00:00.000Z","daysSinceRelease": 2079,"reviewScoreDesc": "Very Positive","totalReviews": 973115,"positiveRate": 0.8681,"metacriticScore": 86,"currentPlayers": 31294,"currency": "USD", "price": 59.99, "discountPercent": 0, "onSale": false,"regionalPrices": [{ "country": "us", "currency": "USD", "price": 59.99 },{ "country": "gb", "currency": "GBP", "price": 49.99 },{ "country": "de", "currency": "EUR", "price": 59.99 },{ "country": "br", "currency": "BRL", "price": 199.90 },{ "country": "tr", "currency": "USD", "price": 44.99 }],"cheapestCountry": "tr","dearestCountry": "us"}
{"recordType": "review","appId": "570","gameName": "Dota 2","reviewId": "233175787","language": "russian","votedUp": false,"weightedVoteScore": 0.5,"steamPurchase": true,"refunded": false,"playtimeAtReviewMinutes": 12,"playtimeForeverMinutes": 12,"authorGamesOwned": 1430,"createdAt": "2026-08-19T06:07:52.000Z"}
Set flattenOutput: true for CSV: arrays become pipe-delimited strings, regionalPrices becomes a count plus a JSON column.
Honest limits
- Steam only. No Epic, GOG, itch or console storefronts. A price comparison here is across Steam's own regions, not across shops.
- There is no "every game on Steam" query. You supply ids or search terms, and a search term returns at most 100 ids. A run is as long as its list.
- Steam publishes no sales, revenue or owner counts, and this Actor does not estimate them.
currentPlayersis concurrent players right now, which is a real measurement and not a stand-in for units sold. - No currency conversion, anywhere.
cheapestCountryanddearestCountryare null unless at least two storefronts quote the same currency, and they rank only those. Everything else sits inregionalPricesfor you to convert with a rate you trust. - A storefront that does not sell a game answers successfully with no price. That is recorded as a null price for that country rather than dropped, so a gap in availability stays visible.
- The rollup is Steam's, over every review the game has; the reviews you receive are a sample, most recent first by default.
totalReviewsandpositiveRatedescribe the whole population,maxReviewsPerAppdescribes your slice, and the two should not be read as the same thing. - Reviews are ordered, not sampled at random.
reviewFilterchanges which end of the list you get. It does not remove the bias, it chooses it. - Metacritic is absent on plenty of good games. Counter-Strike 2 has no score at all.
minMetacritictherefore drops unscored games, which is deliberate and the reason it is off by default. - Player counts are not published for every app. Null means Steam reported none, not that nobody is playing.
- Only real dates become
releaseDate. A quarter or a bare year stays null rather than becoming 1 January. - Steam is documented at roughly 200 requests per five minutes. Twenty-five sequential calls produced zero failures, but a long run at
maxConcurrency: 8will find the ceiling. The Actor keeps a 250 ms gap between calls for one app and reports an HTTP 429 plainly if one arrives. - Games your filters removed, and app ids Steam has no page for, are never charged.
Pricing
| Event | Price | When |
|---|---|---|
| Actor start | $0.005 | Once per run |
| Game scraped | $0.004 | Per game returned after filtering, including every storefront price, the player count and the review rollup |
| Review scraped | $0.0004 | Per review returned |
$4 per 1,000 games and $0.40 per 1,000 reviews. The run at the top of this page, 3 games with 120 reviews each, costs $0.161. A daily watch on 20 competitor titles across five storefronts with reviews off is $0.085 a run. Full working in docs/PRICING.md.
Tips
- Run daily and diff on
currentPlayersandtotalReviews. A review count climbing faster than usual is a launch, a sale or a controversy, and the reviews themselves tell you which. - Leave
includeReviewsoff when you only want prices. Reviews are most of the payload and most of the bill. - Sort reviews on
playtimeAtReviewMinutesbefore reading any of them. The median in one run was 95 hours, so the half below it includes people reviewing on a demo's worth of play. - Put your own market first in
countries. The first entry fillspriceandcurrency, so the top-level fields are the ones you care about and the rest sit inregionalPrices. - Use
searchTermsto discover andappIdsto monitor. Search once to find the competitors, then pin the ids so tomorrow's run reads the same games. onSaleOnly: trueacross a watchlist is a discount alert in one field, withinitialPricebeside it so you can see how deep it goes.