スポンサーリンク

Steamでゲームをプレイした時間を「Google Apps Script」で記録する

Playtime-Google-Spreadsheet2Steam

 この記事は、PCゲーム Advent Calendar 2018の23日目の記事です。

 PCゲーム販売プラットフォームの「Steam」で、自身のプロフィールページ中段に「最近のアクティビティ 過去2週間で ●●時間」と表示されているのはご存じでしょうか? Steamには、2週間前から現在までの間に遊んだゲームのプレイ時間を、プロフィールページに表示する機能があります。プレイヤーはこの表記を見て、2週間の間に何時間ゲームを遊んだのか把握できます。

steam-recently-playtime

Steamプロフィールページより

 しかし、実際には「●月●日に何時間ぐらいPCゲームで遊んでいたのか」知りたかったり、「平日のプレイ状況を俯瞰したい」と調べたくなったりします。Steamが提供するWebAPIでは、2週間に何時間遊んだかを取得するAPIしか公開されていないため、知ることができません。つまり、そういったことを知りたくなったら、どうにかしてシステムを自作する必要があります。自作の場合、アプリケーションサーバやデータベースを用意する必要があり、技術の選定から開発まで時間が掛かって、面倒です。

 そこで今回、Google スプレッドシートGoogle Apps Scriptを用いて、アプリケーションサーバやデータベースなどを用意せずに、1日に何時間ゲームを遊んだか記録できるシステムを構築しました(大げさ)。

steam-life-log

 この記事では、どうやって構築したか紹介します。

Steamでの活動状況をスクレイピングで取得する

 まず、プレイヤーがゲームを遊んでいるかどうか、活動状況を取得しなければなりません。活動状況を取得する「GetPlayerSummaries/v2/」というAPIが公開されていますが(参考)、今回はWebスクレイピング技術を利用しました。

 具体的には、自身のSteamプロフィールページに着目します。Steamには「オンライン」「ゲーム中」という2つの大きなステータスが存在します。オンラインは「Steamを起動している状態」。ゲーム中は、「Steamでゲームを遊んでいる状態」です。

 オンラインでは「プロフィールページ」や「フレンド&チャットリスト」の名前は青色の線で囲まれて表示され、ゲーム中は緑色で囲まれます。この振る舞いをどう実装しているのか、ソースコードで確認します。オンラインのとき、プロフィールページのソースコードの一部はこうなります。

<div class="profile_rightcol">
    <div class="responsive_status_info">
		<div class="profile_in_game persona online">
			<div class="profile_in_game_header">現在オンラインです。</div>
		</div>
	</div>
</div>

 ゲーム中のとき、プロフィールページのソースコードの一部はこうなります。

<div class="profile_rightcol">
	<div class="responsive_status_info">
		<div class="profile_in_game persona in-game">
		<div class="profile_in_game_header">ゲーム中です</div>
		<div class="profile_in_game_name">Arma 3</div>
	</div>
</div>

 つまり、「profile_in_game persona」の後ろが「online」か「in-game」かで、プロフィールページのアイコンを囲う色が変わっています。1分毎にこの値をチェックして「online」であればゲームを遊んでいない、「in-game」であれば、ゲームを遊んでおり、「profile_in_game_name」の要素からゲーム名を取得できます。

「Google Apps Script」を書いてみる

Google-Apps-Scripts

Google Apps Scripts

 Google Apps Script(以下、GAS)とは、Googleの各サービス(Google スプレッドシート、Google フォーム、 Google カレンダー 、Gmailなど)と連携できるJavaScriptベースのスクリプト言語です。誰でも無料で利用できます。今回は、外部ライブラリ「Parser」を用いて「online」か「in-Game」の値を取得し、その値を基にスプレッドシートに記録するコードを書きました。

 公開されている「Parser」という外部ライブラリは、スクレイピングで取得したい値の前後を指定すれば取得できる便利なライブラリです(参考)。外部ライブラリをGASで利用するには、左上のメニューから「リソース」「ライブラリ」を選び、ライブラリ固有のIDを入力します。

 ちなみにParserのIDは「M1lugvAXKKtUxn_vdAG9JZleS6DrsjUUV」です。

