こんにちは、トイロジックでプログラマをしているR.Mです。本記事では、Steamが提供しているWeb APIの一つ「ユーザーレビュー・リストとの取得」の紹介をします。

ゲーム開発において、ユーザーレビューをもとにアップデート計画を立てることもよくあります。その際に、すべてのレビューを目で見て判断するのではなく、解析用のツールにかける必要があるため、このようなAPIが必要になります。今回は特定のアプリの全レビューをとってくる処理をC#で書いてみようと思います。
 

APIのリファレンス

SteamのAPIリファレンスページはこちらになります。認証なども必要ないので単純に、上記URLにパラメータを指定するだけで取得できます

上記URLよりリクエストパラメータを抜粋します (2024/08/14時点での情報)

URL store.steampowered.com/appreviews/<appid>?json=1

 

リクエスト

名前 必須 説明
cursor string 必須 レビューは20個のバッチで返されるため、最初のセットには「*」を渡し、次のセットの応答の中に返される「cursor」の値を渡します。 cursor値には、クエリ文字列で使用するためにURLEndodedが必要な文字が含まれている場合があることに注意してください。
day_range string 必須 現在から n 日前までに参考になったレビューを検索する範囲です。 「all」フィルターにのみ適用されます。 (最大値は 365。)
filter string 必須
  • recent – 作成日時による並び替え
  • updated – 最終更新日時による並び替え
  • all – (デフォルト) 有用性による並び替え、day_range パラメータに基づくスライディングウィンドウ、常に結果を返します。

cursor でレビューをページングする場合、最終的に空のレスポンスリストを受信するように recent オプション、または updated オプションを選択します。

filter_offtopic_activity number デフォルトでは、トピずれのレビュー (別名 “レビュー荒らし”) はフィルターで除外され、このAPIでは返されません。 それらを含めるには0を渡します。 こちらを参照してください。
language string 必須 https://partner.steamgames.com/documentation/languages を参照する (そして API 言語コードリストを使用する) か、すべてのレビューに「all」を渡します。
num_per_page string 必須 デフォルトでは、最大 20 件のレビューを返します。 このパラメーターに基づいてさらにレビューを返すことができます(最大は 100 レビュー)
purchase_type string 必須
  • all – すべてのレビュー
  • non_steam_purchase – Steam 上で製品を購入しなかったユーザーによるレビュー
  • steam – Steam 上で製品を購入したユーザーによるレビュー(デフォルト)
review_type string 必須
  • all – すべてのレビュー (デフォルト)
  • positive – ポジティブなレビューのみ
  • negative – ネガティブなレビューのみ

 

レスポンス

  • success-クエリが成功すると1を返します
  • query_summary -最初のリクエストに返されます
    • num_reviews-この応答に返されたレビューの数
    • review_score-レビュースコア
    • review_score_desc-レビュースコアの説明
    • total_positive-肯定的なレビューの総数
    • total_negative-否定的なレビューの総数
    • total_reviews-クエリパラメータと合致するレビューの総数
  • cursor-バッチのレビューを取得するためのcursorとして、次のリクエストに渡す値
  • reviews
    • recommendationid -おすすめの固有のID
    • author
      • steamid -ユーザーのSteamID
      • num_games_owned -ユーザーが所有するゲームの数
      • num_reviews -ユーザーが投稿したレビューの数
      • playtime_forever -このアプリに記録された通算プレイ時間
      • playtime_last_two_weeks -過去 2 週間でこのアプリに記録されたプレイ時間
      • playtime_at_review – レビューが書かれた時点でのプレイ時間
      • last_played – ユーザーが最後にプレイした時間
    • language -レビュー作成時にユーザーが指定した言語
    • review -レビュー内容のテキスト
    • timestamp_created -レビューの作成日(unixタイムスタンプ)
    • timestamp_updated -レビューの最終更新日(unixタイムスタンプ)
    • voted_up true は、肯定的なおすすめであることを意味します
    • votes_up -このレビューが参考になったと評価したユーザー数
    • votes_funny -このレビューを面白いと評価したユーザー数
    • weighted_vote_score – 参考になったスコア
    • comment_count -このレビューに対するコメント数
    • steam_purchase -ユーザーがゲームをSteamで購入した場合は true
    • received_for_free -ユーザーが「アプリを無料で入手」のボックスにチェックを入れた場合は true
    • written_during_early_access -早期アクセス期間中にユーザーがゲームのレビューを投稿した場合は true
    • developer_response – 開発元からのテキスト(あれば)
    • timestamp_dev_responded – Unixタイムスタンプでの開発元からの回答時間(あれば)

 

