Steam データ活用

Steam appreviewsの使い方|レビュー本文・評価サマリーをPythonで取得

2025年10月17日

この記事では、Steam Storefrontの appreviews を使って、Steamレビュー本文と評価サマリーをPythonで取得する方法を解説します。

Steamレビュー分析では、レビュー本文、好評・不評、投稿日時、プレイ時間、購入区分などを取得できると、評価傾向や不満点、アップデート後の反応を分析しやすくなります。

本記事では、レビューサマリーの取得、本文レビューの取得、cursor を使ったページング、重複除去、CSV保存までを扱います。

この記事でできること

  • Steamの appreviews で取得できる情報を理解する
  • レビュー件数や好評・不評のサマリーを取得する
  • レビュー本文、投稿日時、プレイ時間、購入区分を取得する
  • cursor を使って複数ページのレビューを取得する
  • 日本語レビューや直近レビューに絞って取得する
  • レビューをDataFrame化してCSV保存する
  • レビュー分析で使うときの注意点を理解する

想定読者

  • SteamレビューをPythonで取得したい方
  • Steamレビュー分析記事の元データを作りたい方
  • 好評・不評レビューを分けてテキスト分析したい方
  • レビュー件数や評価傾向を時系列で見たい方
  • Steamのレビュー本文をCSVとして保存したい方

appreviewsとは

appreviews は、Steamストア上のユーザーレビューをJSON形式で取得できるエンドポイントです。AppIDを指定すると、対象タイトルのレビュー本文や評価情報を取得できます。

https://store.steampowered.com/appreviews/2246340?json=1

主に以下の情報を取得できます。

情報主なフィールド内容
レビュー本文reviewユーザーが投稿したレビュー本文
好評・不評voted_upTrue が好評、False が不評
投稿日時timestamp_createdレビュー投稿日時のUNIX時刻
更新日時timestamp_updatedレビュー更新日時のUNIX時刻
購入区分steam_purchaseSteamで購入したかどうか
無料提供received_for_free無料提供を受けたかどうか
早期アクセスwritten_during_early_access早期アクセス期間中の投稿かどうか
投稿者情報authorプレイ時間、所持ゲーム数、投稿レビュー数など
レビューIDrecommendationidレビュー固有ID。重複除去に使える
評価サマリーquery_summaryレビュー件数や好評・不評件数など

ただし、appreviews はSteam公式Web APIとして細かく保証されたエンドポイントではなく、Steamストア側のJSONレスポンスを利用する形式です。将来的に仕様が変わる可能性があるため、例外処理や欠損処理を入れて使うのがおすすめです。

事前準備

この記事では、SteamのAppIDが分かっている前提で進めます。AppIDの確認方法は以下の記事で解説しています。

SteamのAppIDを確認する方法|URL・検索・API・Python一括取得

また、価格や発売日などのストア情報を取得したい場合は、以下の記事も参考になります。

Steam appdetailsの使い方|価格・発売日・対応OS・ジャンルをPythonで取得

使用ライブラリ

HTTPリクエストには requests、表形式の整形には pandas を使います。

pip install -U requests pandas

主なパラメータ

appreviews では、言語、並び順、レビュー種別、購入区分、取得件数などをパラメータで指定できます。

パラメータ主な値説明
json1JSON形式で返す指定
languagejapaneseenglishallレビュー言語
filterrecentupdatedall取得するレビューの並び・範囲
review_typeallpositivenegative好評・不評の絞り込み
purchase_typeallsteamnon_steam_purchase購入区分の絞り込み
num_per_page1〜1001回のリクエストで取得する件数
cursor*、またはレスポンスの cursorページング用カーソル
day_range30、90など直近N日分に絞り込む場合に使う

cursor はページングに使います。初回は * を指定し、2ページ目以降はレスポンスに含まれる cursor を次のリクエストに渡します。

レビューサマリーだけを取得する

まず、レビュー本文ではなく、件数や評価サマリーだけを取得します。num_per_page=0 にすると、本文レビューを取得せずに query_summary を確認できます。

import requests


