Steam Scraper - Game Prices, Reviews & Player Counts avatar

Steam Scraper - Game Prices, Reviews & Player Counts

Pricing

from $4.00 / 1,000 game scrapes

Go to Apify Store
Steam Scraper - Game Prices, Reviews & Player Counts

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

Eimantas V

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

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 86
Counter-Strike 2 free 86% of 9,800,649 582,930 playing
Dota 2 free 81% of 2,760,382 433,500 playing
Cyberpunk 2077 on five storefronts
us USD 59.99 gb GBP 49.99 de EUR 59.99
br BRL 199.90 tr USD 44.99 <- cheapest of the two USD storefronts
360 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:

StorefrontCurrencyPrice
United StatesUSD59.99
United KingdomGBP49.99
GermanyEUR59.99
BrazilBRL199.90
TurkeyUSD44.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 gamename, 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 reviewreview, 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, initialPrice and onSale across 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.

OptionDefaultNotes
appIdsThe number or the store URL. store.steampowered.com/app/730/ is 730
searchTermsSteam's own store search; returns ids, which are then read in full
maxSearchResults50Steam's search caps at 100 per term
countries["us"]Two-letter codes. The first is the home storefront and fills the top-level price
includePlayerCounttrueOne extra call per game, about 65 ms
includeReviewsfalseOff by default, because reviews are most of the payload
maxReviewsPerApp200100 per page, so 120 costs two pages
reviewLanguageallSteam's own names: english, schinese, brazilian
reviewFilterrecentrecent, updated or all
genres / keywordsSubstring, case-insensitive. Keywords also search developer and publisher
freeOnly / onSaleOnlyfalse
minPrice / maxPriceHome storefront. A floor drops unpriced games; a ceiling keeps them
minMetacriticUnscored games are dropped by this, because no score is not a low score
minPositiveRate0.8 and 80 both mean 80 per cent
releasedWithinDaysUnreleased games are kept only where Steam says "coming soon"
includeDescriptionfalseTakes a game from 16 KB to 35 KB on the wire
flattenOutputfalseDot-notation columns for CSV
maxConcurrency3Up 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. currentPlayers is concurrent players right now, which is a real measurement and not a stand-in for units sold.
  • No currency conversion, anywhere. cheapestCountry and dearestCountry are null unless at least two storefronts quote the same currency, and they rank only those. Everything else sits in regionalPrices for 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. totalReviews and positiveRate describe the whole population, maxReviewsPerApp describes your slice, and the two should not be read as the same thing.
  • Reviews are ordered, not sampled at random. reviewFilter changes 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. minMetacritic therefore 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: 8 will 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

EventPriceWhen
Actor start$0.005Once per run
Game scraped$0.004Per game returned after filtering, including every storefront price, the player count and the review rollup
Review scraped$0.0004Per 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 currentPlayers and totalReviews. A review count climbing faster than usual is a launch, a sale or a controversy, and the reviews themselves tell you which.
  • Leave includeReviews off when you only want prices. Reviews are most of the payload and most of the bill.
  • Sort reviews on playtimeAtReviewMinutes before 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 fills price and currency, so the top-level fields are the ones you care about and the rest sit in regionalPrices.
  • Use searchTerms to discover and appIds to monitor. Search once to find the competitors, then pin the ids so tomorrow's run reads the same games.
  • onSaleOnly: true across a watchlist is a discount alert in one field, with initialPrice beside it so you can see how deep it goes.