AppIdを確認する方法

URLに入れる、appidはSteamDBというWebサイトで確認できます。検索ボックスにタイトル入れると検索結果が出てくるのでそこで確認できます。例として弊社開発の大規模アクションゲーム『Warlander』のappidは「1675900」になります。


 

ブラウザで実行してみる

このAPIはクエリパラメータ指定するだけのGETメソッドなのでWebブラウザでも確認できます。

https://store.steampowered.com/appreviews/1675900?json=1&language=all&purchase_type=non_steam_purchaseにアクセスしてみると、それっぽい情報が表示されます。

ただし、パラメータにも書いていますが、一回のリクエストでとってこれるレビューの最大数は100件までになります。それ以上取得する場合は、レスポンスにあるcursorを使って、続きをとってくる必要があります。
 

C#で全てのレビューを取得する

上述の情報をもとに、C#でWarlanderのすべてのレビューを取得してみようと思います。レビューが何万件ものあるようなアプリでテストする場合、実行時間がかかる可能性があるので注意してください。ちなみに、リクエスト/レスポンスの構造体は、上記のパラメータをもとにChatGPTに書いてもらいました。便利です。

環境は

  • Visual Studio 2022

    • .NET 8.0
    • トップレベルステートメントで記述
  • NuGetで以下のパッケージを使用
    • NewtonSoftJson 13.0.3
    • レスポンスのJsonをデシリアライズするのに使用
  • RestSharp 112.0.0
    • HTTPリクエスト処理を簡単に行うため

  


using Newtonsoft.Json;
using RestSharp;
using System.Reflection;
 
// -----パラメータ-------
// WarlanderのsteamAppId
var steamAppId = 1675900;
var client = new RestClient("https://store.steampowered.com");
 
async Task<SteamAppReviewsResponse> GetReviewAsync(SteamAppReviewsRequest req)
{
    var restReq = new RestRequest($"appreviews/{steamAppId}", Method.Get);
    // URLにあったjson=1を入れる処理
    restReq.AddParameter("json", 1);
 
    foreach (var p in req.GetType().GetProperties())
    {
        // JsonPropertyAttributeで指定した名前でクエリパラメータを設定する
        var attr = p.GetCustomAttribute<JsonPropertyAttribute>();
        if (attr == null)
            continue;
        var str = p.GetValue(req)?.ToString();
        if (string.IsNullOrEmpty(str) == false)
            restReq.AddQueryParameter(attr.PropertyName, str);
    }
 
    var res = await client.GetAsync(restReq);
    return JsonConvert.DeserializeObject<SteamAppReviewsResponse>(res.Content);
}
 
var reviews = new List<SteamAppReview>();
var cursor = "*";
// 念のためカーソルが空かもチェックする
while (string.IsNullOrEmpty(cursor) == false)
{
    var review = await GetReviewAsync(new SteamAppReviewsRequest
    {
        Filter = "recent",
        PurchaseType = "all",
        Language = "all",
        // コール回数を抑えるために最大数にする
        NumPerPage = 100,
        Cursor = cursor,
        FilterOfftopicActivity = 0
    });
    reviews.AddRange(review.Reviews);
    // 終わったら以前使ったカーソルと同じものが返ってきたら終了
    if (cursor == review.Cursor)
        break;
    cursor = review.Cursor;
}
 
// jsonで保存する
File.WriteAllText("review.json", JsonConvert.SerializeObject(reviews));
 
public class SteamAppReviewsRequest
{
    public int SteamAppId { get; set; }
 
    /// <summary>
    /// recent – 作成日時による並び替え
    /// updated – 最終更新日時による並び替え
    /// all – (デフォルト) 有用性による並び替え、day_range パラメータに基づくスライディングウィンドウ、常に結果を返します。
    /// cursor でレビューをページングする場合、最終的に空のレスポンスリストを受信するように recent オプション、または updated オプションを選択します。
    /// </summary>
    [JsonProperty("filter")]
    public string Filter { get; set; }
 
    /// <summary>
    /// https://partner.steamgames.com/documentation/languages を参照する (そして API 言語コードリストを使用する) か、すべてのレビューに「all」を渡します。
    /// </summary>
    [JsonProperty("language")]
    public string Language { get; set; }
 
    /// <summary>
    /// 現在から n 日前までに参考になったレビューを検索する範囲です。 「all」フィルターにのみ適用されます。 (最大値は 365。)
    /// </summary>
    [JsonProperty("day_range")]
    public int? DayRange { get; set; }
 