def fetch_review_summary(
    appid: int,
    language: str = "all",
    review_type: str = "all",
    purchase_type: str = "all",
    day_range: int | None = None,
) -> dict:
    """
    Steam appreviews からレビューサマリーを取得する。
    """
    url = f"https://store.steampowered.com/appreviews/{appid}"

    params = {
        "json": 1,
        "language": language,
        "review_type": review_type,
        "purchase_type": purchase_type,
        "num_per_page": 0,
    }

    if day_range is not None:
        params["day_range"] = day_range

    response = requests.get(
        url,
        params=params,
        timeout=30,
    )

    response.raise_for_status()

    data = response.json()

    return data.get("query_summary", {})


appid = 2246340

summary = fetch_review_summary(
    appid=appid,
    language="japanese",
    day_range=90,
)

print(summary)

出力には、レビュー件数、好評数、不評数、レビュー評価の説明などが含まれます。まず対象タイトルにレビューが十分あるか確認したい場合に便利です。

1ページ分のレビュー本文を取得する

次に、レビュー本文を1ページ分だけ取得します。

def fetch_review_page(
    appid: int,
    cursor: str = "*",
    language: str = "japanese",
    filter_: str = "recent",
    review_type: str = "all",
    purchase_type: str = "all",
    num_per_page: int = 100,
    day_range: int | None = None,
) -> dict:
    """
    Steam appreviews から1ページ分のレビューを取得する。
    """
    url = f"https://store.steampowered.com/appreviews/{appid}"

    params = {
        "json": 1,
        "cursor": cursor,
        "language": language,
        "filter": filter_,
        "review_type": review_type,
        "purchase_type": purchase_type,
        "num_per_page": num_per_page,
    }

    if day_range is not None:
        params["day_range"] = day_range

    response = requests.get(
        url,
        params=params,
        timeout=30,
    )

    response.raise_for_status()

    return response.json()


appid = 2246340

data = fetch_review_page(
    appid=appid,
    language="japanese",
    num_per_page=10,
)

reviews = data.get("reviews", [])

print("取得件数:", len(reviews))
print("次のcursor:", data.get("cursor"))

if reviews:
    print(reviews[0].keys())
    print(reviews[0].get("review")[:100])

レスポンスには、レビュー一覧と次ページ取得用の cursor が含まれます。

cursorで複数ページを取得する

大量のレビューを取得する場合は、cursor を使ってページングします。初回は *、次回以降はレスポンスの cursor を指定します。

import time


def fetch_reviews(
    appid: int,
    max_reviews: int = 500,
    language: str = "japanese",
    filter_: str = "recent",
    review_type: str = "all",
    purchase_type: str = "all",
    num_per_page: int = 100,
    day_range: int | None = None,
    sleep_sec: float = 0.7,
) -> list[dict]:
    """
    cursorを使って複数ページのレビューを取得する。
    """
    cursor = "*"
    all_reviews = []
    seen_ids = set()

    while len(all_reviews) < max_reviews:
        data = fetch_review_page(
            appid=appid,
            cursor=cursor,
            language=language,
            filter_=filter_,
            review_type=review_type,
            purchase_type=purchase_type,
            num_per_page=num_per_page,
            day_range=day_range,
        )

        reviews = data.get("reviews", [])

        if not reviews:
            break

        for review in reviews:
            recommendationid = review.get("recommendationid")

            if recommendationid in seen_ids:
                continue

            seen_ids.add(recommendationid)
            all_reviews.append(review)

            if len(all_reviews) >= max_reviews:
                break

        next_cursor = data.get("cursor")

        if not next_cursor or next_cursor == cursor:
            break

        cursor = next_cursor

        time.sleep(sleep_sec)

    return all_reviews


appid = 2246340

reviews = fetch_reviews(
    appid=appid,
    max_reviews=300,
    language="japanese",
    day_range=90,
)

print("取得レビュー数:", len(reviews))

この関数では、recommendationid を使って重複除去しています。また、短時間に連続アクセスしないように sleep_sec で待機時間を入れています。

レビューをDataFrameに変換する

取得したレビューはネストしたJSONになっているため、分析しやすいようにDataFrameへ変換します。

