You will build a small game-data workflow with two paths: retrieve news through a documented Steam Web API method, and normalize a permitted price dataset into CSV, JSON, and SQLite.
The price tracker runs locally with a synthetic fixture so the full parsing and storage path is reproducible without scraping a live storefront. For production prices, connect a source whose access and data-use terms cover your workflow. This distinction matters because a reachable storefront URL is not automatically a supported public pricing API.
Steam Data Sources: Store API, Steam Web API, and HTML
Steam data collection starts with choosing the interface that actually supplies the required fields and permits the intended use.
Choosing the Right Steam Data Source
Source
Suitable purpose
Important limit
Documented Steam Web API
Specific documented methods, such as app news
Not every store field or price is exposed
Approved publisher/partner interface
Data within the developer's entitlement
Credentials and app access may be restricted
Licensed price dataset
Regional offer history under contract
Coverage and redistribution depend on license
Storefront-facing JSON
Potential site implementation detail
Reachability is not a stability or permission guarantee
Store HTML
Approved page-level research
Access rules, regional state, markup, and age gates
Valve's Web API overview describes documented API use. The frequently referenced store appdetails interface should not be represented as a general pricing API with the same documented guarantees.
Understanding Steam App IDs and Regional Pricing
An App ID identifies an application, which may be a game, DLC, demo, or another app type. An offer can also involve packages or bundles, so an app-level label is not a complete commerce identity.
Store App ID, offer type, region, currency, original and current amount, price state, source, and observation time. Preserve integer source amounts with an explicit unit convention. Do not divide every currency by 100 without checking the source's unit definition.
Prerequisites and Python Environment Setup
The supplied tracker uses Python's standard library. Create a directory containing steam_tracker.py and steam_prices_fixture.json, then use a virtual environment.
The fixture contains fictional game names and prices, including paid, free, and unavailable states. It exists to validate behavior; do not use it as a market-history sample.
Step 1: Fetch Steam Game Details
Fetch only through an interface that covers the requested data and use. For a documented, narrow example, Valve's GetNewsForApp method returns app news using an App ID and bounded result count.
The tracker includes this optional command:
python steam_tracker.py --news570
This documentation-based request was not executed as a live Steam test for the article. It retrieves news rather than prices, developer names, or an authoritative product catalog. The distinction prevents a news response from being mislabeled as complete store metadata.
For game details and prices, import a permitted source response through an adapter that maps into the schema below.
Step 2: Extract Game Metadata and Prices
The normalized schema keeps unknown values explicit.
For actual data, also preserve app type, package identity, source locale, and the provider's amount-unit definition. Developer names, release dates, and genres should remain nullable when absent. A displayed release-date string is not always a sortable ISO date.
The parser rejects inconsistent free prices and unknown-price states with numeric amounts. An unavailable regional offer has null prices rather than a fabricated zero.
Step 3: Collect Multiple Steam Games
Apply bounded collection practices and use an allowlist of App IDs and record each outcome. Keep invalid IDs, region-unavailable offers, source errors, and missing prices separate.
When using an approved online source, share one rate budget across workers and cache stable metadata. Do not interpret a universal concurrency recommendation as permission to exceed a method's limits.
The sample processes three local records. Increase the fixture size to test parsing and storage before adding a live acquisition adapter.
Step 4: Export Steam Data to CSV and JSON
CSV is useful for review; JSON preserves null values and field types more clearly. SQLite supports historical observations.
The complete collector writes all three outputs:
"""Parse an authorized/local price feed; optionally call documented public news API."""import argparse
import csv
import json
import sqlite3
import time
from datetime import datetime
from pathlib import Path
from urllib.parse import urlencode
from urllib.request import Request, urlopen
defnormalize(item): appid =int(item["appid"])if appid <=0:raise ValueError("Invalid App ID") observed = item["observed_at"]if datetime.fromisoformat(observed.replace("Z","+00:00")).utcoffset()isNone:raise ValueError("Observation time must include timezone") state = item["price_state"]if state notin("paid","free","unavailable","undisclosed"):raise ValueError("Unknown price state") initial, final = item.get("initial_minor"), item.get("final_minor") currency, region = item.get("currency"), item["region"]if state in("paid","free"):ifnot currency orany(type(v)isnotintor v <0for v in(initial, final)):raise ValueError("Missing comparable price")if state =="free"and(initial !=0or final !=0):raise ValueError("Free price must be zero")elif initial isnotNoneor final isnotNone:raise ValueError("Unknown prices must remain null")returndict(appid=appid, name=item["name"], region=region, currency=currency, initial_minor=initial, final_minor=final, price_state=state, observed_at=observed, source=item["source"])defsave(rows, database):with sqlite3.connect(database)as db: db.execute("""CREATE TABLE IF NOT EXISTS prices(
appid INTEGER,name TEXT,region TEXT,currency TEXT,
initial_minor INTEGER,final_minor INTEGER,price_state TEXT,
observed_at TEXT,source TEXT,
PRIMARY KEY(appid,region,observed_at,source))""") columns =list(rows[0]) db.executemany("INSERT OR REPLACE INTO prices VALUES(?,?,?,?,?,?,?,?,?)",[tuple(row[k]for k in columns)for row in rows])defnews(appid): query = urlencode(dict(appid=appid, count=3, maxlength=200,format="json")) request = Request("https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/?"+ query, headers={"User-Agent":"GameResearchDemo/1.0"})with urlopen(request, timeout=20)as response: payload = json.load(response) data = payload.get("appnews",{})ifint(data.get("appid",-1))!= appid:raise ValueError("Unexpected news response")return data.get("newsitems",[])if __name__ =="__main__": parser = argparse.ArgumentParser() parser.add_argument("--fixture", default="steam_prices_fixture.json") parser.add_argument("--db", default="steam.sqlite") parser.add_argument("--news",type=int) args = parser.parse_args()if args.news:print(json.dumps(news(args.news), ensure_ascii=False, indent=2))else: rows =[normalize(item)for item in json.loads(Path(args.fixture).read_text())]ifnot rows:raise SystemExit("No records") save(rows, args.db) Path("steam_prices.json").write_text(json.dumps(rows, indent=2))withopen("steam_prices.csv","w", newline="")as handle: writer = csv.DictWriter(handle, fieldnames=list(rows[0])) writer.writeheader() writer.writerows(rows)print(f"accepted_records={len(rows)}")
Keep formatted price strings as optional display values. Use normalized numeric amounts and currency for comparisons. Never infer currency from the user's location or language alone.
Step 5: Add Error Handling and Rate Limiting
Handle invalid App IDs, non-JSON responses, HTTP errors, timeouts, unavailable regions, and missing fields as explicit failures or states.
The example makes one bounded news request and stops on error. A production approved-source adapter should add a small retry budget for transient failures, honor Retry-After, and avoid retrying permission failures. No request rate is guaranteed to be acceptable across all Steam interfaces.
Step 6: Scrape Steam Store Pages When APIs Are Insufficient
Use HTML only where your access arrangement permits it. Check whether structured fields are present in the document before launching a browser.
Develop the parser against saved authorized HTML and test absent-price, free-game, DLC, and regional-unavailable cases. Do not work around age verification, login requirements, or explicit denial. If price access is not approved, use a licensed dataset or an appropriate partner interface instead.
Valve's Subscriber Agreement includes automation restrictions. The Web API terms govern API usage; they should be reviewed separately from storefront behavior.
Step 7: Schedule Steam Price Monitoring
Schedule according to the chosen source's permission and freshness requirements, using a documented price-monitoring pipeline. A daily approved feed may be sufficient for a research dashboard; it does not provide transaction-time price execution.
Store observations with UTC time and source identity. Calculate a change only between comparable offers. Alert on an unexpected currency change or missing offer rather than calling it a discount.
Steam Scraper Example: Build a Game Price Tracker
Run the included fixture, inspect three normalized records, and confirm that the free record remains zero while the unavailable record remains null.
To detect a price change, query the latest two accepted observations for the same app, region, currency, source, and offer type. Compare final amounts only when both have valid price states. Add an append-only change table containing previous amount, new amount, timestamps, and evidence references.
Local checks cover normalization, null handling, exports, and repeated storage of the same snapshot. They do not establish live Steam price accuracy or current interface permissions.
Common Steam Scraping Errors and Fixes
Symptom
Possible explanation
Correct treatment
No price
Free, unavailable, undisclosed, or failed extraction
Preserve distinct state and inspect evidence
Different amounts
Region/currency/package mismatch
Compare matching identity keys
Missing developer
Source does not supply the field
Keep null; do not infer
Changed language
Locale or session changed
Store locale with observation
Empty response
Invalid ID, method issue, or source failure
Keep failure outside accepted observations
Sudden βfreeβ game
Missing value coerced to zero
Fix validation and repair affected records
Steam Scraping at Scale: Architecture and Costs
For production deployment, separate discovery, stable metadata, changing offers, normalization, and reporting. Cache stable fields, update only the approved offer cohort, and persist failed jobs with bounded retries.
Budget licensed data, requests, rendering where needed, storage, maintenance, and source review. Using Nstdata Crawl for an approved page path can provide acquisition infrastructure; it does not supply Steam-specific data rights or a guaranteed price schema.
Confirm interface permission, API terms, attribution, caching, redistribution, and commercial-use conditions before deployment. Do not extend a method's availability into permission to collect unrelated user data.
Keep personal player data outside a store-price project unless it is explicitly necessary and appropriately authorized. Product price research does not require player profiles or account activity.
Final Project Checklist
Validate App IDs and offer identities, preserve regional context, distinguish missing from free, test exports, confirm snapshot persistence, and review access terms. Connect a live price source only after the local importer and data-use arrangement are both verified.
Validate permitted store content
Keep approved acquisition separate from game-specific parsing and source rights.
Yes. Valve documents Steam Web API methods, but that does not imply a supported general price endpoint for every use.
Q: How can I get Steam game prices?
Use an approved interface or licensed source that covers the fields, regions, and use you need. This project normalizes that input and tests it with a fixture.
Q: Does the sample contain actual game prices?
No. The supplied price records and App IDs are synthetic, clearly labeled test data.
Q: Can I track discounts?
Yes, if the source permits monitoring and comparable offer records are available. Keep currency, region, package, and timestamps consistent.
Q: Why is appdetails not used as the live tutorial contract?
A storefront-facing endpoint should not be presented as a fully documented, authorized general pricing API merely because it is reachable.
Q: Can the documented news method replace the price source?
No. News and store prices are different datasets and must retain separate schemas and permissions.
Lena Zhou
Oct. 19th 2026
Crawl entire websites with a single API request
99.8% success rate with JavaScript rendering
Get clean, LLM-ready data in multiple formats
Turn any website into Markdown, HTML, JSON, links, PDFs and more β without managing crawling infrastructure.