へちやぼらけのブログ

総PV数30万を達成!主にVtuber・VRの紹介記事などを執筆する。

VTuber大好きライター !
VTuber評論家として、気になったVTuberの魅力を日々記事にしてます。

【解析】シャドバの戦歴データを取得&解析して、色々やってみる記事①

 シャドバで学ぼう。ウェブスクレイピング・基本統計量

へちやぼらけです。ポケモンデータ解析.pyって知ってる人いますか?

基本統計量並び、回帰分析、クラスタリングなどを学べる神サイトである。俺も、この真似事をしたいと思い記事を書いた。

興味ある方、付き合ってくれ。この記事で「ウェブスクレイピング」に触れ、次回の記事で「基本統計量」、最後の記事で「時系列解析」を扱ってお終いにしようと思う。

ウェブスクレイピングをそもそも知らないという方は、以下の記事を参照して欲しい。

ウェブスクレイピング

ウェブスクレイピングだ。

シャドバのデータを解析していく訳だが、解析に使うデータが無ければ何も始まらない。ウェブスクレイピングを使って、データを取ってこようではないか。

俺が目を付けたサイトは、Shadowverse Logというサイトである。ここには2018年1月~今に至るまでの、各クラスごとの「勝率」「使用率」などが掲載されている。

f:id:hetiyaborake:20190802020221p:plain

同サイトをポチポチとサーフィンすることで、URLの法則性を見つけることができた。下の画像を見て欲しい。↓↓↓↓↓↓

f:id:hetiyaborake:20190802020425p:plain

 単純ですね。今回、重要なのはURLの「・・・2018/1・・・r」の部分。2018/1で、2018年1月1週目の戦歴データを取得することが出来るようだ。そこから、2018/2, 2018/3, ‥, 2018/80 などと数を増やすことで、各週ごとの戦歴データをスクレイピングできそうだ。

同サイトのHTMLソースはこんな感じ↓↓。table id ='table1'、class='pie-analyze-list'の部分に、勝率、使用率などが定義されているらしい。

f:id:hetiyaborake:20190802023739p:plain

さて、頑張ってウェブスクレイピングプログラムを作りました。こんな感じである。(動作確認済。※同サイトのサーバー負荷&このページをご覧になってる”あなた”&”筆者(自分)”の3者を守るため、正直皆さんには気軽に下記プログラムを実行して欲しくは無い。というのが本音だったりもする…。)

#インポートする。
import urllib.request #webにアクセスする際、必要。
import bs4 #主役!
import numpy as np
import pandas as pd
from time import sleep #適宜、休憩を入れ、サーバーへの負荷軽減。

# パラメータ定義
y_START = 1 y_END = 82 url_N = 'https://shadowlog.com/trend/2018/'
#ローテーションのデータを取得する。 df_seiseki_allyear = []
# クローリング&スクレイピング開始
for i in range(y_START , y_END + 1):
sleep(60) #※re はfor文で複数回使えない。毎回初期化する。 import re #↓URL名を指定 ↓数字を文字列に (#0=合計、4=マスター、3=AA、2=A、1=B) url = url_N + str(i) + '/0/r' response = urllib.request.urlopen(url) root = bs4.BeautifulSoup(response.read()) data = [] # ↓HTMLで、tableかつCSSが"pie-analyze-list table1"であるもの。 for pick in root.find_all("table", class_="pie-analyze-list table1"): for match in re.findall(r"<td(.*?)</td>", str(pick)): #findした文字列から、欲しいデータを取得する。(インデックスの細かい調整で愚直に対応・・) if match[0:6] == " align": if match[16:20] == "<b><": temp = match[45:].replace('<b>', '') temp = temp.replace('</b>', '') temp = temp.replace('</span>', '') data.append(temp) else: temp = match[16:].replace('<b>', '') temp = temp.replace('</b>', '') data.append(temp) else: #クラス名。 data.append(match[112:115]) seiseki = np.array(data).reshape(int(len(data)/7),7) df_seiseki = pd.DataFrame(seiseki) df_seiseki.columns = ["クラス","使用率","使用数","勝利数","勝率","先攻勝率","後攻勝率"] df_seiseki['year'] = i #ループの度、アペンドしていく。 df_seiseki_allyear.append(df_seiseki)

まぁ、何のひねりも無いウェブスクレイピングPGMです。'https://shadowlog.com/trend/2018/'+数字でクローリングしながら、BeautifulSopuで、HTMLから必要なデータのみ収集していきます。

ちなみに、クローリングする際は同サイトの負荷にめちゃくちゃ気を使った。クローリングって、度が過ぎるとサーバー攻撃と勘違いされかねない。上のコードではsleep関数を使って、60秒ごとにページを遷移する様に設計した。これなら、普通のweb閲覧と遜色無いはずだ。

皆さんもクローリングする際は要注意だ!

 ◇コード解説

はい。ということで、同PGMについて細かく解説していきます。

 

〇最初(インポート部分)

#インポートする。
import urllib.request   #webにアクセスする際、必要。
import bs4              #主役!
import numpy as np
import pandas as pd
from time import sleep #適宜、休憩を入れ、サーバーへの負荷軽減。

ライブラリは上記のモノを使用する。numpy, pandasはいいですよね…。データを扱うのに必要なライブラリ・モジュールです。

urllib.requestとは、webからデータを取得するためのモジュール。bs4は、webスクレイピングする際、必須とも言えるべき便利なライブラリー。こいつを使えば、簡単にHTMLを取得することができます。

 

〇パラメタ定義

