こんにちは、トイロジックでプログラマをしている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 | 必須 |
|
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 | 必須 |
|
review_type | string | 必須 |
|
レスポンス
success
-クエリが成功すると1を返しますquery_summary
-最初のリクエストに返されますnum_reviews
-この応答に返されたレビューの数review_score
-レビュースコアreview_score_desc
-レビュースコアの説明total_positive
-肯定的なレビューの総数total_negative
-否定的なレビューの総数total_reviews
-クエリパラメータと合致するレビューの総数
cursor
-バッチのレビューを取得するためのcursor
として、次のリクエストに渡す値reviews
recommendationid
-おすすめの固有のIDauthor
steamid
-ユーザーのSteamIDnum_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で購入した場合は truereceived_for_free
-ユーザーが「アプリを無料で入手」のボックスにチェックを入れた場合は truewritten_during_early_access
-早期アクセス期間中にユーザーがゲームのレビューを投稿した場合は truedeveloper_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; }
}
非常にシンプルなコードなので説明することは少ないですが、ポイントとしては以下になります。
- 最初はcursorに*を入れる
- filterはrecent(allを指定するとすべてを取得できずおすすめのレビューだけになるので)
- レスポンスに次のcursorが返るのでそれを使う
- レスポンスのcursorがリクエストで渡したcursorと同じだったらそこで終わり
4.のcursorの終了判定ですが、こちらは特にAPIの説明ページには書いていなかったのですが、実際実行してみたところすべてのレビューをとり終わったときは最後のcursorと同じものが返るようになったのでその判定を入れています。
まとめ
今回はSteam Web API
を使って、ユーザーレビューを取得する方法について解説しました。いかがでしたでしょうか。Steam以外にもConfluenceやSlackなどいろいろなサービスがWeb APIによる自動化の手法を提供しています。こういった自動化は、開発効率向上に重要ですのでぜひ色々試してみてください。