デデンネの総合順位をスクレイピングして求めた話
2月の初旬に好きなポケモンがgoogleで投票できましたが、皆さんはデデンネに投票してくださいましたでしょうか?
さて、2020/02/27はポケモンの日。前述した投票の結果も、この日の23時に公開されました。公開された情報は、総合TOP30および各地方のTOP30のみです。残念ながらデデンネは総合順位30位内には入ることができず、総合順位はわかりませんでした。しかし、カロス地方にて7位に輝き、その投票数は21,691でした。他の地方のTOP30の子たちの投票数を見るに、デデンネと同等数の投票数を獲得しているポケモンたちはどうやら15位以内に収まっているようでした。デデンネの総合順位が出せる...!ということで出してみました。
ついでに学べたこともあったので、合わせてこの記事に載せようと思います。
BeautifulSoupでは取得できない?
requests + BeautifulSoupを使ってスクレイピングをした経験があるので、安易に今回もこちらを使ってスクレイピングをしようとしました。
from urllib.request import urlopen from bs4 import BeautifulSoup soup=BeautifulSoup(urlopen(url))
上記のコードで拾ってこれで図鑑番号、ポケモン名と投票数が入っているものが取れると思ったのですが、実際に図鑑番号、ポケモン名と投票数が入るはずのところには違うものが入っていました。
<div class="ranking__text"> <p class="ranking__number">No.<span>{{data.no}}</span></p> <h3 class="ranking__name">{{data.jp}}</h3> <p class="ranking__vote"><span>{{data.vote}}</span>票</p> </div>
すごい何処かで見たことあるやつだと思いました。vueと同じようなやつだなと思いました。(jsのテンプレートエンジンですね。)
jsのテンプレートエンジンを使っている画面は取得できない...?
ひとまず確認のため、最近触っているvueでサンプルページを作ってBeautifulSoupに読ませてみました。
読み込んでみると以下が帰ってきました。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta content="IE=edge" http-equiv="X-UA-Compatible"/> <meta content="width=device-width,initial-scale=1.0" name="viewport"/> <link href="/favicon.ico" rel="icon"/> <title>sample</title> <link as="script" href="/js/app.js" rel="preload"/> <link as="script" href="/js/chunk-vendors.js" rel="preload"/> </head> <body> <noscript> <strong>We're sorry but sample doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> <script src="/js/chunk-vendors.js" type="text/javascript"></script> <script src="/js/app.js" type="text/javascript"></script> </body> </html>
<div id="app"></div>
がそのままなところから、確かに図と同じものは読み込めてなさそうなことがわかりました。ソースコード見るに、index.htmlを読んだだけのように見えます。
We're sorry but sample doesn't work properly without JavaScript enabled. Please enable it to continue. ここも重要ですね、表示されてはいないものの、JavaScriptがないと十分に動かないとの注意書きがされています。(index.htmlにある文章なので、スクレイピングしたから出たわけではないです。)
requests_html
先ほどの調査で、requests + BeautifulSoupだけではindex.htmlなるjsの実行前のものが取得されてしまい、jsで書き換えているようなページはきちんと取得できないということがわかりました。見てるページをきちんと取得するためには、index.htmlだけでなくjsも読み込み反映させたものを取得する必要があります。
pythonにはこのようなことをしてくれるライブラリがいくつかあり、Seleniumが特に有名ですが、今回色々用意しなくても楽に実装できそうなrequests_htmlを使うことにしました。
from requests_html import HTMLSession session = HTMLSession() r = session.get(url) # ブラウザエンジンでHTMLを生成させる r.html.render()
ちょっとBeautifulSoupと比べると手間ですが、これでjs反映後のhtmlが取得できるはずです。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="/favicon.ico"> <title>sample</title> <link href="/js/app.js" rel="preload" as="script"> <link href="/js/chunk-vendors.js" rel="preload" as="script"> <style type="text/css"> h3[data-v-469af010] { margin: 40px 0 0; } ul[data-v-469af010] { list-style-type: none; padding: 0; } li[data-v-469af010] { display: inline-block; margin: 0 10px; } a[data-v-469af010] { color: #42b983; } </style> <style type="text/css"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style> </head> <body> <noscript> <strong>We're sorry but sample doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"><img alt="Vue logo" src="/img/logo.82b9c7a5.png"> <div data-v-469af010="" class="hello"><h1 data-v-469af010="">Welcome to Your Vue.js App</h1> <p data-v-469af010=""> For a guide and recipes on how to configure / customize this project,<br data-v-469af010=""> check out the <a data-v-469af010="" href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>. </p> <h3 data-v-469af010="">Installed CLI Plugins</h3> <ul data-v-469af010=""> <li data-v-469af010=""><a data-v-469af010="" href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li> <li data-v-469af010=""><a data-v-469af010="" href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li> </ul> <h3 data-v-469af010="">Essential Links</h3> <ul data-v-469af010=""> <li data-v-469af010=""><a data-v-469af010="" href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li> <li data-v-469af010=""><a data-v-469af010="" href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a> </li> <li data-v-469af010=""><a data-v-469af010="" href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li> <li data-v-469af010=""><a data-v-469af010="" href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li> <li data-v-469af010=""><a data-v-469af010="" href="https://news.vuejs.org" target="_blank" rel="noopener">News</a> </li> </ul> <h3 data-v-469af010="">Ecosystem</h3> <ul data-v-469af010=""> <li data-v-469af010=""><a data-v-469af010="" href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a> </li> <li data-v-469af010=""><a data-v-469af010="" href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a> </li> <li data-v-469af010=""><a data-v-469af010="" href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li> <li data-v-469af010=""><a data-v-469af010="" href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li> <li data-v-469af010=""><a data-v-469af010="" href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li> </ul> </div> </div> <!-- built files will be auto injected --> <script type="text/javascript" src="/js/chunk-vendors.js"></script> <script type="text/javascript" src="/js/app.js"></script>
<div id="app"></div>
の中身が埋まったことがわかります。
準備が整ったのでデデンネの総合順位を取得する
今回、順位を出すのにデータ取り扱いに強いpandasを使って計算を行いました。
import pandas as pd from requests_html import HTMLSession def get_region_rank_data(region): ls = list() url = 'https://pokemonday.pokemon.co.jp/jp/result/' + region + '/' # セッション開始 session = HTMLSession() r = session.get(url) # ブラウザエンジンでHTMLを生成させる r.html.render() headers = r.html.find('.ranking__header') for header in headers: name = header.find('.ranking__name')[0].text vote = int(header.find('.ranking__vote > span')[0].text.replace(",", "")) number = int(header.find('.ranking__number > span')[0].text) ls.append([number, name, vote]) return ls if __name__ == '__main__': regions = [ 'kanto', 'johto', 'hoenn', 'sinnoh', 'unova', 'kalos', 'alola', 'galar', ] data_list = list() for region in regions: data_list.extend(get_region_rank_data(region)) # 集計 array = pd.np.array(data_list) df = pd.DataFrame({ "number": array[:, 0], "name": array[:, 1], "vote": array[:, 2] }) df["vote"] = df["vote"].astype(int) df = df.sort_values("vote", ascending=False) df["rank"] = df["vote"].rank(0, ascending=False) df = df[["rank", "name", "number", "vote"]] df.to_csv('pokemon_vote_ranking.csv', header=True, index=False)
これによってpokemon_vote_ranking.csvに吐き出されたデータは以下のようになりました。
rank,name,number,vote 1.0,ゲッコウガ,658,140559 2.0,ルカリオ,448,102259 3.0,ミミッキュ,778,99077 4.0,リザードン,6,93968 5.0,ブラッキー,197,67062 6.0,ニンフィア,700,66029 7.0,ガブリアス,445,61877 8.0,レックウザ,384,60939 9.0,サーナイト,282,60596 10.0,ゲンガー,94,60214 11.0,ドラパルト,887,57973 12.0,バンギラス,248,56834 13.0,フシギダネ,1,56015 14.0,ストリンダー,849,55032 15.0,ルギア,249,53268 16.0,モクロー,722,52367 17.0,ギルガルド,681,51517 18.0,シャンデラ,609,50943 19.0,ピカチュウ,25,48060 20.0,イーブイ,133,47762 21.0,レントラー,405,46032 22.0,ジュナイパー,724,44011 23.0,ゾロアーク,571,43782 24.0,ルガルガン,745,42792 25.0,アーマーガア,823,41711 26.0,フライゴン,330,41420 27.0,サザンドラ,635,40054 28.0,ジュカイン,254,38724 29.0,バシャーモ,257,38307 30.0,ユキハミ,872,38034 31.0,ミズゴロウ,258,36920 32.0,カイリュー,149,36873 33.0,ミュウ,151,36266 34.0,メタグロス,376,35631 35.0,バクフーン,157,35184 36.0,オンバーン,715,34795 37.0,ハッサム,212,34691 38.0,ポッチャマ,393,34680 39.0,ミュウツー,150,34585 40.0,ゴウカザル,392,33267 41.0,デンリュウ,181,32009 42.0,ゼラオラ,807,31691 43.0,マホイップ,869,30612 44.0,ダークライ,491,30544 45.0,ヌメルゴン,706,30209 46.0,エーフィ,196,30052 47.0,ガオガエン,727,29925 48.0,ウインディ,59,29795 49.0,ジラーチ,385,29611 50.0,ヒノアラシ,155,28332 51.0,ミロカロス,350,28295 52.0,アブソル,359,27781 53.0,グソクムシャ,768,26975 54.0,エースバーン,815,26892 55.0,ラグラージ,260,26540 56.0,スイクン,245,26277 57.0,グレイシア,471,26161 58.0,ザシアン,888,26158 59.0,アシレーヌ,730,25953 60.0,ワンパチ,835,25695 61.0,ボーマンダ,373,24920 62.0,アルセウス,493,24502 63.5,ウルガモス,637,24389 63.5,ココドラ,304,24389 65.0,ラプラス,131,23411 66.0,オノノクス,612,22937 67.0,ワニノコ,158,22526 68.0,ファイアロー,663,22328 69.0,ジャローダ,497,22269 70.0,ミジュマル,501,21990 71.0,エンペルト,395,21773 72.0,デデンネ,702,21691 73.0,クロバット,169,21548 74.0,ゼクロム,644,21477 75.0,オオタチ,162,21447 76.0,ギラティナ,487,21366 77.0,ウールー,831,21266 78.0,ビクティニ,494,20957 79.0,リーフィア,470,20859 80.0,イベルタル,717,20852 81.0,インテレオン,818,20697 82.0,ドダイトス,389,20632 83.0,セレビィ,251,20492 84.0,ヌメラ,704,20299 85.0,ネギガナイト,865,20217 86.0,レシラム,643,20123 87.0,ヒバニー,813,20058 88.0,カビゴン,143,19768 89.0,ワルビアル,553,19628 90.0,キュウコン,38,19044 91.0,ガチゴラス,697,18778 92.0,ブリムオン,858,18581 93.0,チコリータ,152,18521 94.0,ゼニガメ,7,18476 95.0,ホウオウ,250,18278 96.0,オーダイル,160,18245 97.0,ボスゴドラ,306,18120 98.0,エルフーン,547,17855 99.0,ラティアス,380,17478 100.0,シェイミ,492,17465 101.0,ゼルネアス,716,17415 102.0,キテルグマ,760,17181 103.0,メッソン,816,17155 104.0,ツタージャ,495,17020 105.0,チルタリス,334,16814 106.0,カメックス,9,16795 107.0,ヘラクロス,214,16577 108.0,ソルガレオ,791,16274 109.0,メルタン,808,16077 110.0,タイレーツ,870,16009 111.0,タルップル,842,15989 112.5,ムゲンダイナ,890,15699 112.5,ネイティ,177,15699 114.0,ピチュー,172,15695 115.0,カイオーガ,382,15585 116.0,クチート,303,15523 117.0,ゾロア,570,14910 118.0,シャワーズ,134,14887 119.0,マッシブーン,794,14747 120.0,ヘルガー,229,14742 121.0,ルチャブル,701,14607 122.0,ペンドラー,545,14536 123.5,ナマコブシ,771,14358 123.5,ベトベトン,89,14358 125.0,ディアルガ,483,14292 126.0,トゲピー,175,14288 127.0,エルレイド,475,14144 128.0,ランクルス,579,14129 129.0,ムクホーク,398,14054 130.0,ヒトカゲ,4,14049 131.0,ニャビー,725,14005 132.0,モルペコ,877,13945 133.0,メロエッタ,648,13915 134.0,シルヴァディ,773,13897 135.0,メタモン,132,13843 136.0,ニャオニクス,678,13661 137.0,フォッコ,653,13508 138.0,サルノリ,810,13478 139.0,トゲキッス,468,13426 140.0,クワガノン,738,13375 141.0,ヌオー,195,13308 142.0,オーロンゲ,861,12923 143.0,エンニュート,758,12863 144.0,キノガッサ,286,12801 145.0,ジャラランガ,784,12790 146.0,グライオン,472,12676 147.0,ザマゼンタ,889,12641 148.0,アチャモ,255,12568 149.0,ラティオス,381,12487 150.0,ヤドン,79,12369 151.0,メルメタル,809,12356 152.0,マーシャドー,802,12107 153.0,グラードン,383,11982 154.0,ジガルデ,718,11943 155.0,マルヤクデ,851,11619 156.0,ニドキング,34,11586 157.0,ダイケンキ,503,11444 158.0,ウオノラゴン,882,11436 159.0,ミミロップ,428,11411 160.0,ドリュウズ,530,11376 161.0,ポリゴン,137,11311 162.0,ドレディア,549,11292 163.0,ロコン,37,11224 164.0,コダック,54,11212 165.0,ライチュウ,26,11196 166.0,カイロス,127,11162 167.0,ヒトモシ,607,11140 168.0,サンダース,135,11064 169.0,ハクリュー,148,11051 170.0,モスノウ,873,10988 171.0,イワンコ,744,10986 172.0,キリキザン,625,10975 173.0,ビッパ,399,10924 174.0,ディアンシー,719,10918 175.0,アマージョ,763,10900 176.0,マフォクシー,655,10889 177.0,ナエトル,387,10865 178.0,マニューラ,461,10854 179.0,デオキシス,386,10842 180.0,テールナー,654,10807 181.0,ジュプトル,253,10746 182.0,ジュペッタ,354,10579 183.0,フシギバナ,3,10454 184.0,フリーザー,144,10450 185.0,ユキメノコ,478,10408 186.0,エンテイ,244,10404 187.0,ニャスパー,677,10402 188.0,コオリッポ,875,10392 189.0,キュレム,646,10338 190.0,フーパ,720,10327 191.0,アマルルガ,699,10321 192.0,ヤンチャム,674,10187 193.0,マグマラシ,156,10032 194.0,コイル,81,9955 195.0,ポリゴン2,233,9902 196.0,チラチーノ,573,9892 197.0,ルナアーラ,792,9887 198.0,ドラミドロ,691,9882 199.0,エモンガ,587,9798 200.0,パチリス,417,9694 201.0,ロトム,479,9417 202.0,キモリ,252,9339 203.0,ウパー,194,9323 204.0,バケッチャ,710,9162 205.0,ロズレイド,407,9092 206.0,オーロット,709,9019 207.0,ヤミラミ,302,9012 208.0,ケルディオ,647,8915 209.0,ラランテス,754,8889 210.0,ゴロンダ,675,8859 211.0,ムウマージ,429,8833 212.0,バチュル,595,8796 213.0,チラーミィ,572,8739 214.0,ルンパッパ,272,8708 215.0,タチフサグマ,862,8705 216.0,ゴリランダー,812,8625 217.0,トゲデマル,777,8531 218.0,ウッウ,845,8436 219.0,コリンク,403,8373 220.0,ボクレー,708,8231 221.0,ドヒドイデ,748,8221 222.0,ムウマ,200,8156 223.0,ツボツボ,213,8129 224.0,カプ・コケコ,785,8090 225.0,ゴルーグ,623,8035 226.0,ゲノセクト,649,7905 227.0,バンバドロ,750,7787 228.0,ソーナンス,202,7745 229.0,ネクロズマ,800,7739 230.0,アシマリ,728,7542 231.0,コスモッグ,789,7515 232.0,キングドラ,230,7418 233.0,ゴンベ,446,7289 234.0,アブリボン,743,7126 235.0,ポリゴンZ,474,7040 236.0,ヌケニン,292,6703 237.0,カラマネロ,687,6503 238.0,グラエナ,262,6459 239.0,エレザード,695,6386 240.0,ケロマツ,656,6293
デデンネがいた!!!
72.0,デデンネ,702,21691
デデンネの図鑑番号702から0を抜いた順位になっていました。
まとめ
デデンネは総合順位72位!!!