Steamデータ取得 データ取得

Steam公式Web APIの使い方|アプリ一覧・同時接続数・実績・ニュース取得

2025年10月17日

この記事では、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.csvoutputs/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/GetAppListSteam Web APIキーの取得と保管方法

取得したいもの主な方法
アプリ一覧IStoreService/GetAppList/v1(Web APIキー必須)
現在の同時接続数ISteamUserStats/GetNumberOfCurrentPlayers
実績情報ISteamUserStats 系API
ゲームニュースISteamNews/GetNewsForApp
価格・レビュー本文Storefront API側で扱う

アプリ一覧を取得する

先に python -m pip install requests python-dotenv を実行し、キー管理手順に従って作業フォルダーの .envSTEAM_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順で、各項目には appidnamelast_modifiedprice_change_number が含まれます。1回の取得上限は5万件です。全件取得では前回最後の appid を次回の last_appid に渡します。

アプリ一覧はAppIDの確認に使えます。後続の appdetailsappreviews、同時接続数取得ではAppIDが必要です。なお、後続の ISteamUserStatsISteamNews は別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側の appdetailsappreviews を使うほうが自然です。

実績の全体解除率・ニュースを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.csvoutputs/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 の取得をニュース全件と扱わないでください。feedlabelfeedname に提供元を保存します。外部ニュースを含むことがあり、すべてを公式アップデート告知とは呼べません。

別の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データ取得全体の見通しがよくなります。

-Steamデータ取得, データ取得