はじめに
Python歴1か月半とまだまだ経験不足ですが、大好きなクラロワというスマホゲームのデータ収集をするコードを書いてみたので温かい目でご覧ください。また、「もっとこうしたらスマートだよ~」とか「こう書いたらもっと早く動くよ」というアドバイスがあればコメントで教えていただけると嬉しいです。
また、簡単にクラロワの説明をしておきます。クラロワは高校生を中心に流行しているスマホの対戦型ゲームです。2018年からEsportとしてプロ選手が発足し、現在も各国のプロ選手が対戦するクラロワリーグというものが行われています。今回はそのプロ選手の対戦データを収集しました。
クラロワ公式ホームページ
プログラムの概要
クラロワのAPIを用いて、データを収集しファイルに保存、これを定期的に実行します。
具体的には、
①APIを使う準備を整える
②プロ選手のリスト等、プログラムを組むうえでの前段階の準備
③対戦データを抽出する関数の作成
④DataFrameを作り、ファイルに保存する関数の作成
⑤上記の関数を用いて、実行するコードを書く
⑥定期的に実行できるようにする
の順に行いました。
実際のプログラムコード
それでは実際にコードを書いていきます。
①APIを使う準備を整える
そもそもAPIとは?
Wikipediaより引用
APIとは、サービスがサービスを利用するアプリケーションに提供するためのインターフェースである。APIの重要な役割はサービス提供者が公式に仕様を定義し管理している操作方法(インターフェース)を提供することである。APIは多くの場合アプリケーションを構築する言語と同じ言語のライブラリーとして(HTTPやXMLなどを組み合わせたプロトコル形式の場合もある)提供され、サービス開発者によって提供・管理される。
簡単にいうと、サービスが外部の利用者に向けて公開してくれているデータをまとめたものです。
今回はクラロワの公式が公開してくれているAPIを利用しています。
APIの使い方
ここは他の方が丁寧にまとめていただいているので、そちらを参考にしてください。
僕はこの方の記事を参考にしました。
②プロ選手のリスト等、プログラムを組むうえでの前段階の準備
まず、後々便利になるように
①プロ選手のリスト
②選手のタグと選手名の辞書
③必要なモジュールのインストール
④URLとアクセスキー
を準備します。
####### ①プロ選手のリスト作成
list= ["みかん坊や","天GOD","kota","RAD","ライキジョーンズ",
"Jack","きたっしゃん","だに","けんつめし",
"Rorapolon","焼き鳥","ユイヒイロ","Blossom","kk19212",
"れいや","HANE×HANE","Lewis","ピラメキ","天ぷら"]
####### ②選手のタグと選手名の辞書作成
dic={"みかん坊や":"%232VYJYJ09","天GOD":"%232G0QUGLU","kota":"%23889VQ8JP","RAD":"%238QRCJQ9Y","ライキジョーンズ":"%2398Q8LPQ9",
"Jack":"%23YRVL9U98","きたっしゃん":"%23P8RLY0V9","だに":"%238LJVVGJP","けんつめし":"%23PQRR0CG9",
"Rorapolon":"%239JPRJ9R","焼き鳥":"%232Y8GL0V2","ユイヒイロ":"%23R2GRQPCJ","Blossom":"%238Q20LRC8Y","kk19212":"%23RU2CC2LG",
"れいや":"%232LRVG0C8","HANE×HANE":"%238Y088VU8U","Lewis":"%238Q020U0U","ピラメキ":"%232YGQGY92V","天ぷら":"%238Q2V2CGR",}
もう少しきれいにできそうな気もしますが、泥臭く手作業で入れました。ちなみに、タグの#を%23に変更しないといけないみたいです。
####### ③必要なモジュールのインストール
import os
import json
import requests
import pandas as pd
####### ④URLとアクセスキー
URL = 'https://api.clashroyale.com/v1'
access_key ="" # 自分が受け取ったキーを入れてください
③対戦データを抽出する関数の作成
def battle_info(name):
target_api = URL + "/players/"
playerTag = dic[name]
url = target_api+playerTag+"/battlelog"
headers = {
"content-type": "application/json; charset=utf-8",
"cache-control": "max-age=60",
"authorization": "Bearer %s" % access_key}
r = requests.get(url,headers=headers)
data = r.json()
return data
__name__ == '__battle_info__'
ここで前準備で辞書を作成しておいたメリットが出ましたね。
ここはAPIドキュメントを見ていただいた方が早いのですが、player名を変数とした関数を作り、対戦情報を出力させ、その結果を返り値として設定します。
因みに、これを実行して成形すると以下のようなデータが返ってきます。
[
{
"type": "PvP",
"battleTime": "20190519T153026.000Z",
"isLadderTournament": false,
"arena": {
"id": 54000015,
"name": "Master I"
},
"gameMode": {
"id": 72000006,
"name": "Ladder"
},
"deckSelection": "collection",
"team": [
{
"tag": "#2Q98GVP9V",
"name": "Scott",
"startingTrophies": 5230,
"trophyChange": -23,
"crowns": 0,
"kingTowerHitPoints": 5304,
"princessTowersHitPoints": [
2603
],
"clan": {
"tag": "#9C988GCR",
"name": "\u30af\u30e9\u30f3\u5bfe\u6226\u304c\u3093\u3070(\u2312\u25bd\u2312)",
"badgeId": 16000010
},
"cards": [
{
"name": "Royal Giant",
"id": 26000024,
"level": 13,
"maxLevel": 13,
"iconUrls": {
"medium": "https://api-assets.clashroyale.com/cards/300/mnlRaNtmfpQx2e6mp70sLd0ND-pKPF70Cf87_agEKg4.png"
}
},
{
"name": "Tornado",
"id": 28000012,
"level": 7,
"maxLevel": 8,
"iconUrls": {
"medium": "https://api-assets.clashroyale.com/cards/300/QJB-QK1QJHdw4hjpAwVSyZBozc2ZWAR9pQ-SMUyKaT0.png"
}
},
{
### 省略###
"opponent": [
{
"tag": "#VRP29PGV",
"name": "Wellington",
"startingTrophies": 5223,
"trophyChange": 30,
"crowns": 1,
"kingTowerHitPoints": 5832,
"princessTowersHitPoints": [
1818,
3140
],
"clan": {
"tag": "#LPPYP8R",
"name": "Troca de Cartas",
"badgeId": 16000014
},
"cards": [
{
"name": "Witch",
"id": 26000007,
"level": 5,
"maxLevel": 8,
"iconUrls": {
"medium": "https://api-assets.clashroyale.com/cards/300/cfwk1vzehVyHC-uloEIH6NOI0hOdofCutR5PyhIgO6w.png"
}
},
{
"name": "Giant Skeleton",
"id": 26000020,
"level": 7,
"maxLevel": 8,
"iconUrls": {
"medium": "https://api-assets.clashroyale.com/cards/300/0p0gd0XaVRu1Hb1iSG1hTYbz2AN6aEiZnhaAib5O8Z8.png"
}
},
### 省略###
④DataFrameを作り、ファイルに保存する関数の作成
続いて、↑のデータから必要な部分を抜き出し、ファイルに書き込む関数を作成します。
また、後に選手名を引数として渡せるように、予めファイルと選手名の辞書を作成しています。
(もっとスマートにやる方法がありそうですが、、、)
具体的処理の流れとしては、
- ファイル名と選手名の辞書を作る
- dataframeの列名columns1や、空のリストdatalist、ファイル名を入れるpathを用意
- ifで条件分岐し、ファイルがあれば読み込み、なければ空のdataframeを作成
- mydecklist:自分が使ったカード8枚、opodecklist:敵が使ったカード8枚、decktype:対戦の種類、mycrown:自分のクラウン数 opocrown:相手のクラウン数を取得。※25というのは、APIとして提供される対戦数が上限25試合分だったため、設定しています。
- ↑の値のうち、特定の試合目(newnum)の値を取得しdatakistに入れる。
- DataFrameにデータを取り込み、重複行の削除、時間順に並び替えを行い、ファイルに書き込み
となっています。
filedic={"みかん坊や":"battledata_mikan.csv","天GOD":"battledata_tengod.csv","kota":"battledata_kota.csv","RAD":"battledata_rad.csv","ライキジョーンズ":"battledata_raiki.csv",
"Jack":"battledata_jack.csv","きたっしゃん":"battledata_kitassyan.csv","だに":"battledata_dani.csv","けんつめし":"battledata_kentsumeshi.csv",
"Rorapolon":"battledata_rorapolon.csv","焼き鳥":"battledata_yakitori.csv","ユイヒイロ":"battledata_yuihiro.csv","Blossom":"battledata_blossom.csv","kk19212":"battledata_kk.csv",
"れいや":"battledata_reiya.csv","HANE×HANE":"battledata_hanehane.csv","Lewis":"battledata_lewis.csv","ピラメキ":"battledata_pirameki.csv","天ぷら":"battledata_tempura.csv"}
def selfdeck_list(name):
columns1=["type","my","opponent","result","time"]
ba = battle_info(name) #変数にすることで処理速度が速くなる
datalist = []
path = filedic[name]
if os.path.isfile(path):
df = pd.read_csv(path) # 既存ファイルがあれば読み込む
else:
df = pd.DataFrame(columns=columns1) # なければ雛型を作成
for newnum in range(0,25):
mydecklist = [ ba[decknum]["team"][0]["cards"][numindeck]["name"] for decknum in range(0,25) for numindeck in range(0,8) ]
opodecklist= [ ba[decknum]["opponent"][0]["cards"][numindeck]["name"] for decknum in range(0,25) for numindeck in range(0,8) ]
decktype = [ ba[decknum]["type"] for decknum in range(0,25)]
mycrowns = [ ba[decknum]["team"][0]["crowns"] for decknum in range(0,25)]
opocrowns= [ ba[decknum]["opponent"][0]["crowns"] for decknum in range(0,25)]
time = [ ba[decknum]["battleTime"] for decknum in range(0,25)]
a = decktype[int(newnum)]
b = tuple(mydecklist[int(newnum*8):int(newnum*8+8)])
c = tuple(opodecklist[int(newnum*8):int(newnum*8+8)])
if mycrowns[int(newnum)] > opocrowns[int(newnum)]:
winorlose = "win"
elif mycrowns[int(newnum)] < opocrowns[int(newnum)]:
winorlose = "lose"
else:
winorlose = "draw"
d = winorlose
e = time[int(newnum)][:15]
data = [a,b,c,d,e]
datalist.append(data)
df_new = pd.DataFrame(data=datalist,columns=columns1 )
df = df.append(df_new)
df = df.drop_duplicates(["time"]).reset_index(drop=True)
df = df.sort_values('time')
df.to_csv(path,index=False)
__name__ == '__selfdeck_list__'
⑤上記の関数を用いて、実行するコードを書く
ここまでくればほぼ完成です。
あとは、for文で回して、全選手に対して関数を実行します。
allplayer_battlelog= [selfdeck_list(pla) for pla in list]
allplayer_battlelog
⑥定期的に実行できるようにする
これを手動で行ってもデータ収集ができるのですが、面倒なので自動で行えるようにします。pythonのscheduleライブラリを使ってもできるようですが、自分はなぜかうまくいかなかったので、タスクスケジューラを用いて行いました。これも詳しく書いてくださっている方がいるので自分からは説明しません。
以上で完成です。お疲れさまでした。
感想
初めて1から自分で考えながら書いたコードだったので、詰まる部分もありましたが、web質問板などのおかげもあり、なんとか完成できました。実装速度をもっと早くしたり、きれいなコードを書いたりとまだまだできる部分はあるのでしょうが、とりあえずはちゃんと動くものを作れたことに満足しています。
これからは、これらのデータを用いてこれから色々分析していきたいなと思います。
ご覧いただきありがとうございました。