Google-Apps-Scripts-resourceGoogle-Apps-Scripts-Resource-Library

「Google Apps Script」で作成した関数を1分毎に実行する

 作成したコードを定期的に実行するにはトリガー機能を利用します。左上の吹き出し時計マークを選択すると、トリガー一覧ページが表示されるので、作成した関数を自動で実行するための新規トリガーを追加します。


Google-Apps-Script-trigger-button

Google-Apps-Script-trigger これで、作成した関数が1分おきに自動で実行されるようになり、スプレッドシートへの記録が始まります。

Steamの1日あたりのプレイ時間をグラフで可視化する

 というわけで、2018年12月22日のプレイ時間を実際に集計したので、グラフにして見てみます。とりあえずゲームで遊んだ時間を可視化するため、起動していないタイミングを「0」、起動しているタイミングを「1」とする列と、何時の記録か、hour関数で抽出した列を用意しました。

Playtime-Google-Spreadsheet2

 そして、全データを選択し「データ」メニューにある「ピボットテーブル…」でピボットテーブルを作成します。行を抽出した時間に、値を「0」と「1」の列に設定して、生成された表を選び、グラフを作成します。

 Steam-playtime-pivot-table

 というわけで2018年12月22日のゲームを遊んだ時間をグラフ化したものがこちらになります。steam-playtime-graph-20181222 3時から6時まで、15時から18時まで、20時から0時すぎまでゲームをやっているということが簡単に分かりました。昼夜逆転してますね。

まとめ

 GoogleスプレッドシートとGoogle Apps Scriptを用いて、ゲームのプレイ時間を記録してみました。

 実は「1日に何回ゲームを遊んだか記録したい」という気持ちがあるので、システムは完成していません。グラフを見れば「3回」となんとなく分かるのですが、機械的にどう抽出できるのか、考えています。「こうすればできるよー」というアイデアを待っています。

 そして、改善できる点としては、Webスクレイピングで取得しているので、SteamのWebサイトに繋がらないとデータを取得できません。12月22日のスクレイピングは、3回失敗していたので、分岐処理を追加し、取得できなかった旨を記録する必要がありますね。

 今回は自分のアカウントで試しましたが、Steamでの活動状況を公開しているユーザーのゲームプレイ時間を監視することが簡単にできてしまいます。そういうのがされたくない方は、公開設定を見直しておいたほうが良いのかもしれません。

