Steam データ活用

Steam appreviewsの使い方【レビュー本文・サマリー取得】カーソル完全ガイド+Python最小サンプル

この記事でできること:SteamのStorefront appreviews(事実上のAPI)を使って、レビュー本文クエリサマリーを取得する方法をまとめます。カーソル(cursor)でのページング、language / filter / purchase_type / day_range などのパラメータ活用、重複除去・CSV保存までをPython最小コードで解説します。

全体像は Steamガイド(入口ページ)、前提のAppIDやストア情報は Step1 / Step3 を参照ください。

appreviewsとは(要点)

  • エンドポイント例:https://store.steampowered.com/appreviews/<APPID>?json=1&language=japanese&filter=recent&num_per_page=100&cursor=*
  • 本文レビューreview)、評価voted_up)、作成/更新時刻購入区分などが取得可能。
  • クエリサマリーquery_summary)に件数・ポジ/ネガ比などの要約が入る。num_per_page=0 でサマリーだけ取得も可能。
  • ページングはカーソル方式。初期値は * を渡し、返ってきた cursor を繋いで次ページへ。
  • 非公開仕様(ストア側の実装)です。将来の変更に備え、堅牢化と監視が前提。

主なパラメータ早見表

パラメータ主な値既定説明
json1JSONで返す指定。必須。
languagejapanese / english / allenglish対象言語を絞る。allは全言語。
filterrecent / updated / allrecentソート傾向(最近投稿/最近更新/全体)。
review_typeall / positive / negativeallレビューの極性で絞り込み。
purchase_typeall / steam / non_steam_purchaseall購入区分(Steam内/外)で絞る。
day_range例:30, 90直近N日分に限定(recent分析に便利)。
num_per_page1–10020前後1ページあたりの件数。最大100。
cursor*(初回)、以降レスポンスの値ページング用。毎回レスポンスのcursorを次リクエストへ。

注意:cursorはURLエンコードが必要な場合がありますが、requestsparamsに渡せば多くは自動対応されます(多重エンコードに注意)。

クイックスタート(Python最小サンプル)

ページングして最大N件を取得(日本語・最近・Steam購入に限定)

import time, requests

BASE = "https://store.steampowered.com/appreviews/{appid}"

def fetch_reviews(appid:int, max_reviews=300, language="japanese",
                  filter_="recent", purchase_type="steam", review_type="all",
                  num_per_page=100, sleep=0.6, day_range=None):
    cursor = "*"
    reviews, total = [], 0
    while True:
        params = {
            "json": 1,
            "language": language,
            "filter": filter_,
            "review_type": review_type,
            "purchase_type": purchase_type,
            "num_per_page": num_per_page,
            "cursor": cursor
        }
        if day_range:
            params["day_range"] = day_range
        r = requests.get(BASE.format(appid=appid), params=params, timeout=30).json()
        batch = r.get("reviews", [])
        if not batch:
            break
        reviews.extend(batch)
        total += len(batch)
        cursor = r.get("cursor")
        if not cursor or total >= max_reviews:
            break
        time.sleep(sleep)  # マナー
    return reviews

# 例:直近90日の日本語レビューを最大300件
rows = fetch_reviews(2420510, max_reviews=300, day_range=90)
print(len(rows), "reviews collected")
print(rows[0].keys())

サマリーだけ欲しい(件数・スコア等)

import requests
def fetch_summary(appid:int, language="all", day_range=None):
    params = {"json": 1, "language": language, "num_per_page": 0}
    if day_range: params["day_range"] = day_range
    r = requests.get(f"https://store.steampowered.com/appreviews/{appid}", params=params, timeout=30).json()
    return r.get("query_summary", {})
summary = fetch_summary(2420510, language="japanese", day_range=90)
print(summary)  # total_positive / total_negative / review_score_desc など

CSV保存+重複除去(recommendationid)

import csv

