# Steam Game Reviews Scraper (`zerahdata/steam-reviews-scraper`) Actor

Fetch user reviews for any Steam game from Steam's public official store review API, with filtering, sorting, and date range support.

- **URL**: https://apify.com/zerahdata/steam-reviews-scraper.md
- **Developed by:** [Zerah Data](https://apify.com/zerahdata) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 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.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Steam Game Reviews Scraper

Fetch user reviews for any public Steam game - review text, recommendation status,
helpfulness votes, playtime, Steam Deck and refund flags, and more - directly from
Steam's current official reviews API, plus rule-based per-game insights and optional
change tracking between runs.

**Updated for Steam's new official reviews API (October 2026).** Steam retired the
old `store.steampowered.com/appreviews` endpoint on October 22, 2026 - it now
redirects to the store homepage instead of returning data. This actor calls Steam's
replacement, `IUserReviewsService/GetAppReviews`, directly.

### What it does

Give it one or more Steam games (store URLs, app IDs, or plain game names) and it will:

- Resolve each game from a full store URL (e.g.
  `https://store.steampowered.com/app/730/CounterStrike_2/`), a plain app ID, or a
  free-text name (e.g. `Stardew Valley`) via Steam's public store search.
- Look up the game's name via Steam's public app details endpoint (best-effort).
- Page through that game's reviews using cursor-based pagination.
- Filter by language (one, several, or all), recommendation (positive/negative),
  purchase type, minimum/maximum playtime at review, Steam Deck play, and whether to
  include off-topic "review bomb" activity.
- Sort by most recent, last updated, or most helpful.
- Restrict results to a date range - applied by Steam's API itself, so it works with
  any sort order and never fetches or charges for reviews outside your window.
- Optionally add one summary row per game with Steam's overall review score, totals,
  and how many reviews matched your filters.
- Optionally add one rule-based insights row per game (see below).
- Optionally track changes between runs, so a scheduled run only fetches reviews
  posted since the last time it ran.
- Clearly report games that can't be found, or names that don't resolve, instead of
  silently skipping them.
- Write a self-check report at the end of every run.

No personal reviewer identity (Steam ID, profile URL) is collected or output - only
the review content and the aggregate author stats Steam exposes publicly (playtime,
number of reviews written).

### Why this actor

- **Built on Steam's current API, not a deprecated one.** Many Steam-review tools
  were built against the old `store.steampowered.com/appreviews` endpoint, which
  Steam turned off in October 2026. This actor speaks the replacement API, including
  its new filters (playtime range, Steam Deck, server-side date range that works with
  any sort order, off-topic/review-bomb toggle).
- **Analysis, not just raw rows.** An optional insights row per game gives you
  positive-rate trends, keyword counts for positive vs. negative reviews, playtime
  patterns, language mix, and refund/early-access share - computed locally from the
  reviews already fetched, with no extra Steam API calls, using transparent, deterministic rules.
- **Cheap, transparent monitoring.** Turn on change tracking and a scheduled run only
  pays for and fetches reviews posted since the previous run - not the whole history
  again.
- **A self-check on every run.** A `RUN_REPORT` record in the key-value store tells
  you exactly what happened: games processed and resolved, rows pushed by type,
  errors by code, warnings (e.g. a search term matching nothing), and whether any
  output row was missing a required field.
- **Resilient to partial failures.** One bad app ID or an unresolved game name never
  aborts the whole run - you get a clear error row for that entry and full results for
  the rest.
- **No reviewer identity, ever.** Steam IDs and profile links are never collected or
  output, by design - not configurable, not an opt-in.

### How to use

1. Add one or more games to **Games** (app ID or store URL) and/or **Search by game
   name** (free text, resolved automatically). Any of these formats work:
   - `730`
   - `https://store.steampowered.com/app/730/CounterStrike_2/`
   - `Counter-Strike 2` (in **Search by game name**)
2. Set **Max reviews per game** to cap how many reviews are fetched per game. Leave at
   `0` to fetch all available reviews.
3. (Optional) Choose **Sort by**, **Languages**, **Review type**, and **Purchase
   type** to narrow down the results. Add **Minimum/maximum playtime**, **Steam Deck
   only**, or **Include off-topic review bombs** for finer control.
4. (Optional) Set **Date from** / **Date to** to restrict reviews to a date range -
   this works with any sort order.
5. (Optional) Leave **Include game summary row** and **Include game insights row**
   on (default) to get per-game aggregate rows alongside the raw reviews.
6. (Optional) Enable **Track changes between runs** and set a **Monitor name** to
   fetch only new reviews on subsequent runs with the same name.
7. (Optional) Turn on a proxy under **Proxy configuration** if you're fetching very
   large volumes across many games - Steam's review API itself has no anti-bot
   protection, so this is rarely required.
8. Run the actor.

#### Example input

```json
{
  "games": ["730"],
  "gameNames": ["Stardew Valley"],
  "maxReviewsPerGame": 100,
  "sort": "recent",
  "languages": ["all"],
  "reviewType": "all",
  "purchaseType": "all",
  "includeGameSummary": true,
  "includeInsights": true
}
```

### Output example

Review row:

```json
{
  "type": "review",
  "appId": "730",
  "gameName": "Counter-Strike 2",
  "reviewId": "123456789",
  "language": "english",
  "reviewText": "Great game, runs well and the new engine is a big upgrade.",
  "votedUp": true,
  "votesUp": 12,
  "votesFunny": 0,
  "weightedVoteScore": 0.52,
  "commentCount": 0,
  "steamPurchase": true,
  "receivedForFree": false,
  "writtenDuringEarlyAccess": false,
  "refunded": false,
  "primarilySteamDeck": false,
  "developerResponse": null,
  "developerRespondedAt": null,
  "createdAt": "2026-03-15T10:22:31.000Z",
  "updatedAt": "2026-03-15T10:22:31.000Z",
  "authorPlaytimeForeverHours": 245.3,
  "authorPlaytimeAtReviewHours": 180.1,
  "authorPlaytimeLastTwoWeeksHours": 6.5,
  "authorLastPlayedAt": "2026-03-20T08:00:00.000Z",
  "authorNumReviews": 8,
  "scrapedAt": "2026-01-01T12:00:00.000Z"
}
```

Game summary row (when **Include game summary row** is enabled):

```json
{
  "type": "gameSummary",
  "appId": "730",
  "gameName": "Counter-Strike 2",
  "reviewScoreDesc": "Very Positive",
  "totalPositive": 1500000,
  "totalNegative": 150000,
  "totalReviews": 1650000,
  "totalMatching": 1650000,
  "scrapedAt": "2026-01-01T12:00:00.000Z"
}
```

Game insights row (when **Include game insights row** is enabled) - computed from
the reviews fetched in this run, not Steam's full history:

```json
{
  "type": "gameInsights",
  "appId": "730",
  "gameName": "Counter-Strike 2",
  "reviewsAnalyzed": 500,
  "positivePercentOverall": 82.4,
  "positivePercentLast30Days": 76.1,
  "monthlyTrend": [
    { "month": "2026-01", "totalReviews": 120, "positivePercent": 85.0 }
  ],
  "topPositiveKeywordCounts": [{ "keyword": "fun", "count": 42 }],
  "topNegativeKeywordCounts": [{ "keyword": "cheaters", "count": 18 }],
  "shareUnder2HoursPlaytimePercent": 9.2,
  "avgPlaytimeAtReviewPositiveHours": 310.5,
  "avgPlaytimeAtReviewNegativeHours": 95.2,
  "languageBreakdown": [{ "language": "english", "count": 210 }],
  "earlyAccessSharePercent": 0,
  "refundSharePercent": 1.2,
  "freeKeySharePercent": 3.4,
  "scrapedAt": "2026-01-01T12:00:00.000Z"
}
```

Monitoring status row (when **Track changes between runs** is enabled):

```json
{
  "type": "monitorStatus",
  "appId": "730",
  "gameName": "Counter-Strike 2",
  "monitorName": "my-cs2-monitor",
  "isFirstRun": false,
  "newReviewsCount": 14,
  "previousLastSeenAt": "2026-01-05T00:00:00.000Z",
  "newLastSeenAt": "2026-01-06T00:00:00.000Z",
  "scrapedAt": "2026-01-06T01:00:00.000Z"
}
```

If a game can't be found, a name doesn't resolve, or an input entry is invalid,
you'll get a clear error row instead of a silent gap in your data:

```json
{
  "type": "error",
  "input": "999999999",
  "error": "GAME_NOT_FOUND",
  "message": "App 999999999 was not found (appdetails failed and no reviews were returned)",
  "scrapedAt": "2026-01-01T12:00:00.000Z"
}
```

#### Run report

Every run writes a `RUN_REPORT` record to the default key-value store (and
summarizes it in the run's status message): games processed, rows pushed by type,
errors by code, warnings (e.g. a search term that matched nothing, or a game with
zero reviews for the selected filters), and whether any output row was found missing
a required field.

### Tips

- **Most helpful sort**: pages a limited top-helpful set within a day-range window
  rather than full history - use "Most recent" or "Last updated" to page through a
  game's complete review history.
- **Date range works with any sort**: unlike the old retired endpoint, Steam's
  current API applies date filtering server-side regardless of sort order.
- **Language codes**: use Steam's API language codes (e.g. `english`, `german`,
  `schinese`, `brazilian`) or `all` for every language. **Languages** accepts several
  at once.
- **Change tracking**: use the same **Monitor name** across scheduled runs to keep
  monitoring the same games cheaply; use a new name to start a fresh monitor.

### Pricing

This actor uses **pay-per-result** pricing: **$0.50 per 1,000 results**, with no
start fee. A result is one review row, one game summary row, or one game insights
row. Error rows and monitoring status rows are never charged. You can cap spending
with the run's maximum cost setting, and the actor stops fetching further reviews as
soon as that limit is reached.

### FAQ

**Is this legal?** The actor only reads data that Steam already publishes publicly
through its own official reviews API, store search API, and app details API - the
same data shown on every game's public review page. It does not access private data,
log in, or bypass any access controls.

**Does it collect reviewer identities?** No. Steam IDs and profile links are never
collected or output - only the review content and the aggregate author stats Steam
exposes publicly (playtime, review count).

**What happens if a game ID is invalid or a name doesn't resolve?** You'll get an
error row (`GAME_NOT_FOUND`, `SEARCH_NO_MATCH`, or `INVALID_INPUT`) for that entry
instead of a failed run - all other games in the same run are unaffected.

**How are the insights calculated?** Keyword counts are simple stopword-filtered
word/bigram frequency counts over English-language reviews; every other insight
field is a straightforward aggregate (percentages, averages, breakdowns) computed
from the reviews already fetched in the run.

**What are the limits?** Steam's review API only returns reviews it has indexed for
the selected filters; very old or heavily filtered combinations (e.g. a rare
language with positive-only reviews) may return fewer results than the total review
count suggests. Insights are computed from the reviews fetched in the current run
(capped for performance on very large pulls), not from a game's entire review
history, unless **Max reviews per game** is set to `0`.

# Actor input Schema

## `games` (type: `array`):

Steam games to fetch reviews for. Enter a Steam store URL (e.g. https://store.steampowered.com/app/730/CounterStrike_2/) or a plain numeric app ID (e.g. 730).

## `gameNames` (type: `array`):

Optional. Game titles to resolve automatically via Steam's public store search (e.g. 'Stardew Valley'). Each name is resolved to one app ID; if nothing matches, you'll get a SEARCH_NO_MATCH error row for that name instead of a silent gap.

## `maxReviewsPerGame` (type: `integer`):

Maximum number of reviews to fetch per game. Set to 0 for unlimited (fetch all available reviews).

## `sort` (type: `string`):

How Steam should order the reviews. 'Most recent' and 'Last updated' page reliably through all reviews. 'Most helpful' widens the day-range window until it finds results and does not reliably page through full history.

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

Review languages to include, using Steam's API language codes (e.g. 'english', 'german', 'schinese'). Leave empty or include 'all' for every language.

## `reviewType` (type: `string`):

Filter reviews by recommendation.

## `purchaseType` (type: `string`):

Filter reviews by how the game was obtained.

## `includeOffTopicActivity` (type: `boolean`):

Steam filters out review bombs (off-topic activity bursts) by default. Enable this to include them in the results.

## `steamDeckOnly` (type: `boolean`):

Only include reviews whose author played primarily on Steam Deck when they wrote the review.

## `playtimeMinHours` (type: `integer`):

Optional. Only include reviews whose author had at least this many hours played when they wrote the review.

## `playtimeMaxHours` (type: `integer`):

Optional. Only include reviews whose author had at most this many hours played when they wrote the review.

## `dateFrom` (type: `string`):

Optional. Only include reviews created on or after this date (ISO format, e.g. 2024-01-01). Filtered by Steam's API itself, so it works with any sort order and never fetches or charges for reviews outside the window.

## `dateTo` (type: `string`):

Optional. Only include reviews created on or before this date (ISO format, e.g. 2024-12-31).

## `dayRange` (type: `integer`):

Only used when 'Sort by' is 'Most helpful'. How many days back Steam should search for helpful reviews. 0 means no limit.

## `includeGameSummary` (type: `boolean`):

If enabled, pushes one extra row per game with Steam's overall review score, totals, and the number of reviews matching your filters. Summary rows are counted and charged as results.

## `includeInsights` (type: `boolean`):

If enabled, pushes one extra 'gameInsights' row per game with rule-based analytics computed from the fetched reviews: positive % overall/last 30 days/by month, top keyword counts for positive vs negative reviews (English only, simple stopword-filtered word/bigram counts - not AI), share of reviews from players with under 2 hours playtime, average playtime for positive vs negative reviews, language breakdown, early-access share, and refund/free-key share. Insight rows are counted and charged as results.

## `trackChanges` (type: `boolean`):

If enabled, the actor remembers the newest review it has seen per game in a named key-value store and, on the next run with the same 'Monitor name', fetches only reviews newer than that - a cheap way to check for new reviews since your last run. Forces sort to 'Most recent' internally so new reviews can be detected reliably.

## `monitorName` (type: `string`):

Name of the key-value store used to persist last-seen review state when 'Track changes' is enabled. Use the same name across scheduled runs to keep monitoring the same games; use a different name to start a fresh monitor.

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

Optional proxy configuration. Steam's review API has no anti-bot protection, so a proxy is rarely needed - it can help if you're fetching very large volumes across many games.

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

Maximum number of games to fetch reviews for in parallel.

## Actor input object example

```json
{
  "games": [
    "730"
  ],
  "maxReviewsPerGame": 20,
  "sort": "recent",
  "languages": [
    "all"
  ],
  "reviewType": "all",
  "purchaseType": "all",
  "includeOffTopicActivity": false,
  "steamDeckOnly": false,
  "dayRange": 0,
  "includeGameSummary": true,
  "includeInsights": true,
  "trackChanges": false,
  "monitorName": "steam-reviews-monitor",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "maxConcurrency": 3
}
```

# Actor output Schema

## `overview` (type: `string`):

All scraped review rows, game summary/insights rows, monitoring status rows, and error rows for games that could not be fetched.

## `runReport` (type: `string`):

End-of-run self-check: inputs processed, rows pushed by type, errors by code, warnings, and output validation results.

# 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 = {
    "games": [
        "730"
    ],
    "maxReviewsPerGame": 20,
    "languages": [
        "all"
    ],
    "monitorName": "steam-reviews-monitor",
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("zerahdata/steam-reviews-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 = {
    "games": ["730"],
    "maxReviewsPerGame": 20,
    "languages": ["all"],
    "monitorName": "steam-reviews-monitor",
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("zerahdata/steam-reviews-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 '{
  "games": [
    "730"
  ],
  "maxReviewsPerGame": 20,
  "languages": [
    "all"
  ],
  "monitorName": "steam-reviews-monitor",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call zerahdata/steam-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zerahdata/steam-reviews-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/pVpE5X7dsxqwO8oNe/builds/blDsCaNgb6yOdU72w/openapi.json
