# Steam Store Games Scraper (`scrapyx/steam-games-scraper`) Actor

Scrapes the Steam store: search or browse by tag, genre, feature, OS and language. Returns price and discount in your chosen store currency, review score and count, release date, developers, publishers, genres, Metacritic score and achievements.

- **URL**: https://apify.com/scrapyx/steam-games-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Categories:** E-commerce, Games, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.05 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Steam Store Games Scraper

Scrapes the [Steam](https://store.steampowered.com) store catalogue — search by
keyword, or browse by tag, content type, player mode, feature, OS and language.

Returns price and discount **in the store currency you choose**, review score
and count, release date, developers, publishers, genres, Metacritic score,
achievement counts and full descriptions.

***

### What you get

One row per app, plus a `SEARCH_SUMMARY` row per search telling you Steam's own
total, which filters actually resolved, and every honesty flag.

#### From the store search (always)

`appId` · `name` · `storeUrl` · `releasedText` · `priceText` ·
`originalPriceText` · `discountPctText` · `reviewSummary` ("Very Positive") ·
`reviewPositivePct` (87) · `reviewCount` (835,393) · `tagIds` · `platforms`

#### From Valve's appdetails API (`includeAppDetails`, on by default)

`appType` · `isFree` · `priceInitial` / `priceFinal` (integer cents) ·
`priceCurrency` · `priceFormatted` · `discountPercent` · `releaseDate` ·
`comingSoon` · `developers` · `publishers` · `genres` · `metacriticScore` ·
`recommendationsTotal` · `achievementsTotal` · `requiredAge` ·
`shortDescription` · `website` · `supportsWindows` / `supportsMac` /
`supportsLinux` · `screenshotCount` · `movieCount` — plus the complete raw
payload in `appDetails`.

***

### Input

```jsonc
{
  "searchTerms": ["roguelike"],
  "tags": ["RPG", "Souls-like"],     // by NAME, combined with AND
  "contentTypes": ["Games"],
  "country": "us",                    // sets the currency — read below
  "sortBy": "_ASC",
  "maxItems": 100,
  "includeAppDetails": true
}
```

#### Filters are typed by name, not by id

You write `"RPG"`, not `122`. All **430 tags** and **103 languages** Steam
publishes are resolved live at run time, so new tags work without an actor
update.

A name Steam doesn't know is **refused with suggestions**, before any request.
That matters more than it sounds: Steam answers an unknown filter value with
the **complete unfiltered catalogue** (274,412 items) and a healthy HTTP 200.
A scraper that passed your typo straight through would return a successful-
looking run that quietly ignored your filter entirely.

Tags combine with AND — measured: RPG (59,235) + Action (110,871) → **21,818**
for both.

***

### Read this before you trust the prices

**Steam prices in whatever currency the requesting IP resolves to, unless you
tell it otherwise.** Same query, same moment, different exit IP:

| Request | Prices returned |
| --- | --- |
| no region | `Rp 359 999`, `Rp 39 549` |
| `country: "us"` | `$39.99`, `$4.19` |
| `country: "gb"` | `£24.99`, `£3.49` |
| `country: "de"` | `29,99€`, `4,19€` |

This actor **always** sends your chosen region, so a rotating proxy pool can't
change the currency midway through a crawl. An unknown region is refused up
front, because Steam does not reject one — it silently falls back to the IP.

`storeCurrencySeen` on the summary row reports the ISO code Steam actually
priced in, so you can verify rather than assume. (It's `null` when the detail
pass is off — the search page carries only formatted strings like `"$59.99"`,
and putting one of those in a field called "currency" would be worse than
leaving it empty. The raw sample lands in `samplePriceText` instead.)

***

### Known limits

**1. Two sort orders are secretly filters.** Sorting by release date drops
items with no usable date (63,610 → 49,845 on one measured query); sorting by
review score drops everything with no reviews (→ 18,752). When it happens the
summary reports `sortImplicitlyFiltered: true` **and** the unsorted total, so
the missing rows are visible instead of mysterious. An unknown sort is refused,
because Steam silently ignores one.

**2. Steam repeats a few promoted rows across page boundaries** (2 of 50 in one
measurement). The actor de-duplicates and keeps fetching until it has the
number of unique apps you asked for, so `maxItems: 250` returns exactly 250 —
occasionally taking one extra request to get there.

**3. Details cost one request per app.** The `appdetails` endpoint takes a
plural `appids` parameter but a comma-separated list answers HTTP 400. Turn the
detail pass off for fast list-only crawls.

**4. Region-locked and delisted apps** answer `success: false` with HTTP 200.
Those rows keep their search fields and carry `_detailError`, never a silently
blank record.

**5. Pagination is honest** — past the last page Steam returns zero rows rather
than re-serving page 1 — so an unlimited crawl terminates correctly. But a
broad query can be tens of thousands of rows at 100 per request; size
`maxItems` accordingly.

***

### Anti-bot posture

**None.** Steam runs no bot mitigation on these public store endpoints: 5/5 TLS
profiles clean, and several hundred recon requests across both surfaces drew
zero challenges. An unpaced burst of 199 sequential detail calls sustained
2.6 req/s with no rejection at all — considerably more permissive than the
widely repeated "200 requests per 5 minutes" folklore.

The actor still ships a rotating profile pool, a retry ladder and
challenge-marker detection, and paces requests by default. A WAF-free surface
is worth keeping that way.

### Policy

Steam's `robots.txt` is unusually permissive: `User-agent: *` disallows only
`/share/`, `/news/externalpost/`, `/email/`, `/widget/`, `/account/ackgift/`
and some tokenised account URLs. Neither the store search nor `/app/` nor
`api/appdetails` is restricted, and no query parameter is disallowed — so
unlike several other actors in this portfolio, this one uses the site's own
filter query string directly. `appdetails` is Valve's own public API.

# Actor input Schema

## `searchTerms` (type: `array`):

Free-text store searches — one crawl per entry, each with its own SEARCH\_SUMMARY row. Leave empty to browse the whole catalogue filtered only by the fields below (Steam lists ~274,000 items in total).

## `tags` (type: `array`):

Steam tags by name — 'RPG', 'Indie', 'Souls-like', 'Open World'. Multiple tags combine with AND (verified: RPG 59,235 + Action 110,871 → 21,818 for both). All 430 tags Steam publishes are resolved live at run time, so new tags work without an update. A name Steam does not know is REFUSED with suggestions rather than sent — Steam answers an unknown filter with the full unfiltered catalogue and a healthy 200, so an unmatched tag would silently return everything.

## `contentTypes` (type: `array`):

Restrict to certain store item types by name: 'Games', 'Software', 'Downloadable Content', 'Demos', 'Soundtracks', 'Videos', 'Mods', 'Hardware', 'Playtests'. Leave empty for everything.

## `playerModes` (type: `array`):

Filter by number-of-players facets by name: 'Single-player', 'Multi-player', 'Co-op', 'PvP', 'Online PvP', 'LAN Co-op' and similar.

## `features` (type: `array`):

Filter by Steam feature facets by name: 'Steam Achievements', 'Steam Trading Cards', 'Steam Cloud', 'Full controller support' and similar.

## `operatingSystems` (type: `array`):

'Windows', 'macOS' or 'SteamOS + Linux'.

## `languages` (type: `array`):

Restrict to titles supporting a language, by name — 'English', 'Japanese', 'Simplified Chinese'. All 103 languages Steam publishes are resolved live.

## `country` (type: `string`):

Which Steam store region to price against. THIS MATTERS: without it Steam prices in whatever currency the exit IP resolves to, so a rotating proxy pool can change the currency midway through a crawl. Steam does not reject an unknown region — it silently falls back to the IP — so only verified regions are offered here.

## `sortBy` (type: `string`):

Careful — two of these are implicit FILTERS, not just orderings. 'Release date' drops items with no usable date (63,610 → 49,845 on one measured query) and 'User review score' drops items with no reviews (→ 18,752). When that happens the summary row reports sortImplicitlyFiltered:true along with the unsorted total, so you can see what was excluded. An unknown sort is refused, because Steam silently ignores one.

## `onlyDiscounted` (type: `boolean`):

Restrict to items currently on sale (Steam's 'Discounts & Events' facet — 8,423 items when measured).

## `hideFreeToPlay` (type: `boolean`):

Exclude free-to-play titles.

## `maxItems` (type: `integer`):

Stop after this many apps per search term. Set to 0 for unlimited — Steam's pagination is genuinely honest here (past the last page it returns zero rows rather than re-serving page 1), so an unlimited crawl terminates correctly, but a broad query can mean tens of thousands of rows at 100 per request.

## `includeAppDetails` (type: `boolean`):

Fetch each app from Valve's public appdetails API — developers, publishers, genres, Metacritic score, achievement count, exact integer prices, descriptions, platform support and screenshot/movie counts. Costs one extra request per app (the endpoint does not accept batches). Turn it off for a fast list-only crawl; search rows still carry name, price, release text and the review score.

## `language` (type: `string`):

Language for descriptions and localised fields returned by the details API, e.g. 'english', 'german', 'japanese'.

## `maxConcurrency` (type: `integer`):

How many requests may be in flight at once. This absorbs latency variance; it is NOT the throttle — the crawl rate is set by 'Minimum interval between requests' below.

## `minRequestInterval` (type: `number`):

The actual speed control: the shortest gap between two requests to Steam, across all workers. An unpaced burst of 199 detail calls sustained 2.6 req/s with no rejection, but Steam serves no bot challenge at all and that is worth preserving. Values below 0.05s are clamped.

## `proxyConfiguration` (type: `object`):

Steam runs no bot mitigation on these public store endpoints. Residential with NO country pin is the cloud default: the store region comes from the 'Store region' field above, so the exit IP does not affect prices, and an unpinned pool keeps rotation wide.

## Actor input object example

```json
{
  "searchTerms": [
    "roguelike"
  ],
  "tags": [],
  "contentTypes": [],
  "playerModes": [],
  "features": [],
  "operatingSystems": [],
  "languages": [],
  "country": "us",
  "sortBy": "_ASC",
  "onlyDiscounted": false,
  "hideFreeToPlay": false,
  "maxItems": 100,
  "includeAppDetails": true,
  "language": "english",
  "maxConcurrency": 5,
  "minRequestInterval": 0.3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchTerms": [
        "roguelike"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/steam-games-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "searchTerms": ["roguelike"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/steam-games-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchTerms": [
    "roguelike"
  ]
}' |
apify call scrapyx/steam-games-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/steam-games-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/CE2MxOfVS44NhTiOn/builds/Ij8dJGe8esVqHJu2k/openapi.json