def to_rows(reviews:list[dict]):
    out = []
    for r in reviews:
        a = r.get("author", {}) or {}
        out.append({
            "recommendationid": r.get("recommendationid"),
            "language": r.get("language"),
            "review": (r.get("review") or "").replace("\\n"," ").strip(),
            "voted_up": r.get("voted_up"),
            "votes_up": r.get("votes_up"),
            "votes_funny": r.get("votes_funny"),
            "weighted_vote_score": r.get("weighted_vote_score"),
            "comment_count": r.get("comment_count"),
            "steam_purchase": r.get("steam_purchase"),
            "received_for_free": r.get("received_for_free"),
            "written_during_early_access": r.get("written_during_early_access"),
            "timestamp_created": r.get("timestamp_created"),
            "timestamp_updated": r.get("timestamp_updated"),
            "author_steamid": a.get("steamid"),
            "author_num_games_owned": a.get("num_games_owned"),
            "author_num_reviews": a.get("num_reviews"),
            "author_playtime_forever": a.get("playtime_forever"),
            "author_playtime_last_two_weeks": a.get("playtime_last_two_weeks"),
        })
    # recommendationid で重複除去
    uniq = {row["recommendationid"]: row for row in out if row.get("recommendationid")}
    return list(uniq.values())

def save_csv(rows, path="reviews.csv"):
    fields = list(rows[0].keys()) if rows else []
    with open(path, "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=fields)
        w.writeheader()
        for row in rows:
            w.writerow(row)

rows = to_rows(fetch_reviews(2420510, max_reviews=300, day_range=90))
save_csv(rows, "reviews.csv")
print("saved reviews.csv:", len(rows))

レビュー項目の読み方(スキーマ抜粋)

キー意味
review本文(改行含む)。
voted_upポジティブ(True)/ネガティブ(False)。
timestamp_created / timestamp_updated投稿/更新のUNIX時刻。
steam_purchaseSteamでの購入か。
received_for_free無料配布/提供の可能性。
written_during_early_accessEA期間の投稿か。
votes_up / votes_funny「参考になった」「おもしろい」投票数。
weighted_vote_score重み付きスコア(文字列)。解析時はfloat化。
developer_response開発元の返信(あれば)。
author.*投稿者のゲーム所持数/プレイ時間など。
recommendationidレビュー固有ID。重複判定に利用。
query_summary.*返却トップにある集計(total_positive/negative 等)。

実務Tips:要約/NLPの前処理

  • 期間固定:例)day_range=90で最近の声に限定。時期ブレを避ける。
  • 言語固定language=japanese で日本語のみ抽出。多言語混在は別記事で比較。
  • 短文/定型の除外:本文長が極端に短いものを除外(例:10文字未満)。
  • 重複除去recommendationidでユニーク化。
  • スコア別サンプリングvoted_upで正/負を分け、要約の偏りを低減。
  • プレイ時間による重みauthor.playtime_foreverを重み付けに使う設計も有効。

可視化やCCUとの突き合わせは CCUの自前収集 と組み合わせると洞察が深まります。

堅牢化のコツ(非公開仕様ゆえ)

  • スリープ&リトライ:短時間連打は避け、0.5–1.0秒を目安に間隔を確保。
  • スキーマチェック:キー存在確認(get多用)で欠損に強く。
  • フェイルセーフ:ページが空のときやcursor欠落時は打ち切り。
  • ログ/監視:仕様変更を検知するため、件数やキー有無を定期ログに。
  • 規約順守:本文の再配布・商用利用・スクレイピング方針に留意(引用は最小限に)。

次のステップ(関連How-to)

  1. 【How-to】同時接続の自前収集と可視化(時系列)
  2. 【How-to】推定オーナー数とタグ集計(SteamSpy)

入口に戻る:Steamガイド / 前の記事:appdetailsの使い方 / さらに前:公式Web APIの使い方 / 最初:AppIDの見つけ方

免責とポリシー:
appreviews は公式ドキュメント外のストアエンドポイントです。挙動は予告なく変わる可能性があります。利用規約・法令・引用ルールを遵守の上でご利用ください。

-Steam, データ活用
-, , , , , ,