先日ネットサーフィンしてたら非公式ディズニーの待ち時間アプリがハッキングの類ではないかと間違って紹介されてたので、今回はスクレイピングの仕組みを説明しつつディズニー待ち時間アプリをPythonで再現してみようと思います。(前半はアプリの構造、後半はソースコードの解説です)
スクレイピングとは
スクレイピングとはWeb上にあるデータを自動で抜き取ってきて、自分のパソコンやサーバーに保存する技術です。結構身近でも使われてる技術で、例えばGoogleの検索エンジンだったり、Twitterのデータ解析などにも頻繁に用いられています。一般的にはPythonで実装するのが普通ですが、RubyやJavascriptでも実装することは全然可能です。(Pythonの魅力については先日こちらのブログで力説したので良かったらご覧ください)
今回のディズニー待ち時間アプリでは、Pythonで書かれたプログラムがディズニーの公式サイトを巡回して"待ち時間と思しき数値"を自動で取ってくるというものになっています。つまり人間がブラウザを通してするようなことをプログラムが勝手にしてくれるというわけですね。因みにですが、ブラウザの操作だったらプログラムを知らなくてもSeleniumというツールで実は自動化できるんです。上手く利用することができれば例えば大学の掲示板が更新されたら差分のファイルだけ自動で取ってきて、LineBotで通知するなんてこともできちゃうので良かったら調べてみてください。
ただこのスクレイピングはかなり便利な反面、実は犯罪ギリギリラインの技術でもあります。これまでにも悪意のないスクレイピングで逮捕されてしまった案件がありますし(岡山市立中央図書館事件)、ちょっと前には大学生が就活サイトをスクレイピングにかけてたことがバレて企業とトラブルになったこともありました。少なくとも本番環境で運用する際には”interval”などを設けたり、リクエストヘッダーの部分を書き換えるなどしてトラブルを避けるようにしてください。(少なくとも1秒に1回未満じゃないとDoS攻撃とみなされる可能性がある)
映画「ソーシャルネットワーク」で若かりしマークザッカーバーグがハッキングと称して行ってたものは実はスクレイピング。もちろんスクレイピングとハッキングは別物。
プログラムを組んでみる
一般的にスクレイピングのプログラムを組む際には
目的とするサイトのHTMLを手動で調べる。
↓
目的のデータを定義しているクラス名を確認する
↓
プログラムを組む
という流れになっています。そこで今日はこの流れでディズニー待ち時間アプリを実現してみましょう。あくまで内部ロジックを再現するためで、完成品のデザインはだいぶ手抜きですがほとんどの待ち時間アプリが同じようなロジックで再現されているはずです。
まずChromeでディズニーの公式サイトにあるアトラクションの待ち時間一覧のページに行ってください(ここでChromeを指定したのは拡張性と機能性が他のブラウザに比べて高いから)。
上記のようなページが開けたら右クリックを押して「検証(inspect)」ツールを押してください。そうするとこのページのソースコードが閲覧できます。ここで目的の待ち時間が定義されているクラス名を探しに行きます。ある程度HTMLとCSSの仕組みを理解してる必要がありますが、classという変数に代入されてる文字列が定義されてるクラス名なのでそこを探してください (本当は代入されてるわけじゃないんだけど)。見つけたらこのクラス名とURLをメモしてブラウザを閉じます。ここから実際にPythonでプログラムを書いていきます。
今回はDjangoというPythonに付属するフレームワークを使ってアプリケーションを構築します。簡単にですがコマンドもあわせて説明します。
仮想環境を作る
python -m venv venv_disney
プロジェクトを立てる
django-admin startproject scrape_disney
アプリケーションを立てる
python manage.py startapp attr_waittime
一般的にDjangoはMVCならぬMTV構造という形を採用しています。といっても構造自体は同じで名称が変わってるだけですので、そこまで特異なものではありません。
ここからがアプリケーションのコアとなるプログラムです。Djangoには関数ベースビューとクラスベースビューという二つの書き方があります。クラスベースビューはコード量は少ないけど、内部がブラックボックス化しがちという特徴があり、関数ベースビューは直感的に理解しやすいけどコードは複雑という特徴があります。どちらも一長一短ですが、今回はややアルゴリズムが複雑なので関数ベースビューを利用します。(普通であればPythonは書き方が一意に決まるのが特徴)
#ライブラリをインポート import requests from bs4 import BeautifulSoup def waittaime_land(request): #スクレイピング url = 'https://www.tokyodisneyresort.jp/tds/realtime/attraction/’ res = requests.get(url) soup = BeautifulSoup(res.text, "html.parser") #リストに格納 attraction = [] wait_time = [] for attraction_temp in soup.find_all(class_ = "realtime-attr-name"): attraction.append(attraction_temp.text.strip()) for wait_time_temp in soup.find_all(class_ = "attr_wait"): wait_time_treat = wait_time_temp.text.split("分")[0].strip() if wait_time_treat.isdecimal(): wait_time_treat += "分" wait_time.append(wait_time_treat) #テンプレート用に整形 atr = [] for i, _ in enumerate(attraction): art.append({ ‘attraction’: attraction[i], ‘wait_time’: wait_time[i], }) params = {‘ at’r : atr ,} return render(request, 'list.html', params)
ライブラリはrequestsとBeautiful Soupを活用します。事前にpipなどでインストールしてください。基本的にはスクレイピングしてきたデータを整形してリストに追加するという動作を繰り返すだけです。ここでのポイントは最後にenumerate関数で二つのリストを回すところです。本来であれば二つのリストをzip関数で要素をまとめたいところですが、残念ながらDjangoのテンプレートではzip関数が使えません。そのため二つのリストを回すためにはenumerateでカウンターと共に改めて辞書を要素としたリストを作ります。これはランドバージョンですが、シーバージョンもほとんど中身は同じですのでコピペしてください。
次にテンプレートです。
<div class="container"> <ul class="list-group list-group-flush"> <table class='table table-striped' border="2"> <tr> <th>アトラクション</th> <th>待ち時間</th> </tr> {% for a in atr_list %} <tr> <td>{{ a.attraction }}</td> <td>{{ a.wait_time }}</a></td> </tr> {% endfor %} </table> </ul> </div>
デザイナーではないので別にテンプレートは適当でいいと思いますが、今回はBootstrapのテーブルを採用しました。リストをfor文で回しながらペアになった辞書を取り出してテンプレートで整形するというところが肝です。(top pageは別に適宜用意してください)
これをローカルサーバーで立ち上げると
このように一通りのアプリケーションが完成します。(ディズニーが開園してる10時から19時の間で開発しないと動作が確認できないから面倒だった笑)。こんなのはデータベースも立てない非常に軽量なアプリケーションですが、基本的にどんなアプリも内部ロジックはこのようになってるはずです。あとはこのデータをjsonに変換してswiftなどフロントサイドに送ればスマホアプリが完成しますし、Line Official Managerでアカウント登録してコードを少しいじればLineから呼び出すことも可能です。
まあ今は公式があるのでこんなアプリ必要ないのでしょうけど、例えば#TDR_nowで呟かれたツイートをTweepyでスクレイピングしてAIにデータ食わせたり、待ち時間を統計データとして予測したりとそこから自作アプリを差別化する方法はいくらでもあると思います(サーバーレスでは無理かな)。ただその際はもちろんOLCの規約をきちんと確認する必要がありますけど。(正直このアプリも運用するとなるとだいぶグレーゾーン。まあ運用するつもりないけど)
今回はDjangoで構築しましたが、これくらいならFlaskと呼ばれる軽量なフレームワークでいいかもしれませんし、アプリとして活用するならDjango REST Frameworkを活用する方が良さそうです。(スマホのバックエンドをDjangoのRESTで構築する例が増えてきた)。アプリ自体は非常に簡単なものですが、同時にWeb開発とスクレイピングの両方を学べる有能なプログラムでもあると思うのでPythonを少しかじったら勉強の一環として挑戦してみるのもいいと思います。
因みに使ってる画像は全部こないだ撮ってきた写真
参考にしたブログ、書籍
Line Botで待ち時間をディズニーの待ち時間を表示する (Qiita)
Pythonクローリング・スクレイピング・データ収集解析のための実践ガイド(技術評論社)
現場で使えるDjangoの教科書(基礎編)