    /// <summary>
    /// レビューは20個のバッチで返されるため、最初のセットには「*」を渡し、次のセットの応答の中に返される「cursor」の値を渡します。 cursor値には、クエリ文字列で使用するためにURLEndodedが必要な文字が含まれている場合があることに注意してください。
    /// </summary>
    [JsonProperty("cursor")]
    public string Cursor { get; set; }
 
    /// <summary>
    /// all – すべてのレビュー (デフォルト)
    /// positive – ポジティブなレビューのみ
    /// negative – ネガティブなレビューのみ
    /// </summary>
    [JsonProperty("review_type")]
    public string ReviewType { get; set; }
 
    /// <summary>
    /// all – すべてのレビュー
    /// non_steam_purchase – Steam 上で製品を購入しなかったユーザーによるレビュー
    /// steam – Steam 上で製品を購入したユーザーによるレビュー(デフォルト)
    /// </summary>
    [JsonProperty("purchase_type")]
    public string PurchaseType { get; set; }
 
    /// <summary>
    /// デフォルトでは、最大 20 件のレビューを返します。 このパラメーターに基づいてさらにレビューを返すことができます(最大は 100 レビュー)
    /// </summary>
    [JsonProperty("num_per_page")]
    public int? NumPerPage { get; set; }
 
    /// <summary>
    /// デフォルトでは、トピずれのレビュー (別名 "レビュー荒らし") はフィルターで除外され、このAPIでは返されません。 それらを含めるには0を渡します。 こちらを参照してください。
    /// </summary>
    [JsonProperty("filter_offtopic_activity")]
    public int? FilterOfftopicActivity { get; set; }
}
 
 
public class SteamAppReviewsResponse
{
    /// <summary>
    /// クエリが成功すると1を返します
    /// </summary>
    [JsonProperty("success")]
    public int Success { get; set; }
 
    /// <summary>
    /// 最初のリクエストに返されます
    /// </summary>
    [JsonProperty("query_summary")]
    public SteamAppReviewSummary QuerySummary { get; set; }
 
    /// <summary>
    /// バッチのレビューを取得するためのcursorとして、次のリクエストに渡す値
    /// </summary>
    [JsonProperty("cursor")]
    public string Cursor { get; set; }
 
    /// <summary>
    /// レビューのリスト
    /// </summary>
    [JsonProperty("reviews")]
    public List<SteamAppReview> Reviews { get; set; }
}
 
public class SteamAppReviewSummary
{
    /// <summary>
    /// この応答に返されたレビューの数
    /// </summary>
    [JsonProperty("num_reviews")]
    public int NumReviews { get; set; }
 
    /// <summary>
    /// レビュースコア
    /// </summary>
    [JsonProperty("review_score")]
    public int ReviewScore { get; set; }
 
    /// <summary>
    /// レビュースコアの説明
    /// </summary>
    [JsonProperty("review_score_desc")]
    public string ReviewScoreDesc { get; set; }
 
    /// <summary>
    /// 肯定的なレビューの総数
    /// </summary>
    [JsonProperty("total_positive")]
    public int TotalPositive { get; set; }
 
    /// <summary>
    /// 否定的なレビューの総数
    /// </summary>
    [JsonProperty("total_negative")]
    public int TotalNegative { get; set; }
 
    /// <summary>
    /// クエリパラメータと合致するレビューの総数
    /// </summary>
    [JsonProperty("total_reviews")]
    public int TotalReviews { get; set; }
}
 
public class SteamAuthor
{
    /// <summary>
    /// ユーザーのSteamID
    /// </summary>
    [JsonProperty("steamid")]
    public string SteamId { get; set; }
    /// <summary>
    /// ユーザーが所有するゲームの数
    /// </summary>
    [JsonProperty("num_games_owned")]
    public int NumGamesOwned { get; set; }
    /// <summary>
    /// ユーザーが投稿したレビューの数
    /// </summary>
    [JsonProperty("num_reviews")]
    public int NumReviews { get; set; }
    /// <summary>
    /// このアプリに記録された通算プレイ時間
    /// </summary>
    [JsonProperty("playtime_forever")]
    public int PlaytimeForever { get; set; }
    /// <summary>
    /// 過去 2 週間でこのアプリに記録されたプレイ時間
    /// </summary>
    [JsonProperty("playtime_last_two_weeks")]
    public int PlaytimeLastTwoWeeks { get; set; }
    /// <summary>
    /// レビューが書かれた時点でのプレイ時間
    /// </summary>
    [JsonProperty("playtime_at_review")]
    public int PlaytimeAtReview { get; set; }
    /// <summary>
    /// ユーザーが最後にプレイした時間
    /// </summary>
    [JsonProperty("last_played")]
    public int LastPlayed { get; set; }
}
 
