feature image

2018年11月2日 | ブログ記事

pythonでwebスクレイピングしよう【アドベントカレンダー2018 9日目】

前書き

こんにちはのidatenです。今回はwebスクレイピングというものを紹介したいと思います。

そもそもwebスクレイピングって?

ウェブスクレイピング(英: Web scraping)とは、ウェブサイトから情報を抽出するコンピュータソフトウェア技術のこと。
(出典:Wikipedia)

ということで、webサイトにある情報を取ってきて情報解析を行いたいときに用いるものです。
webスクレイピングの技術を使えば、web上に公開されているデータなら基本的になんでも取ってくることができます。しかし、法律上の注意事項やそもそもそのサイトがwebスクレイピングを禁止している場合もあるので、自分でよく調べてから実践してください。
今回は、webページにあるhtmlを取得して解析することで必要なデータを取ってきてそれを処理したいと思います。

webサイトからデータを抽出して何が楽しいの?

公開されているデータを取得して、自分の好きなようにデータ処理をすることができます。例えば、ホームページ内の見出しの一覧をとってきたり、中には、自然言語処理のために文章をとってきたりというのをコンピュータに自動でやらせることができます。

実際にやってみる

前置きはこれぐらいにして、実際にwebスクレイピングをしてみたいと思います。今回使用する言語はpythonです。ここではpythonの詳しい使い方やwebスクレイピングで用いるライブラリの詳しい説明は行わないので自分でやってみたい方は、別のサイトや書籍を参考にしてください。

今回は、僕が最近ハマっている競技プログラミングを題材にして、AtCoderというオンラインで参加できるプログラミングコンテストのサイトから、レーティングとパフォーマンスという数値をとってきて、それをグラフ化するまでやってみたいと思います。

とりあえず、僕のユーザーページのコンテスト成績表というに行くと次のように表示されています。(日現在)

----------2018-10-31-15.04.59

表で表示されて、その要素の一つに新Ratingとパフォーマンスという値がありますね。これらの値を取ってこようという算段です。ソースコードを表示させて見てみると、htmlでは表を表示するための<table>タグが使われています。新Ratingの方は、次のように、ratingに応じた色をつけるために、<span>タグで囲まれていますが、このように、これだけについているcssセレクタやタグがあるとデータの抽出処理をしやすいのですが、パフォーマンスの値が入っている部分は表の要素を入れるための<td>タグで囲まれているだけです。

<td>1458</td>
<td><span class='user-cyan'>1316</span></td>

htmlパーサでは、ページのhtmlを取得してそれらに処理を行うことで、求めているデータを抽出する必要がありますがこのままではパフォーマンスを抽出できません。ここで、pandasというpythonのデータ処理を行うライブラリを用いることで、<table>タグに用いらる表の要素名<th>タグを指定するとその中身である<td>の要素を返してくれる機能を使います。

これによってデータを抽出できそうですね。

では実際のコーディングに入っていきましょう。
先ほどのユーザーページを見ると、urlが

https://beta.atcoder.jp/users/(ユーザー名)/history

となっているので、urlは次のように指定できます。

usr = "idaten"
url = "https://beta.atcoder.jp/users/" + usr +  "/history"

urlを指定してpandasをインポートした上でpd.read_html(url)を実行するとページ内の表をすべて取得しDataFrameのリストとして返してくれます。但し、pandasを次のように実行するには色々あらかじめpandasとlxml、html5lib、beautifulsoup4をインストールしている必要があります。それは各自で行っておいてください。

import pandas as pd
usr = "idaten"
url = "https://beta.atcoder.jp/users/" + usr +  "/history"
dfs = pd.read_html(url)
print(dfs)

これを実行すると、次のような出力がされるので、ちゃんと動いていることが確かめられます。(長いので一部抜粋)

    Rank Performance NewRating  Diff  
0   1739        -165         5     -  
1    777         560        45   +40  
2   1382         621       106   +61  
3    911         434       144   +38  
4    644           -         -     -  
5    641        1122       302  +158  
6    485        1370       547  +245  
7    427        1026       630   +83  
8    374        1050       698   +68  
9    515        1541       857  +159  
10  1159        1077       887   +30  
11   674        1153       923   +36  
12   562           -         -     -  
13   491        1255       969   +46  
14   188        1278      1011   +42  
15   499        1308      1050   +39  
16   663        1512      1114   +64  
17   458           -         -     -  
18   654           -         -     -  
19   497        1585      1178   +64  
20   144        1600      1234   +56  
21   486        1707      1298   +64  
22   198           -         -     -  
23   457        1458      1316   +18  

dataframeとして返してくれるので、簡単に表の値を取得できます。しかしよくみて見ると、所々表の中身が"-"となっているところがあります。これは別にhtmlの解析に失敗したとかではなくAtCoderのコンテストの中にはunratedな(rateのつかない)コンテストも開催されていることが原因です。ここで、とりあえず、perfomanceだけ取ってきて要素が"-"のものは取り除く処理を行ってみます。
次のようなコードを実行するとできます。