import pandas as pd


def reviews_to_dataframe(reviews: list[dict]) -> pd.DataFrame:
    """
    appreviewsのレビュー一覧をDataFrameに変換する。
    """
    rows = []

    for review in reviews:
        author = review.get("author", {}) or {}

        rows.append({
            "recommendationid": review.get("recommendationid"),
            "language": review.get("language"),
            "review": (review.get("review") or "").replace("\n", " ").strip(),
            "voted_up": review.get("voted_up"),
            "votes_up": review.get("votes_up"),
            "votes_funny": review.get("votes_funny"),
            "weighted_vote_score": review.get("weighted_vote_score"),
            "comment_count": review.get("comment_count"),
            "steam_purchase": review.get("steam_purchase"),
            "received_for_free": review.get("received_for_free"),
            "written_during_early_access": review.get("written_during_early_access"),
            "timestamp_created": review.get("timestamp_created"),
            "timestamp_updated": review.get("timestamp_updated"),
            "author_steamid": author.get("steamid"),
            "author_num_games_owned": author.get("num_games_owned"),
            "author_num_reviews": author.get("num_reviews"),
            "author_playtime_forever": author.get("playtime_forever"),
            "author_playtime_last_two_weeks": author.get("playtime_last_two_weeks"),
            "author_playtime_at_review": author.get("playtime_at_review"),
        })

    df = pd.DataFrame(rows)

    if not df.empty:
        df["created_at"] = pd.to_datetime(
            df["timestamp_created"],
            unit="s",
            utc=True,
            errors="coerce",
        )

        df["updated_at"] = pd.to_datetime(
            df["timestamp_updated"],
            unit="s",
            utc=True,
            errors="coerce",
        )

        for col in [
            "author_playtime_forever",
            "author_playtime_last_two_weeks",
            "author_playtime_at_review",
        ]:
            if col in df.columns:
                df[col.replace("author_", "") + "_hours"] = df[col] / 60

    return df


df_reviews = reviews_to_dataframe(reviews)

print(df_reviews.head())
print(df_reviews.shape)

プレイ時間は分単位で返るため、分析では時間単位に変換しておくと見やすくなります。

CSVに保存する

DataFrameに変換したレビューはCSVとして保存できます。

appid = 2246340

output_path = f"steam_reviews_{appid}.csv"

df_reviews.to_csv(
    output_path,
    index=False,
    encoding="utf-8-sig",
)

print("保存しました:", output_path)
print("件数:", len(df_reviews))

utf-8-sig で保存すると、Excelで開いたときに日本語が文字化けしにくくなります。

好評・不評を分けて取得する

review_type を指定すると、好評レビューと不評レビューを分けて取得できます。

positive_reviews = fetch_reviews(
    appid=2246340,
    max_reviews=200,
    language="japanese",
    review_type="positive",
    day_range=365,
)

negative_reviews = fetch_reviews(
    appid=2246340,
    max_reviews=200,
    language="japanese",
    review_type="negative",
    day_range=365,
)

df_positive = reviews_to_dataframe(positive_reviews)
df_negative = reviews_to_dataframe(negative_reviews)

print("好評:", len(df_positive))
print("不評:", len(df_negative))

好評・不評を分けて取得すると、「魅力として語られている要素」と「不満点として語られている要素」を比較しやすくなります。

期間を指定して取得する

day_range を指定すると、直近N日分のレビューに絞り込めます。アップデート後の反応や直近の不満点を見たい場合に便利です。

recent_reviews = fetch_reviews(
    appid=2246340,
    max_reviews=500,
    language="japanese",
    filter_="recent",
    day_range=90,
)

df_recent = reviews_to_dataframe(recent_reviews)

print("直近90日のレビュー数:", len(df_recent))

レビュー分析記事では、全期間の傾向と直近期間の傾向を分けて見ると、評価変化を読み取りやすくなります。

購入区分を指定する

purchase_type を指定すると、Steamで購入したユーザーのレビューや、Steam外購入のレビューを分けて取得できます。