public class SteamAppReview
{
    /// <summary>
    /// おすすめの固有のID
    /// </summary>
    [JsonProperty("recommendationid")]
    public string RecommendationId { get; set; }
 
    /// <summary>
    /// レビューの著者
    /// </summary>
    [JsonProperty("author")]
    public SteamAuthor Author { get; set; }
 
    /// <summary>
    /// レビュー作成時にユーザーが指定した言語
    /// </summary>
    [JsonProperty("language")]
    public string Language { get; set; }
 
    /// <summary>
    /// レビュー内容のテキスト
    /// </summary>
    [JsonProperty("review")]
    public string ReviewText { get; set; }
 
    /// <summary>
    /// レビューの作成日(unixタイムスタンプ)
    /// </summary>
    [JsonProperty("timestamp_created")]
    public long TimestampCreated { get; set; }
 
    /// <summary>
    /// レビューの最終更新日(unixタイムスタンプ)
    /// </summary>
    [JsonProperty("timestamp_updated")]
    public long TimestampUpdated { get; set; }
 
    /// <summary>
    /// true は、肯定的なおすすめであることを意味します
    /// </summary>
    [JsonProperty("voted_up")]
    public bool VotedUp { get; set; }
 
    /// <summary>
    /// このレビューが参考になったと評価したユーザー数
    /// </summary>
    [JsonProperty("votes_up")]
    public int VotesUp { get; set; }
 
    /// <summary>
    /// このレビューを面白いと評価したユーザー数
    /// </summary>
    [JsonProperty("votes_funny")]
    public int VotesFunny { get; set; }
 
    /// <summary>
    /// 参考になったスコア
    /// </summary>
    [JsonProperty("weighted_vote_score")]
    public float WeightedVoteScore { get; set; }
 
    /// <summary>
    /// このレビューに対するコメント数
    /// </summary>
    [JsonProperty("comment_count")]
    public int CommentCount { get; set; }
 
    /// <summary>
    /// ユーザーがゲームをSteamで購入した場合は true
    /// </summary>
    [JsonProperty("steam_purchase")]
    public bool SteamPurchase { get; set; }
 
    /// <summary>
    /// ユーザーが「アプリを無料で入手」のボックスにチェックを入れた場合は true
    /// </summary>
    [JsonProperty("received_for_free")]
    public bool ReceivedForFree { get; set; }
 
    /// <summary>
    /// 早期アクセス期間中にユーザーがゲームのレビューを投稿した場合は true
    /// </summary>
    [JsonProperty("written_during_early_access")]
    public bool WrittenDuringEarlyAccess { get; set; }
 
    /// <summary>
    /// 開発元からのテキスト(あれば)
    /// </summary>
    [JsonProperty("developer_response")]
    public string DeveloperResponse { get; set; }
 
    /// <summary>
    /// Unixタイムスタンプでの開発元からの回答時間(あれば)
    /// </summary>
    [JsonProperty("timestamp_dev_responded")]
    public long? TimestampDevResponded { get; set; }
}

非常にシンプルなコードなので説明することは少ないですが、ポイントとしては以下になります。

  1. 最初はcursorに*を入れる
  2. filterはrecent(allを指定するとすべてを取得できずおすすめのレビューだけになるので)
  3. レスポンスに次のcursorが返るのでそれを使う
  4. レスポンスのcursorがリクエストで渡したcursorと同じだったらそこで終わり

4.のcursorの終了判定ですが、こちらは特にAPIの説明ページには書いていなかったのですが、実際実行してみたところすべてのレビューをとり終わったときは最後のcursorと同じものが返るようになったのでその判定を入れています。

 

まとめ

今回はSteam Web APIを使って、ユーザーレビューを取得する方法について解説しました。いかがでしたでしょうか。Steam以外にもConfluenceやSlackなどいろいろなサービスがWeb APIによる自動化の手法を提供しています。こういった自動化は、開発効率向上に重要ですのでぜひ色々試してみてください。

著者紹介 R.M
2015年にトイロジックに新卒入社。現在は「Warlander」でセーブデータやツール、UIを担当。

トイロジックでは現在、一緒に働くプログラマーを募集しています。

不明点などもお気軽にお問い合わせくださいフルリモート採用も行っております、ご応募お待ちしております!