import pandas as pd
usr = "idaten"
url = "https://beta.atcoder.jp/users/" + usr +  "/history"
dfs = pd.read_html(url)
data = "Performance"
pef = dfs[0][[data]]
l = len(pef.index)
for i in range(l):
    if(pef.at[i,data]=='-'):
        pef.drop(i,inplace=True)

pef = pef.reset_index(drop=True)
print(pef)
pef = pef.astype(int)
pef.rename(columns={usr:data},inplace=True)

次のような操作をしています。dfsリストから"Performance"の要素を取り出してpef変数に入れたうえで、要素の中身が"-"であった場合はdropという要素をリストから消去するメソッドを実行しています。dropの2番目の引数は消去した時、間のを詰めることを指定しています。また、printのあとの行は後にグラフに表示させるために要素を文字列から数字に変換しています。(""をにしている)そして、列の名前の変更もしています。
これを実行すると次のように出力されるので、正しく動いていることがわかります。

   Performance
0         -165
1          560
2          621
3          434
4         1122
5         1370
6         1026
7         1050
8         1541
9         1077
10        1153
11        1255
12        1278
13        1308
14        1512
15        1585
16        1600
17        1707

同様の操作をratingの方もするのですが、同じコードかくのは面倒なので、先ほどのコードを関数化してperformanceとratingをつの関数で扱います。これで、グラフに表示するためのデータが揃いましたが、ただそれらを表示するだけだと面白くないので、ついでにperformanceの平均値の推移も作ってこれをmatplotlibというデータを可視化するためのライブラリを用いて表示してみます。

それをコーディングしたものが次のソースコードです。ratedコンテストの参加回数ごとのperformanceなどの値をグラフにしています。

import pandas as pd
import matplotlib as mlp
import matplotlib.pyplot as plt

def get_usr_data(usr,data):
    url = "https://beta.atcoder.jp/users/" + usr +  "/history"
    dfs = pd.read_html(url)
    pef = dfs[0][[data]]
    l = len(pef.index)
    for i in range(l):
        if(pef.at[i,data]=='-'):
            pef.drop(i,inplace=True)

    pef = pef.reset_index(drop=True)
    pef = pef.astype(int)
    pef.rename(columns={usr:data},inplace=True)
    return pef

data = ["Performance","NewRating"]
usrname = "idaten"

data_all = get_usr_data(usrname,data[0])
for i in range(1,len(data)):
    data_all = pd.concat([data_all,get_usr_data(usrname,data[i])],axis=1)

perf_sum_list = [data_all["Performance"][0]]
perf_ave_list = []
for i in range(1,len(data_all["Performance"])):
    perf_sum_list.append((perf_sum_list[i-1]+data_all["Performance"][i]))

for i in range(len(perf_sum_list)):
    perf_ave_list.append(perf_sum_list[i]/(i+1))

perf_ave = pd.DataFrame(perf_ave_list)
perf_ave = perf_ave.astype(int)
perf_ave.rename(columns={0:"perf_ave"},inplace=True)
print(perf_ave)
data_all = pd.concat([data_all,perf_ave],axis=1)

data_all.plot()
plt.style.use('ggplot')
data_all.plot(marker="o")
plt.title("AtCoderのperf,rate推移")
plt.xlabel("回数") 
plt.show()
#data_all.to_csv("atcoder_data.csv")

これを実行すると次のようなグラフが表示されてます。
----------2018-10-31-16.35.40
これでwebスクレイピングを無事することができました。
ちなみに最後のコメントアウトを外すとcsv形式でデータを出力することもできます。

終わりに

いかがでしたでしょうか。今回はpythonをつかってwebスクレイピングをしてみました。これで好きなデータをwebページから取ってきた情報解析をすることができることがわかったと思います。

明日はonkyiの記事です、お楽しみに!

idaten icon
この記事を書いた人
idaten

普段は競プロをしています。

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

ERC20トークンを用いた宝探しゲーム(真)の提案【アドベントカレンダー2018 10日目】 feature image
2018年11月3日
ERC20トークンを用いた宝探しゲーム(真)の提案【アドベントカレンダー2018 10日目】
Azon icon Azon
2021年4月18日
ベズー係数とN項の拡張ユークリッドの互除法
0214sh7 icon 0214sh7
2018年12月12日
多重スリーブの世界と,各種進捗報告。
Silviase icon Silviase
2018年12月23日
LogicProXでのサラウンド設定,オーケストラ用テンプレ作成,その他の小ネタ
SolunaEureka icon SolunaEureka
2018年12月16日
ICPCアジア地区横浜大会参加記【アドベントカレンダー2018 52日目】
eiya icon eiya
2018年11月30日
Flutterでスマホアプリを作ってみ(た | よう)【アドベントカレンダー2018 37日目】
Fourmsushi icon Fourmsushi
記事一覧 タグ一覧 Google アナリティクスについて