steam_purchase_reviews = fetch_reviews(
    appid=2246340,
    max_reviews=300,
    language="japanese",
    purchase_type="steam",
)

df_steam_purchase = reviews_to_dataframe(steam_purchase_reviews)

print("Steam購入レビュー:", len(df_steam_purchase))

通常のレビュー分析では purchase_type="all" で全体を見ても問題ありませんが、購入経路による違いを見たい場合は絞り込みが有効です。

取得したレビューの基本集計

取得したレビューを簡単に集計してみます。

summary_table = df_reviews.agg(
    review_count=("recommendationid", "count"),
)

positive_rate = df_reviews["voted_up"].mean()

print("レビュー件数:", len(df_reviews))
print("好評率:", round(positive_rate * 100, 1), "%")

if "playtime_at_review_hours" in df_reviews.columns:
    print("レビュー時点のプレイ時間中央値:", df_reviews["playtime_at_review_hours"].median())

voted_up の平均を取ると、取得した範囲内の好評率を計算できます。ただし、これは取得条件に依存するため、Steamストア上の総合評価と完全に一致するとは限りません。

レビュー項目の読み方

分析でよく使う項目は以下です。

列名意味分析での使い方
reviewレビュー本文テキスト分析、要約、トピック抽出
voted_up好評・不評高評価/低評価レビューの分類
created_at投稿日時月次・日次のレビュー件数推移
steam_purchaseSteam購入か購入経路による傾向比較
received_for_free無料提供かレビュー解釈時の補助情報
written_during_early_access早期アクセス中の投稿か正式リリース前後の比較
votes_up参考になった票数代表レビュー抽出の補助
weighted_vote_score重み付き投票スコアレビューの注目度の参考
playtime_at_review_hoursレビュー時点のプレイ時間短時間レビュー・長時間レビューの比較
playtime_forever_hours累計プレイ時間やり込み層のレビュー傾向を見る

レビュー取得時の注意点

appreviews を使うときは、以下の点に注意してください。

  • 公式Web APIとして細かく保証された仕様ではない
    将来的にレスポンス形式や取得仕様が変わる可能性があります。
  • 取得条件で結果が変わる
    languagefilterreview_typepurchase_typeday_range によって件数や内容が変わります。
  • cursorはそのままparamsに渡す
    手動で多重エンコードすると取得に失敗する場合があります。
  • 短時間に大量リクエストしない
    ページング取得では sleep を入れて間隔を空けます。
  • レビュー本文の扱いに注意する
    本文の長文転載や過度な再配布は避け、分析・引用は必要最小限にします。
  • 取得した好評率は取得条件に依存する
    Steamストア上の表示評価と完全に一致するとは限りません。
  • 個人ユーザーの特定につながる扱いは避ける
    分析では個別ユーザーではなく、レビュー全体の傾向を見ることを基本にします。

レビュー分析での活用例

取得したSteamレビューは、以下のような分析に活用できます。

  • 月次・日次のレビュー件数推移を見る
  • 高評価レビューで多い魅力要素を抽出する
  • 低評価レビューで多い不満点を抽出する
  • アップデート後にレビュー傾向が変わったか確認する
  • プレイ時間別にレビュー傾向を比較する
  • 日本語レビューと日本語以外レビューを比較する
  • レビュー本文からトピック抽出や感情分析を行う

たとえば、低評価レビューだけを抽出して頻出語やトピックを確認すると、ユーザーが不満に感じている要素を整理しやすくなります。

次に読む記事

まとめ

この記事では、Steam Storefrontの appreviews を使って、レビュー本文と評価サマリーをPythonで取得する方法を紹介しました。

appreviews では、レビュー本文、好評・不評、投稿日時、プレイ時間、購入区分などを取得できます。cursor を使ってページングすることで、複数ページのレビューを取得し、CSVとして保存できます。

Steamレビュー分析では、取得条件を明確にしたうえで、レビュー件数、好評率、本文、プレイ時間、時期を組み合わせて見ることが重要です。次は、同時接続数を自前で継続収集し、レビュー傾向と合わせて確認する方法を扱います。

-Steam, データ活用