この記事では、Steam公式Web APIをPythonから使い、アプリ一覧、同時接続数、実績、ニュースなどを取得する基本をまとめます。
自分のデータに合う実行経路を選ぶ
以下はWindows PowerShellでの手順です。Python 3.10以上を用意し、必要なファイルを保存したフォルダーをエクスプローラーで開き、アドレス欄に powershell と入力してEnterを押します。Get-Location で作業フォルダーを確認してください。コード全文は後ろに掲載しています。選んだ経路だけを実行し、別の経路のコードを同じファイルへつなげないでください。
アプリ一覧・全体解除率・ニュースは別々の処理です。アプリ一覧(IStoreService/GetAppList)はAPIキーが必要です。今回の全体解除率(GetGlobalAchievementPercentagesForApp、gameid)・ニュース(GetNewsForApp、appid)・現在CCU(GetNumberOfCurrentPlayers、appid)にはキーが不要です。個人の実績を読むGetPlayerAchievementsは別で、SteamID・AppID・キーが必要です。
実績の全体解除率/ニュースをCSVへ保存する(API通信あり)
save_steam_public_data.py1個を保存します。APIレスポンスを画面で学ぶ前半の短い例とは代替で、同じファイルへ連結しません。まずrequestsを導入し、目的のコマンドだけ実行します。
python -m pip install requests実績の全体解除率を保存する場合:
python save_steam_public_data.py achievements --appid 730ニュースを最大5件保存する場合:
python save_steam_public_data.py news --appid 730 --count 5相対パスは作業フォルダー基準です。出力は outputs/steam_achievements.csv/outputs/steam_news.csv と各.metadata.json。画面の保存先・件数を確認します。既存ファイルがあれば停止するため、履歴を残して別の--outputフォルダーを選びます。
取得したCSVを新しいPython実行から読む(API通信なし)
read_steam_public_csv.pyを保存します。この既存の読込例は実績とニュースの両方のCSV・metadataを順に確認します。片方だけ取得した場合は、そのCSVを直接開いて確認し、両方があるときだけ下を実行してください。
python read_steam_public_csv.py列・件数・取得条件と先頭2行が表示されます。解除率はachievement_name(内部名)とpercent、ニュースはtitle・published_at・urlを確認します。日付順に読みたい場合はCSV閲覧ソフトでpublished_at列を並べ替えてください。読込例自体は並べ替えを行いません。元CSVを保存し直すとhashが変わるため閲覧用コピーを使います。公開日時published_atと取得日時fetched_atはいずれもUTCですが別の時刻です。
掲載コードと詳しい条件
Steam公式Web APIで扱う範囲
Steam Web APIは、SteamがWeb開発者向けに提供しているAPIです。公式ドキュメントでは、API呼び出しの基本形や出力形式、APIキーの取得方法が説明されています。
Steamworks公式:IStoreService/GetAppList / Steam Web APIキーの取得と保管方法
| 取得したいもの | 主な方法 |
|---|---|
| アプリ一覧 | IStoreService/GetAppList/v1(Web APIキー必須) |
| 現在の同時接続数 | ISteamUserStats/GetNumberOfCurrentPlayers |
| 実績情報 | ISteamUserStats 系API |
| ゲームニュース | ISteamNews/GetNewsForApp |
| 価格・レビュー本文 | Storefront API側で扱う |
アプリ一覧を取得する
先に python -m pip install requests python-dotenv を実行し、キー管理手順に従って作業フォルダーの .env に STEAM_API_KEY を設定します。以下の各API例は別々に実行できます。
現行のアプリ一覧取得は IStoreService/GetAppList/v1 を使います。旧 ISteamApps/GetAppList/v2 はSteamworks公式資料で非推奨です。現行MethodにはWeb APIキーが必要で、Service interfaceの取得条件は input_json へ渡します。
import json
import os
import requests
from dotenv import load_dotenv
load_dotenv()
api_key = os.getenv("STEAM_API_KEY")
if not api_key:
raise RuntimeError("STEAM_API_KEY が設定されていません")
url = "https://partner.steam-api.com/IStoreService/GetAppList/v1/"
max_results = 50_000
last_appid = 0
apps = []
while True:
request_data = {
"include_games": True,
"last_appid": last_appid,
"max_results": max_results,
}
params = {
"key": api_key,
"input_json": json.dumps(request_data),
}
response = requests.get(url, params=params, timeout=30)
response.raise_for_status()
page = response.json()["response"].get("apps", [])
if not page:
break
apps.extend(page)
next_last_appid = page[-1]["appid"]
if next_last_appid <= last_appid:
raise RuntimeError("last_appid が進まないため取得を中止しました")
last_appid = next_last_appid
print(len(apps))
print(apps[:3])結果はAppID順で、各項目には appid、name、last_modified、price_change_number が含まれます。1回の取得上限は5万件です。全件取得では前回最後の appid を次回の last_appid に渡します。
アプリ一覧はAppIDの確認に使えます。後続の appdetails、appreviews、同時接続数取得ではAppIDが必要です。なお、後続の ISteamUserStats や ISteamNews は別Interfaceであり、IStoreService のParameterやResponse構造をそのまま使うものではありません。
実績の全体解除率を取得する
Steamworks公式ISteamUserStatsの全体解除率を取得します。個人の解除履歴ではありません。AppIDはこのメソッドでは gameid へ渡します。
import requests
response = requests.get(
"https://api.steampowered.com/ISteamUserStats/GetGlobalAchievementPercentagesForApp/v2/",
params={"gameid": 730}, timeout=30,
)
response.raise_for_status()
achievements = response.json().get("achievementpercentages", {}).get("achievements", [])
if not achievements:
print("実績データは0件です。対象AppIDと実績対応を確認してください。")
for achievement in achievements:
print(achievement.get("name"), achievement.get("percent"))同時接続数を取得する
import requests
appid = 730
url = "https://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1/"
params = {"appid": appid}
response = requests.get(url, params=params, timeout=30)
response.raise_for_status()
data = response.json()["response"]
print(data)この値は取得時点の現在値です。推移を見たい場合は、一定間隔で自前収集して保存します。
ニュースを取得する
import requests
appid = 730
url = "https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/"
params = {
"appid": appid,
"count": 5,
"maxlength": 300,
"format": "json",
}
response = requests.get(url, params=params, timeout=30)
response.raise_for_status()
items = response.json()["appnews"]["newsitems"]
for item in items:
print(item["title"], item.get("url"))公式APIとStorefront APIの切り分け
Steamのデータ取得では、公式Web APIだけで完結しないことがあります。価格、発売日、ジャンル、レビュー本文はStorefront API側の appdetails や appreviews を使うほうが自然です。
- Steam appdetailsの使い方|価格・発売日・対応OS・ジャンルをPythonで取得
- Steam appreviewsの使い方|レビュー本文・評価サマリーをPythonで取得
- SteamSpy APIの使い方|推定オーナー数・タグ・CCU指標をPythonで取得
実績の全体解除率・ニュースをCSVへ保存する
以下はアプリ一覧のコードとは独立した手順です。キーや .env は使いません。全体解除率・現在同時接続数とニュースの今回のメソッドにはキー引数がありません。個人の実績を取得する GetPlayerAchievements は別メソッドで、SteamID・AppID・キーが必要です。
次を save_steam_public_data.py として保存します。取得対象は --appid で変更できます。ニュース本文は保存しません。
import argparse
import csv
import hashlib
import io
import json
import math
from datetime import datetime, timezone
from pathlib import Path
import requests
def fetch(kind, appid, count=5, get=requests.get):
started = datetime.now(timezone.utc).isoformat()
if kind == 'achievements':
method = 'ISteamUserStats/GetGlobalAchievementPercentagesForApp/v2/'
params = {'gameid':appid}
parent, field = 'achievementpercentages', 'achievements'
columns = ['appid','achievement_name','percent','fetched_at']
elif kind == 'news':
if count < 1: raise ValueError('countは1以上にしてください')
method = 'ISteamNews/GetNewsForApp/v2/'
params = {'appid':appid,'count':count,'maxlength':1,'format':'json'}
parent, field = 'appnews', 'newsitems'
columns = ['appid','gid','title','url','published_at','feedlabel','feedname','fetched_at']
else:
raise ValueError('achievementsまたはnewsを指定してください')
response = get('https://api.steampowered.com/'+method, params=params, timeout=30)
response.raise_for_status()
data = response.json()
if not isinstance(data.get(parent), dict) or not isinstance(data[parent].get(field), list):
raise RuntimeError('API失敗または応答形式不正。0件として保存しません')
items = data[parent][field]
fetched = datetime.now(timezone.utc).isoformat()
rows, missing = [], []
for i, item in enumerate(items):
row = {'appid':str(appid),'fetched_at':fetched}
if kind == 'achievements':
row['achievement_name'] = item.get('name')
value = item.get('percent')
if value is not None:
value = float(value)
if not math.isfinite(value) or not 0 <= value <= 100:
raise ValueError('解除率が不正です。保存しません')
row['percent'] = value
else:
for k in ['gid','title','url','feedlabel','feedname']:
row[k] = str(item[k]) if item.get(k) is not None else None
stamp = item.get('date')
row['published_at'] = datetime.fromtimestamp(float(stamp), timezone.utc).isoformat() if stamp is not None else None
for k in columns:
if row.get(k) is None: missing.append({'row':i+2,'column':k})
rows.append(row)
return columns, rows, dict(appid=str(appid), method=method, sent_parameters=params,
started_at=started, finished_at=fetched, saved_rows=len(rows),
missing_fields=missing, scope='single response snapshot; not complete news history')
def save(kind, appid, output=Path('outputs'), count=5, get=requests.get):
columns, rows, metadata = fetch(kind, appid, count, get)
path = Path(output)/f'steam_{kind}.csv'; meta = path.with_suffix('.metadata.json')
if path.exists() or meta.exists():
raise FileExistsError('以前のCSVとmetadataを退避するか、別の保存フォルダーを指定してください')
buf = io.StringIO(newline=''); w = csv.DictWriter(buf, fieldnames=columns)
w.writeheader(); w.writerows(rows); raw = buf.getvalue().encode('utf-8-sig')
metadata.update(csv_file=path.name, csv_sha256=hashlib.sha256(raw).hexdigest())
path.parent.mkdir(parents=True, exist_ok=True)
with path.open('xb') as f: f.write(raw)
meta.write_text(json.dumps(metadata, ensure_ascii=False, indent=2), encoding='utf-8')
print(path.resolve(), len(rows), '件。0件の場合も列名を保存します')
return metadata
if __name__ == '__main__':
p = argparse.ArgumentParser()
p.add_argument('kind', choices=['achievements','news'])
p.add_argument('--appid', type=int, default=730)
p.add_argument('--count', type=int, default=5)
p.add_argument('--output', default='outputs')
a = p.parse_args(); save(a.kind, a.appid, a.output, a.count)
保存先は outputs/steam_achievements.csv と outputs/steam_news.csv、それぞれに .metadata.json が付きます。既存ファイルがある場合は上書きせず停止します。別時点を保存するなら2ファイルを退避するか、--output outputs/another_snapshot を指定してください。
正常な0件でも固定列を保存します。親オブジェクトや配列の欠落、HTTP失敗は0件とは扱わず保存前に停止します。任意項目の欠損は空欄のまま残し、metadataの missing_fields で確認できます。解除率0は欠損とは異なります。実績の achievement_name は内部名で、翻訳済み表示名ではありません。1回の取得結果から過去の解除率推移は分かりません。
ニュースの published_at は公開日時、fetched_at は応答取得時点で、いずれもUTCです。count=5 の取得をニュース全件と扱わないでください。feedlabel・feedname に提供元を保存します。外部ニュースを含むことがあり、すべてを公式アップデート告知とは呼べません。
別のPython実行からCSVを読み直す
次を read_steam_public_csv.py として保存し、取得したファイルを同じ作業フォルダーから確認します。API通信はありません。公開・取得日時、送信条件、CSV hash、保存件数を照合します。
import csv
import hashlib
import json
from pathlib import Path
for name in ["achievements", "news"]:
path = Path(f"outputs/steam_{name}.csv")
metadata = json.loads(path.with_suffix(".metadata.json").read_text(encoding="utf-8"))
assert hashlib.sha256(path.read_bytes()).hexdigest() == metadata["csv_sha256"]
with path.open(encoding="utf-8-sig", newline="") as f:
reader = csv.DictReader(f)
print("列:", reader.fieldnames)
rows = list(reader)
assert len(rows) == metadata["saved_rows"]
print(path.resolve(), len(rows), "件", metadata["sent_parameters"])
print(rows[:2])まとめ
Steam公式Web APIは、AppID確認、同時接続数、ニュース、実績情報などを扱う入口になります。一方で、ストアページ由来の価格やレビュー本文は別APIに分けて考えると、Steamデータ取得全体の見通しがよくなります。