# Steam Reviews Scraper - With Playtime Data (`dataio/steam-reviews-playtime-scraper`) Actor

Scrape Steam reviews with the hours the player had when writing them, how long they kept playing afterwards, the early access flag, and whether they bought the game on Steam or received it. Filter by language and minimum playtime. No API key needed.

- **URL**: https://apify.com/dataio/steam-reviews-playtime-scraper.md
- **Developed by:** [Tom Awake](https://apify.com/dataio) (community)
- **Categories:** E-commerce, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.33 / 1,000 reviews

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

### What does Steam Reviews Scraper do?

Steam reviews with **the hours the player had when they wrote them** — and
how long they kept playing afterwards.

Steam is the only major store that publishes this, and almost nobody uses
it. It changes what a review means. A negative review at two hours says
the game disappoints immediately. The same review at five hundred hours
says it was loved for a long time and then spoiled — by a patch, a
monetisation change, an abandonment. Both count as one thumb-down in the
average.

No API key. No account.

***

### What playtime does to the numbers

400 recent reviews of Cyberpunk 2077:

| Hours at review | Reviews | Positive |
|---|---|---|
| 2–10h | 33 | **88%** |
| 10–50h | 116 | 94% |
| 50–200h | 211 | **97%** |
| 200–1000h | 37 | 95% |

Nine points between players passing through and players who know the game.
The store's headline percentage shows one number for all of them.

Median playtime at review in that run: **71.8 hours**.

And **38% kept playing after posting** — including five negative reviews
followed by another 4 to 12 hours. A complaint from someone who came back
is a different signal from one who left.

***

### The four things this does that the source does not

**1. Playtime in hours, with a readable tier.**

Steam returns minutes. `playtimeAtReviewHours` converts, and
`playtimeTier` buckets it — the first boundary at 2 hours, because that is
Steam's own refund window and the line between trying a game and playing
it.

**2. What happened after.**

The API gives playtime at review *and* playtime now, and never subtracts
them. `playtimeSinceReviewHours` and `keptPlayingAfterReview` do.

**3. Purchase context, surfaced.**

Four flags buried in the response that qualify every review:
`purchasedOnSteam` (90% in the measured run), `receivedForFree`,
`refunded`, `writtenDuringEarlyAccess`. A review from a free key and one
from a paid copy are not the same evidence.

**4. Cursor pagination, exhausted safely.**

Steam pages by cursor, and the cursor eventually repeats. Without a guard
the loop runs forever on the same page; this one stops.

***

### Field coverage

Measured on 400 reviews.

| Field | Coverage |
|---|---|
| `playtimeAtReviewHours`, `playtimeTotalHours` | 100% |
| `recommended`, `language`, `createdAt` | 100% |
| `weightedVoteScore`, `reviewUrl` | 100% |
| `review` text | 99% |
| `playtimeSinceReviewHours` | 51% |
| `authorGamesOwned` | 50% |
| `votesUp` | 8% |

Three of those need explaining rather than hiding:

**`playtimeSinceReviewHours` at 51%** — half the players have not played
since. That absence is the finding, not a gap.

**`authorGamesOwned` at 50%** — private Steam profiles. Not retrievable.

**`votesUp` at 8%** — recent reviews have not been voted on yet. Sort by
**Most helpful** if you need that field; those reviews are older by
construction.

***

### What it is for

- **Post-patch triage.** Negative reviews from players with 50+ hours are
  about what you changed. Negative reviews under 2 hours are about
  onboarding. They are different problems and different teams.
- **Competitive research.** What players actually dislike about a rival
  game, weighted by whether they know it.
- **Review-bomb analysis.** A sudden wave of sub-2-hour negatives from
  non-Steam keys looks nothing like genuine dissatisfaction, and the
  fields to tell them apart are all here.
- **Community management.** Run daily with **Most recent** and read what
  arrived overnight.
- **Store page research.** Which languages your complaints come from.

Three dataset views ship with the Actor: **Reviews**, **Playtime context**
and **Complaints**.

Pairs with **Steam Regional Pricing**, which covers the store listing —
price per market, discounts and review totals.

***

### Limits

Stated plainly, because they affect what you can conclude.

- **This is a sample, not a census.** Cyberpunk 2077 has 980,377 reviews;
  a run takes hundreds. Which order you sample in matters more than how
  many you take, and the sort you choose decides the bias.
- **Recent reviews skew negative and short.** People motivated to write
  today are disproportionately people with a problem, and their reviews
  have no votes yet.
- **Reviews are not translated.** Russian reviews come back in Russian,
  Chinese in Chinese. That is the real text; translating it is your
  choice.
- **`postedWithinDays` only works server-side with Most helpful.** Steam
  ignores `day_range` on the other sorts, so it is applied to the results
  instead — which means it filters a sample rather than selecting one.
- **Playtime is for that game only**, and comes from the player's profile
  at the time Steam served the response. A private profile still reports
  playtime but not games owned.
- **Author names are not included.** The Steam ID and profile link are,
  because they are how you verify a review; nothing else about the person
  is collected.
- **30 games per run.** Beyond that, split the work.

***

### How much does it cost?

You pay per review returned: **$0.002 each**, that is $2.00 per 1,000. There is no start fee, and subscription plans pay less per review.

The example input below asks for up to 1,000 reviews, so it costs **$2.00 at most**.

If a run reaches the spending limit you set, the output stops at that limit and never goes past it. You are never charged for rows that were not delivered.

### Use Steam Reviews Scraper as an API

Call it from your own code with the Apify client, here in Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("DataIO/steam-reviews-playtime-scraper").call(run_input={
    'appIds': ['1091500'],
    'reviewsPerGame': 300,
    'maxItems': 1000,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

It also works from JavaScript, Make, Zapier, n8n, and from AI agents through the Apify MCP server.

### Other actors you might like

- [App Store Price Scraper](https://apify.com/DataIO/app-store-price-intelligence): Compare by Country
- [App Store Reviews Scraper](https://apify.com/DataIO/app-store-reviews-by-country-version): By Country & Version
- [Steam Price Scraper](https://apify.com/DataIO/steam-regional-pricing-intelligence): Regional Game Prices

### FAQ

#### Is it legal to use this data?

The actor reads public data from its official source, without logging in and without bypassing any access control. What you do with the data, for example contacting people listed in it, is your responsibility under the laws that apply to you, such as GDPR in Europe.

#### Can I run it on a schedule?

Yes. Create a schedule in Apify Console, daily or weekly for example, and each run delivers a fresh dataset, which you can send by email, webhook or integration.

#### Can AI agents use it?

Yes. It is available through the Apify MCP server, and every input field is described in its input schema, so an agent can call it directly.

### Source

Steam's public review API (`store.steampowered.com/appreviews`), the same
endpoint behind the review section of every store page. No authentication.
This Actor is not affiliated with Valve.

# Actor input Schema

## `appIds` (type: `array`):

The number in a Steam URL: store.steampowered.com/app/1091500/ is 1091500. Up to 30 games per run.

## `reviewsPerGame` (type: `integer`):

100 per request, paginated by cursor. Popular games have hundreds of thousands of reviews, so this is a sample — which order you take it in matters more than how many.

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

Most recent is what players are saying now. Most helpful surfaces the reviews the community voted up — those have vote counts, recent ones almost never do.

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

Negative only is the fastest route to what is actually wrong.

## `minPlaytimeHours` (type: `integer`):

The whole point of this Actor. Set 50 and you read only players who actually know the game; measured on Cyberpunk 2077, that group rates it 97% positive against 88% for players under 10 hours.

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

Steam language code: english, french, german, schinese, russian, brazilian. Leave as all for every language — reviews are returned in their original language, untranslated.

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

Steam purchases only excludes third-party keys, which are more often review-bombed or given away.

## `postedWithinDays` (type: `integer`):

Steam only honours this server-side with the Most helpful sort; with the other sorts it is applied to the results instead.

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

One row per review, across all games.

## Actor input object example

```json
{
  "appIds": [
    "1091500"
  ],
  "reviewsPerGame": 300,
  "sortBy": "recent",
  "reviewType": "all",
  "language": "all",
  "purchaseType": "all",
  "maxItems": 1000
}
```

# Actor output Schema

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

Every row produced by the run, in JSON.

# 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 = {
    "appIds": [
        "1091500"
    ],
    "reviewsPerGame": 300,
    "maxItems": 1000
};

// Run the Actor and wait for it to finish
const run = await client.actor("dataio/steam-reviews-playtime-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 = {
    "appIds": ["1091500"],
    "reviewsPerGame": 300,
    "maxItems": 1000,
}

# Run the Actor and wait for it to finish
run = client.actor("dataio/steam-reviews-playtime-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 '{
  "appIds": [
    "1091500"
  ],
  "reviewsPerGame": 300,
  "maxItems": 1000
}' |
apify call dataio/steam-reviews-playtime-scraper --silent --output-dataset

```

## MCP server setup

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