# パラメータ定義
y_START = 1
y_END = 82
url_N = 'https://shadowlog.com/trend/2018/'

#ローテーションのデータを取得する。
df_seiseki_allyear = []

 続いて、パラメタ定義部分。y_START, y_ENDはクローリングするデータのURLの始めと最後を現しております。y_START「1」から始まり、2,3,4,5,‥と1ずつカウントアップしていきながら、y_END「82」に至るまでfor文のループを回し続けます。

カウントアップする数は、クローリングするURLの1部分として使うよ。クローリングの際、共通で使うURLの文字列は、url_Nで定義しています。

さて、それでもって、各週の「勝率」「使用率」を取得する。そのデータはdf_seiseki_allyear に逐次的に保存していきます。(ちなみに、df_って書いてますけど、array型ですねこれ。)

何を言ってるんだコイツ?と思われた方。安心してください。以降のプログラムを見れば、筆者の言いたいことがわかります。

 

〇クローリング部分

# クローリング&スクレイピング開始
for i in range(y_START , y_END + 1):
sleep(60)
     #※re はfor文で複数回使えない。毎回初期化する。
     import re
     #↓URL名を指定 ↓数字を文字列に (#0=合計、4=マスター、3=AA、2=A、1=B)
     url = url_N + str(i) + '/0/r'
     response = urllib.request.urlopen(url)
     root = bs4.BeautifulSoup(response.read())
     data = []

y_START「1」から始まり、2,3,4,5,‥と1ずつカウントアップしていきながら、y_END「82」に至るまでfor文のループを回し続けます。

ループの度にsleep(60)で、60秒待機。サーバーへの負荷を軽減します。アクセスするurl名は、url_N ('https://shadowlog.com/trend/2018/')に、y_START「1」からカウントアップする数字を足す。その後、'/0/r'を付けたモノです。

要するに、https://shadowlog.com/trend/2018/1/0/r'~https://shadowlog.com/trend/2018/82/0/r'までのURLにアクセスしていきます。

f:id:hetiyaborake:20190802020425p:plain

urllib.request.urlopen(url)は、http.client.HTTPResponseのオブジェクトが返されます。そのオブジェクトをresponseに保存させます。(良くわからなくてもOK。URLにアクセスするための入場許可証みたいなモンだと思って下さい。)

次に、root = bs4.BeautifulSoup(response.read())でrootに読み込ませたHTMLデータのルート要素を取得します。

変数rootを使えば、指定したURLのHTMLの先頭~最後まで不可逆的にサーチすることができるんです。

f:id:hetiyaborake:20190802230855p:plain

 

スクレイピング部分

変数rootを使えば、指定したURLのHTMLの先頭~最後まで不可逆的にサーチすることができるんです。ただ、HTMLの先頭~最後まで全部欲しい訳ではありません。私の目的は、クラスごとの「勝率」「使用率」を取得することです。

ということで、欲しいデータを取得するために、欲しいデータの付近にあるHTMLを確認します。

f:id:hetiyaborake:20190802231454p:plain

確認した結果、table id ='table1'、class='pie-analyze-list'の部分に、勝率、使用率などが定義されているらしい。

ということで、プログラムを以下の様に作成した。

    # ↓HTMLで、tableかつCSSが"pie-analyze-list table1"であるもの。
     for pick in root.find_all("table", class_="pie-analyze-list table1"):
          for match in re.findall(r"<td(.*?)</td>", str(pick)):
               #findした文字列から、欲しいデータを取得する。(インデックスの細かい調整で愚直に対応・・)
               if match[0:6] == " align":
                   if match[16:20] == "<b><":
                       temp = match[45:].replace('<b>', '')
                       temp = temp.replace('</b>', '') 
                       temp = temp.replace('</span>', '')
                       data.append(temp)
                   else: 
                       temp = match[16:].replace('<b>', '')
                       temp = temp.replace('</b>', '') 
                       data.append(temp)
               else:
                      #クラス名。
                      data.append(match[112:115])

root.find.all(xxxxx)関数では、HTMLの先頭からサーチしていき、引数に設定した文字列にヒットした場合にその部分のHTMLを返します。上述の通り「勝率」「使用率」は、table id ='table1'、class='pie-analyze-list'の部分に定義されているので、for pick in root.find_all("table", class_="pie-analyze-list table1"):と記述すればOKだ。

試しに、URL=「url_N + "50" + '/0/r'」として、root.find_allでちゃんと該当のHTMLを取得できているか確認だ。

f:id:hetiyaborake:20190802235410p:plain
いい感じに取得できてますね。ただ、それにしても不要なデータが多すぎる。ここから、「勝率」「使用率」をピンポイントで取得しなければならない。筆者は、ここで正規表現(re)を使うことにした。「勝率」「使用率」は、<td>・・・</td>の中に定義されているようだ。

f:id:hetiyaborake:20190802234843p:plain

for match in re.findall(r"<td(.*?)</td>", str(pick)):により、更に<td>の中にある文字列のみ取得する様にしたぞ。

さて、取得した文字列を確認してみよう。↓↓↓

f:id:hetiyaborake:20190802235155p:plain

おお、いい感じ。後は、取得した文字列の何文字目から何文字目までを取得するかプログラムで指定してあげればOK。

取得したデータを確認!

てな感じで取得したデータを、ちょっと整理整頓してエクセルに落としてみたよ。こんな感じです。

f:id:hetiyaborake:20190802235548p:plain

f:id:hetiyaborake:20190802235633p:plain

いいすね。以降の記事では、取得したデータを使って、pythonで解析していきます。お楽しみに。