# Steam Reviews Scraper | Full History, Filters, $0.50/1K (`kesar_chen/steam-reviews-scraper`) Actor

Full review history of any Steam game: positive/negative totals, review score, playtime, filters by language, date and sentiment, incremental runs. Official public data. Pay per review, no start fee.

- **URL**: https://apify.com/kesar_chen/steam-reviews-scraper.md
- **Developed by:** [Kesar Chen](https://apify.com/kesar_chen) (community)
- **Categories:** Developer tools, AI
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 1,000 review scrapeds

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?

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 Reviews Scraper

Pull the **full review history** of any Steam game as clean JSON: every review Steam still serves, newest first, with thumbs up or down, playtime at the time of writing, helpful votes, language, refund and Early Access flags, developer replies where they exist, and the game's all-time positive and negative totals. Filter by language, sentiment, date range and playtime before anything is fetched, so you only pay for rows you will read. **$0.50 per 1,000 reviews, no start fee**: a 30,000-review game costs about $15 once, and a daily watch on 10 games runs about $3 a month in incremental mode.

Built on Valve's public reviews data, no login, no API key required.

### Why this one

- **Full history, newest first, sliceable by date.** `sinceDate: "12 months"` or a `sinceDate`/`untilDate` pair are applied by Steam itself, so a 1-million-review game narrows down to the two weeks after a patch without reading anything else.
- **Steam-side filters, billed after filtering.** Language, thumbs up or down, bought on Steam or activated with a key, minimum and maximum playtime, Steam Deck players, off-topic review bombs included or not. Filtered-out reviews are never fetched and never charged.
- **Only new reviews on every scheduled run.** Turn on `incremental` and the Actor remembers what it already returned per game; your dataset never needs deduping and you never pay twice for the same review.
- **A summary row per game** with the Steam review score bucket, all-time positive and negative counts, the count matching your filters, price, release date, Metacritic score and a last-N-months not-recommended share with the sample size next to it.

### Who uses it

- **Studios and publishers** watch their own and competing titles after each patch: `reviewType: "negative"`, `sinceDate: "14 days"`, grouped by language.
- **Community and support teams** pull reviews from players with real playtime (`playtimeMinHours: 20`) and skip drive-by posts.
- **Analysts and researchers** get stable IDs, ISO dates, playtime and vote counts in one fixed schema across every language.
- **AI agents** call it through the Apify MCP server and read the field descriptions from the dataset schema.

### Speed

Measured on the Apify platform: 100 reviews per request, about 1.5 seconds a page, so 3,000 reviews in under a minute and 100,000 reviews in about 25 minutes per game. Games run in parallel, pages within a game in sequence.

### Use it from Claude, Cursor, or any MCP client

Add this URL as an MCP server in your client:

```
https://mcp.apify.com/?tools=actors,docs,kesar_chen/steam-reviews-scraper
```

For Claude Desktop, put this in `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com/?tools=actors,docs,kesar_chen/steam-reviews-scraper",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

For Claude Code, one command:

```bash
claude mcp add --transport http apify "https://mcp.apify.com/?tools=actors,docs,kesar_chen/steam-reviews-scraper" --header "Authorization: Bearer YOUR_APIFY_TOKEN"
```

Then ask things like:

- "Pull the negative reviews of app 413150 from the last 3 months and group the complaints."
- "Compare the not-recommended share of these three games over the last 12 months."
- "Every morning, fetch new reviews for my game and tell me if anything mentions crashes."

Review text is user-generated content: tell your agent to treat `body` as data, never as instructions.

### Run it in the Apify Console

1. Paste app IDs, store URLs or `search:game name` into **Games**.
2. Optionally set **Only reviews since**, **Review type**, **Languages** or a playtime floor, and a maximum cost per run under **Run options**.
3. Click **Start**. The **Reviews** view holds one row per review, the **Summary** view one row per game. Export as JSON, CSV or Excel, or read it through the API.

### Pricing

Pay per event, no subscription, no start fee.

| Event | Price |
|---|---|
| Review scraped | $0.50 per 1,000 reviews |
| Game summary (one per game, with totals and score) | $0.002 |

| Scenario | Reviews | Cost |
|---|---|---|
| Try it: one game, newest 500 | 500 | about $0.25 |
| One game, full history | 30,000 | about $15 |
| 10 games, daily incremental, ~20 new reviews each per day | ~200 a day | about $3 a month |

Apify platform usage is included. The monthly credit of Apify's free plan covers several thousand reviews. Paid Apify plans get a lower per-review price, shown on the Actor page.

Set a maximum budget per run in the Apify Console. When it is reached the run stops cleanly: reviews already written are kept and charged, nothing else is, and the summary row records how far it got. In incremental mode the saved state is only updated for games that completed, so a budget stop never loses data.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `apps` | list | required | App ID (`413150`), store URL, or `search:name` |
| `languages` | list | `["all"]` | Steam language codes: `english`, `schinese`, `japanese`, `german` ... |
| `sort` | select | `recent` | `recent`, `updated`, `helpful`, `funny`. `recent` is required for `sinceDate` and incremental mode |
| `reviewType` | select | `all` | `positive`, `negative` |
| `purchaseType` | select | `all` | `steam` (bought on Steam), `non-steam` (key activation) |
| `sinceDate` / `untilDate` | text | empty | `2026-01-01` or `12 months`, `30 days`. Applied by Steam |
| `playtimeMinHours` / `playtimeMaxHours` | integer | `0` | Playtime at the time of writing. `0` = no filter |
| `steamDeckOnly` | boolean | `false` | Authors who played mostly on Steam Deck |
| `includeOffTopic` | boolean | `false` | Include reviews from periods Steam flagged as review bombs |
| `keywords` | list | `[]` | Keep only reviews containing one of these words |
| `maxReviewsPerApp` | integer | `3000` | `0` = everything that matches |
| `summaryWindowMonths` | integer | `12` | Not-recommended share window in the summary row. `0` turns it off |
| `incremental` | boolean | `false` | Only new reviews on later runs, state kept per game |
| `stateStoreName` | text | `steam-reviews-state` | Named key-value store for incremental state. If a store with this name was created by another Actor, the run logs a warning and uses `<name>-<actor id>` instead |
| `notifyWebhookUrl` | text | empty | Slack, Discord, Feishu or any HTTPS webhook; see Alerts |
| `notifyMaxRating` | integer | `1` | Only reviews at or below this rating are posted |
| `notifyMaxItems` | integer | `10` | Reviews per message; the rest are counted |
| `notifyEvenIfEmpty` | boolean | `false` | Post a message even when nothing matched |
| `includeAuthorId` | boolean | `false` | Off by default; playtime figures are always included |
| `searchResultsPerKeyword` | integer | `1` | Games a `search:name` entry expands to |

Minimal example:

```json
{ "apps": ["413150"], "sinceDate": "12 months" }
```

Patch watch:

```json
{ "apps": ["730", "1172470"], "reviewType": "negative", "sinceDate": "14 days", "languages": ["english"], "playtimeMinHours": 5 }
```

### Output

One row per review plus one summary row per game. Use the `type` field or the two dataset views to tell them apart.

Review row:

```json
{
  "type": "review",
  "reviewId": "237163238",
  "appId": 413150,
  "appName": "Stardew Valley",
  "language": "english",
  "recommended": false,
  "body": "Crashes on load since the last update.",
  "createdAt": "2026-10-20T12:34:56.000Z",
  "updatedAt": "2026-10-20T12:34:56.000Z",
  "votesUp": 12,
  "votesFunny": 0,
  "weightedVoteScore": 0.62,
  "commentCount": 1,
  "steamPurchase": true,
  "receivedForFree": false,
  "writtenDuringEarlyAccess": false,
  "primarilySteamDeck": false,
  "refunded": false,
  "playtimeAtReviewMinutes": 1830,
  "playtimeForeverMinutes": 4200,
  "playtimeLastTwoWeeksMinutes": 120,
  "authorReviewCount": 3,
  "authorSteamId": null,
  "developerResponse": null,
  "isNew": true,
  "scrapedAt": "2026-10-25T00:00:00.000Z"
}
```

Summary row:

```json
{
  "type": "summary",
  "appId": 413150,
  "appName": "Stardew Valley",
  "appType": "game",
  "developers": ["ConcernedApe"],
  "publishers": ["ConcernedApe"],
  "releaseDate": "Feb 26, 2016",
  "isFree": false,
  "price": "$14.99",
  "metacriticScore": 89,
  "reviewScore": 9,
  "reviewScoreDesc": "Overwhelmingly Positive",
  "totalPositive": 1031073,
  "totalNegative": 15900,
  "totalReviews": 1046973,
  "totalMatching": 53808,
  "reviewsScraped": 3000,
  "coverageNote": "reached maxReviewsPerApp=3000",
  "window": { "months": 12, "from": "2025-10-25T00:00:00.000Z", "reviews": 3000, "negative": 41, "negativeShare": 1.4, "note": "sample 3000; coverage does not reach the whole window, counts are a lower bound" },
  "scrapedAt": "2026-10-25T00:00:00.000Z"
}
```

`totalReviews` is the all-time count; `totalMatching` is what matches this run's filters; `reviewsScraped` is what was written. `negativeShare` is only given when the window has at least 20 reviews. The window is always the last `summaryWindowMonths`, independent of `sinceDate`.

### Limitations

- Steam has no star ratings and no review titles. `recommended` is the thumbs up or down; `title` is always null and exists only so that the schema lines up with the App Store Reviews Scraper.
- Developer replies are rare on Steam. `developerResponse` is filled when Steam provides one and null otherwise.
- Reviews from periods Steam flagged as off-topic review activity are hidden by default, matching the store page. Turn on `includeOffTopic` to include them.
- `helpful` and `funny` sorts do not page through the whole history reliably; use `recent` for full pulls.
- Steam may cache anonymous responses for a few minutes, so a review posted seconds ago can show up on the next run.

### Alerts: post new low-rated reviews to Slack, Discord or Feishu

Fill `notifyWebhookUrl` with a Slack incoming webhook, a Discord channel webhook, a Feishu/Lark custom bot webhook, or any HTTPS endpoint, turn on `incremental`, and attach a Schedule. After each game the Actor posts the reviews written in that run that are at or below `notifyMaxRating` (default 1, meaning not-recommended reviews only), with the star rating, date, version where known, whether the developer already replied, and a link to the store page. Nothing is posted when nothing matched, unless `notifyEvenIfEmpty` is on. Unknown hosts receive a JSON body with the same fields, so Make, n8n, Zapier or your own service can take it from there. There is no extra charge for notifications.

An n8n template that does the same with room for translation or routing is in the [templates folder](https://github.com/ClaymanTwinkle/AppStoreReviewsActor/tree/main/templates).

### Scheduling and integrations

Save your input as a Task, attach a Schedule and turn on `incremental`. Each run then returns only new reviews, which you can push to Slack, Google Sheets, Make, Zapier, n8n or any webhook through Apify integrations.

### FAQ

**Is it legal to scrape Steam reviews?**
This Actor reads public review data that Valve exposes without login or API key, and collects no private account data; the author's Steam ID is off by default. Collecting publicly available data is generally considered legal in the US and most of the EU. How you store and use the output, and whether it fits the terms of the services involved, is your responsibility. See [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/)

**Why not use Steam's own review export?**
Steam gives developers a dashboard for their own games only. This Actor works for any game, which is what you need for competitor analysis and market research.

**How long does a run take?**
About 1.5 seconds per 100 reviews per game; games run in parallel.

**What happens when my budget runs out mid-run?**
The run stops cleanly, keeps what was written, and the summary row records how far it got.

**Is this affiliated with Valve?**
No. This is an independent tool and is not endorsed by or affiliated with Valve Corporation.

### See also

[App Store Reviews Scraper](https://apify.com/kesar_chen/app-store-reviews-scraper) and [Google Play Reviews Scraper](https://apify.com/kesar_chen/google-play-reviews-scraper): the same schema, filters and incremental mode for iOS and Android apps, so one pipeline can watch all three stores.

[Hiring Signals Monitor](https://apify.com/kesar_chen/hiring-signals-monitor): job opened / closed events and a hiring summary per company from public job boards, for watching studios' teams the same way you watch their reviews.

### Support

Bugs or missing fields: open an issue on the Issues tab. Reports go to a human.

# Actor input Schema

## `apps` (type: `array`):

Steam app IDs (730), store URLs (https://store.steampowered.com/app/413150/...), or search:name to resolve by name. Required unless mode is canary.

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

Steam language codes (english, schinese, japanese, german ...). \["all"] for every language.

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

recent walks the full history newest first and is required for sinceDate and incremental mode.

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

Filtered on Steam's side, so you only pay for what matches.

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

Filtered on Steam's side. Steam's own default is purchases on Steam only; this Actor defaults to all.

## `sinceDate` (type: `string`):

2026-01-01, or relative like 12 months, 30 days, 2 weeks. Applied on Steam's side.

## `untilDate` (type: `string`):

Upper bound, same formats. Combine with sinceDate to pick a window, for example the two weeks after a patch.

## `maxReviewsPerApp` (type: `integer`):

0 = everything that matches. Large games have a million-plus reviews, set a limit or a date range.

## `keywords` (type: `array`):

Keep only reviews whose text contains at least one of these words, case-insensitive. Applied after fetching; filtered-out reviews are not charged.

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

Skip reviews from people who had played less than this when they wrote the review. 0 = no filter.

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

0 = no filter.

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

Only reviews from authors who played mostly on Steam Deck.

## `includeOffTopic` (type: `boolean`):

Steam hides reviews from periods it flagged as off-topic review activity. Turn on to include them.

## `summaryWindowMonths` (type: `integer`):

The summary row counts not-recommended reviews in this window. 0 turns it off.

## `incremental` (type: `boolean`):

Remembers which reviews it has seen per game in a named key-value store and outputs only new ones on later runs. Requires sort = recent.

## `stateStoreName` (type: `string`):

Named key-value store that keeps the incremental state. Named stores never expire.

## `notifyWebhookUrl` (type: `string`):

Paste a Slack incoming webhook, a Discord channel webhook, a Feishu/Lark custom bot webhook, or any HTTPS endpoint. After each job the Actor posts the reviews written in this run that are at or below the rating threshold. With incremental mode on, that means only new low-rated reviews. Unknown hosts receive a JSON body.

## `notifyMaxRating` (type: `integer`):

Steam has thumbs up/down only: 1 = post only not-recommended reviews (default), 5 = post everything written.

## `notifyMaxItems` (type: `integer`):

The rest are summarised as a count; all of them are in the dataset.

## `notifyEvenIfEmpty` (type: `boolean`):

Off = silence when there is nothing at or below the threshold.

## `includeAuthorId` (type: `boolean`):

Off by default to keep personal identifiers out of your dataset. Playtime figures are always included.

## `searchResultsPerKeyword` (type: `integer`):

How many games a search:name entry expands to, by store relevance.

## `mode` (type: `string`):

Leave on scrape.

## Actor input object example

```json
{
  "apps": [
    "413150"
  ],
  "languages": [
    "all"
  ],
  "sort": "recent",
  "reviewType": "all",
  "purchaseType": "all",
  "sinceDate": "12 months",
  "untilDate": "",
  "maxReviewsPerApp": 3000,
  "keywords": [],
  "playtimeMinHours": 0,
  "playtimeMaxHours": 0,
  "steamDeckOnly": false,
  "includeOffTopic": false,
  "summaryWindowMonths": 12,
  "incremental": false,
  "stateStoreName": "steam-reviews-state",
  "notifyMaxRating": 1,
  "notifyMaxItems": 10,
  "notifyEvenIfEmpty": false,
  "includeAuthorId": false,
  "searchResultsPerKeyword": 1,
  "mode": "scrape"
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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 = {
    "apps": [
        "413150"
    ],
    "sinceDate": "12 months",
    "untilDate": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("kesar_chen/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 = {
    "apps": ["413150"],
    "sinceDate": "12 months",
    "untilDate": "",
}

# Run the Actor and wait for it to finish
run = client.actor("kesar_chen/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 '{
  "apps": [
    "413150"
  ],
  "sinceDate": "12 months",
  "untilDate": ""
}' |
apify call kesar_chen/steam-reviews-scraper --silent --output-dataset

```

## MCP server setup

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