',b.captions&&s){var u=J("figcaption");u.id="baguetteBox-figcaption-"+t,u.innerHTML=s,l.appendChild(u)}e.appendChild(l);var c=J("img");c.onload=function(){var e=document.querySelector("#baguette-img-"+t+" .baguetteBox-spinner");l.removeChild(e),!b.async&&n&&n()},c.setAttribute("src",r),c.alt=a&&a.alt||"",b.titleTag&&s&&(c.title=s),l.appendChild(c),b.async&&n&&n()}}function X(){return M(o+1)}function D(){return M(o-1)}function M(e,t){return!n&&0<=e&&e=k.length?(b.animation&&O("right"),!1):(q(o=e,function(){z(o),V(o)}),R(),b.onChange&&b.onChange(o,k.length),!0)}function O(e){l.className="bounce-from-"+e,setTimeout(function(){l.className=""},400)}function R(){var e=100*-o+"%";"fadeIn"===b.animation?(l.style.opacity=0,setTimeout(function(){m.transforms?l.style.transform=l.style.webkitTransform="translate3d("+e+",0,0)":l.style.left=e,l.style.opacity=1},400)):m.transforms?l.style.transform=l.style.webkitTransform="translate3d("+e+",0,0)":l.style.left=e}function z(e){e-o>=b.preload||q(e+1,function(){z(e+1)})}function V(e){o-e>=b.preload||q(e-1,function(){V(e-1)})}function U(e,t,n,o){e.addEventListener?e.addEventListener(t,n,o):e.attachEvent("on"+t,function(e){(e=e||window.event).target=e.target||e.srcElement,n(e)})}function W(e,t,n,o){e.removeEventListener?e.removeEventListener(t,n,o):e.detachEvent("on"+t,n)}function G(e){return document.getElementById(e)}function J(e){return document.createElement(e)}return[].forEach||(Array.prototype.forEach=function(e,t){for(var n=0;n","http://www.w3.org/2000/svg"===(e.firstChild&&e.firstChild.namespaceURI)}(),m.passiveEvents=function i(){var e=!1;try{var t=Object.defineProperty({},"passive",{get:function(){e=!0}});window.addEventListener("test",null,t)}catch(n){}return e}(),function a(){if(r=G("baguetteBox-overlay"))return l=G("baguetteBox-slider"),u=G("previous-button"),c=G("next-button"),void(d=G("close-button"));(r=J("div")).setAttribute("role","dialog"),r.id="baguetteBox-overlay",document.getElementsByTagName("body")[0].appendChild(r),(l=J("div")).id="baguetteBox-slider",r.appendChild(l),(u=J("button")).setAttribute("type","button"),u.id="previous-button",u.setAttribute("aria-label","Previous"),u.innerHTML=m.svg?f:"<",r.appendChild(u),(c=J("button")).setAttribute("type","button"),c.id="next-button",c.setAttribute("aria-label","Next"),c.innerHTML=m.svg?g:">",r.appendChild(c),(d=J("button")).setAttribute("type","button"),d.id="close-button",d.setAttribute("aria-label","Close"),d.innerHTML=m.svg?p:"×",r.appendChild(d),u.className=c.className=d.className="baguetteBox-button",function n(){var e=m.passiveEvents?{passive:!1}:null,t=m.passiveEvents?{passive:!0}:null;U(r,"click",x),U(u,"click",E),U(c,"click",C),U(d,"click",B),U(l,"contextmenu",A),U(r,"touchstart",T,t),U(r,"touchmove",N,e),U(r,"touchend",L),U(document,"focus",P,!0)}()}(),S(e),function s(e,a){var t=document.querySelectorAll(e),n={galleries:[],nodeList:t};return w[e]=n,[].forEach.call(t,function(e){a&&a.filter&&(y=a.filter);var t=[];if(t="A"===e.tagName?[e]:e.getElementsByTagName("a"),0!==(t=[].filter.call(t,function(e){if(-1===e.className.indexOf(a&&a.ignoreClass))return y.test(e.href)})).length){var i=[];[].forEach.call(t,function(e,t){var n=function(e){e.preventDefault?e.preventDefault():e.returnValue=!1,H(i,a),I(t)},o={eventHandler:n,imageElement:e};U(e,"click",n),i.push(o)}),n.galleries.push(i)}}),n.galleries}(e,t)},show:M,showNext:X,showPrevious:D,hide:j,destroy:function e(){!function n(){var e=m.passiveEvents?{passive:!1}:null,t=m.passiveEvents?{passive:!0}:null;W(r,"click",x),W(u,"click",E),W(c,"click",C),W(d,"click",B),W(l,"contextmenu",A),W(r,"touchstart",T,t),W(r,"touchmove",N,e),W(r,"touchend",L),W(document,"focus",P,!0)}(),function t(){for(var e in w)w.hasOwnProperty(e)&&S(e)}(),W(document,"keydown",F),document.getElementsByTagName("body")[0].removeChild(document.getElementById("baguetteBox-overlay")),w={},h=[],o=0}}})
タイトルとURLをコピーしました