【Python】SeleniumでTDLの待ち時間を取得してみた

#目的
スプラッシュ・マウンテンの待ち時間が３０分以下になったらLINEで通知を受けるための監視ボットを作る．
![スクリーンショット 2020-12-18 19.07.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377231/f87e2505-fa4b-add0-ad83-257620b6d75d.png)


アトラクション待ち時間ページ：https://www.tokyodisneyresort.jp/tdl/realtime/attraction/

- ステップ１：各アトラクション情報をまとめて取得
- ステップ２：アトラクションの名前とその待ち時間のみを抽出
- ステップ３：待ち時間が30分以下になるまでずっと監視
- ステップ４：LINEに通知
- ステップ５：アトラクションと待ち時間の閾値をユーザが自由に選択できるように拡張


#なぜseleniumを使うのか？
個人的にseleniumを使ったWebスクレイピングはあまりスマートではない．
できるかぎり[Webスクレイピング入門](https://qiita.com/shutokawabata0723/items/878e9cf3651738228643)のように`requests`モジュールのみで

```python
import requests
html = requests.get("<アトラクション待ち時間ページのURL>").text
スプラッシュマウンテンの待ち時間 = html.<抽出するためのロジック>
if スプラッシュマウンテンの待ち時間 <= 30:
    LINEで通知
```
のようににシンプルにリクエストを投げるコードにしたかった．しかし取得した待ち時間ページのhtmlに待ち時間が一切なかった．調べてみたら，どうやらJavaScriptがブラウザで実行されることによって待ち時間がブラウザのみ表示されることがわかった．つまりブラウザでは待ち時間が表示されるけどブラウザなしのただのWebスクレイピングでは表示されないってこと．そこで！ブラウザを実際に立ち上げてJavaScriptの実行まで行うSeleniumが必要になった

![スクリーンショット 2020-12-20 23.14.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/377231/51a34e72-9e0e-0e81-9374-cb11acd8b134.png)


#ステップ１：各アトラクション情報をまとめて取得
アトラクション待ち時間ページ（https://www.tokyodisneyresort.jp/tdl/realtime/attraction） の各アトラクション情報を取得する．

```Python:webScraping.py
#coding:utf-8

#seleniumを使うのに必要なモジュール
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import Select

#アトラクション待ち時間ページ
url = "https://www.tokyodisneyresort.jp/tdl/realtime/attraction/"

#chrome driverの指定
b = webdriver.Chrome('./chromedriver')

#chromeドライバでページを開く
b.get(url)

#待ち時間ページで各アトラクションを構成するclass要素"listItem parentAreaHide"で検索かけた結果（複数）をリスト型で格納
items = b.find_elements_by_css_selector(".listItem.parentAreaHide")

#各アトラクションごとに！
for i in items:
    print (i.text)
    print ("_________________")
b.close()
```

実行結果

```
ペニーアーケード
13:00 – 18:00
案内終了
18:00 更新
_________________
カリブの海賊
9:00 – 21:00
運営中
15:54 更新
待ち時間
5分
_________________
ジャングルクルーズ：ワイルドライフ・エクスペディション
9:00 更新
_________________
スイスファミリー・ツリーハウス
9:00 更新
_________________
魅惑のチキルーム：スティッチ・プレゼンツ〝アロハ・エ・コモ・マイ！〝
10:00 – 20:00
案内終了
20:00 更新
_________________
カントリーベア・シアター
10:00 – 20:00
案内終了
20:00 更新
_________________
蒸気船マークトウェイン号
10:00 – 21:00
運営中
10:01 更新
待ち時間
5分
_________________
トムソーヤ島いかだ
9:00 更新
_________________
ビッグサンダー・マウンテン
9:00 – 21:00
運営中
19:29 更新
待ち時間
35分
_________________
ビーバーブラザーズのカヌー探険
10:00 – 16:00
案内終了
16:00 更新
_________________
イッツ・ア・スモールワールド
9:00 – 21:00
運営中
19:10 更新
待ち時間
5分
_________________
キャッスルカルーセル
9:00 – 21:00
運営中
19:39 更新
待ち時間
5分
_________________
白雪姫と七人のこびと
10:00 – 21:00
運営中
19:43 更新
待ち時間
5分
_________________
シンデレラのフェアリーテイル・ホール
9:00 – 21:00
運営中
18:48 更新
待ち時間
25分
_________________
空飛ぶダンボ
9:00 – 21:00
運営中
19:48 更新
待ち時間
25分
_________________
美女と野獣“魔法のものがたり” NEW
2020年9月28日（月）～
エントリー受付対象
9:00 – 21:00
エントリー受付中です
17:23 更新
_________________
ピーターパン空の旅
9:00 – 21:00
運営中
19:05 更新
待ち時間
30分
_________________
ピノキオの冒険旅行
9:00 – 21:00
運営中
13:07 更新
待ち時間
5分
_________________
プーさんのハニーハント
9:00 – 21:00
運営中
19:47 更新
待ち時間
14分
_________________
ホーンテッドマンション
9:00 – 21:00
運営中
14:11 更新
待ち時間
13分
_________________
ミッキーのフィルハーマジック
9:00 – 21:00
運営中
19:47 更新
待ち時間
10分
_________________
グーフィーのペイント＆プレイハウス
9:00 – 21:00
運営中
19:28 更新
待ち時間
5分
_________________
チップとデールのツリーハウス
9:00 – 21:00
運営中
9:00 更新
待ち時間
0分
_________________
トゥーンパーク
9:00 – 21:00
運営中
9:00 更新
待ち時間は施設でご確認ください。
_________________
ドナルドのボート
9:00 – 21:00
運営中
9:00 更新
待ち時間
0分
_________________
ミニーの家
9:00 更新
_________________
ロジャーラビットのカートゥーンスピン
10:00 – 21:00
運営中
19:57 更新
待ち時間
10分
_________________
スティッチ・エンカウンター
10:00 – 20:00
案内終了
19:54 更新
_________________
スペース・マウンテン
9:00 – 21:00
運営中
19:37 更新
待ち時間
45分
_________________
バズ・ライトイヤーのアストロブラスター
9:00 – 21:00
運営中
19:32 更新
待ち時間
10分
_________________
ベイマックスのハッピーライド NEW
2020年9月28日（月）～
エントリー受付対象
9:00 – 21:00
エントリー受付は終了しました
17:04 更新
_________________
モンスターズ・インク“ライド＆ゴーシーク！”
9:00 – 21:00
運営中
19:32 更新
待ち時間
10分
_________________
```



とりあえず欲しい情報はとれた．

この時点ではまだアトラクションの名前とその待ち時間に分けれていないので，ステップ２でアトラクション名とその待ち時間のみを抽出する．


#ステップ２：アトラクションの名前とその待ち時間のみを抽出

アトラクションの名前とその待ち時間のみに限定して抽出する．

ステップ１の実行結果より，ターミナルに出力された

```
モンスターズ・インク“ライド＆ゴーシーク！”
9:00 – 21:00
運営中
19:32 更新
待ち時間
10分
```
は

```
モンスターズ・インク“ライド＆ゴーシーク！”\n9:00 – 21:00\n運営中\n19:32 更新\n待ち時間\n10分
```
に同じ．つまり`\n`を区切り文字とすればアトラクション名と待ち時間のみを取得できる．


```python
#coding:utf-8
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import Select
import re

url = "https://www.tokyodisneyresort.jp/tdl/realtime/attraction/"
b = webdriver.Chrome('./chromedriver')
b.get(url)

items = b.find_elements_by_css_selector(".listItem.parentAreaHide")

for i in items:
    index = i.text.split('\n')
    #アトラクションの名前
    print(index[0])
    flag = True
    for n in index:
        if re.match('\d+分', n) or re.match("エントリー受付中です", n):
            print (n)
            flag = False

    if flag:
        print ("休止中") 
    
    print ("_________________")

b.close()
```


#ステップ３：待ち時間が30分以下になるまでずっと監視
スプラッシュ・マウンテンの待ち時間が３０分以下になるまで監視させる．
３０分以下になったらプログラムの実行を終了させる．

```python
#coding:utf-8
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import Select
import re

url = "https://www.tokyodisneyresort.jp/tdl/realtime/attraction/"
b = webdriver.Chrome('./chromedriver')
b.get(url)

items = b.find_elements_by_css_selector(".listItem.parentAreaHide")

FLAG = True
while FLAG == True:
    for i in items:
        index = i.text.split('\n')
        #アトラクションの名前
        print(index[0])
        flag = True
        for n in index:
            if re.match('\d+分', n) or re.match("エントリー受付中です", n):
                print (n)
                flag = False

            if index[0] == "スプラッシュ・マウンテン" and re.match('\d+分', n) and int(n.replace("分","")) <= 30:
                FLAG = False

        if flag:
            print ("休止中") 
    
        print ("_________________")

b.close()
```


#ステップ４：LINEに通知
スプラッシュ・マウンテンの待ち時間が３０分以下になったらプログラムを終了する前にLINEで通知させる．
下準備として[ここで](https://notify-bot.line.me/ja/)LINE Notify APIのトークンを取得しておく．


やり方は，”LINE通知関数”なるものを作ってそれを

```python:
if index[0] == "スプラッシュ・マウンテン" and re.match('\d+分', n) and int(n.replace("分","")) <= 30:
   <******"ここでLINE通知関数の呼び出し"******>
   FLAG = False #フラグを倒す（whileの終了-->プログラムの終了）
```

で呼び出す．


LINE通知関数はこんな感じ

```python:
def send_line_notify():
    
    line_notify_token = 'ここに発行したトークン'
    line_notify_api = 'https://notify-api.line.me/api/notify'
    headers = {'Authorization': f'Bearer {line_notify_token}'}
    data = {'message': message: "スプラッシュマウンテンの待ち時間が３０分以下になりました"}
    requests.post(line_notify_api, headers = headers, data = data)

```



ソースコードはこのようになる．

```python
#coding:utf-8
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import Select
import re

#LINE通知のための関数
def send_line_notify():
    
    line_notify_token = 'ここに発行したトークン'
    line_notify_api = 'https://notify-api.line.me/api/notify'
    headers = {'Authorization': f'Bearer {line_notify_token}'}
    data = {'message': message: "スプラッシュマウンテンの待ち時間が３０分以下になりました"}
    requests.post(line_notify_api, headers = headers, data = data)

url = "https://www.tokyodisneyresort.jp/tdl/realtime/attraction/"
b = webdriver.Chrome('./chromedriver')
b.get(url)

items = b.find_elements_by_css_selector(".listItem.parentAreaHide")

FLAG = True
while FLAG == True:
        index = i.text.split('\n')
        #アトラクションの名前
        print(index[0])
        flag = True
        for n in index:
            if re.match('\d+分', n) or re.match("エントリー受付中です", n):
                print (n)
                flag = False

            if index[0] == "スプラッシュ・マウンテン" and re.match('\d+分', n) and int(n.replace("分","")) <= 30:
                send_line_notify()
                FLAG = False

        if flag:
            print ("休止中") 
    
        print ("_________________")

b.close()
```


#ステップ５:アトラクションと待ち時間の閾値をユーザが自由に選択できるように拡張
アトラクションと通知を受けたい待ち時間の閾値をユーザからのコマンドライン入力引数にすることで，拡張性を持たせる

```
python3 pythonのファイル名.py アトラクション名　閾値
```

で実行する場合，この引数，アトラクション名とその閾値を受け取る方法は以下のようになる．

```python:
import sys
args = sys.argv

attraction_name = sys.argv[1] #アトラクション名
waiting_time    = sys.argv[2] #待ち時間の閾値
```

ソースコードはこのようになる

```python
#coding:utf-8
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import Select
import re
import sys

#LINE通知のための関数
def send_line_notify(name,threshold,waiting_time):
    
    line_notify_token = 'ここに発行したトークン'
    line_notify_api = 'https://notify-api.line.me/api/notify'
    headers = {'Authorization': f'Bearer {line_notify_token}'}
    data = {'message': "【アトラクション】"+name+"¥n【閾値】"+threshold+"¥n【待ち時間】"+waiting_time}
    requests.post(line_notify_api, headers = headers, data = data)

args = sys.argv
attraction_name = sys.argv[1]
waiting_time    = sys.argv[2]
url = "https://www.tokyodisneyresort.jp/tdl/realtime/attraction/"
b = webdriver.Chrome('./chromedriver')
b.get(url)

items = b.find_elements_by_css_selector(".listItem.parentAreaHide")

FLAG = True
while FLAG == True:
    for i in items:
        index = i.text.split('\n')
        #アトラクションの名前
        print(index[0])
        flag = True
        for n in index:
            if re.match('\d+分', n) or re.match("エントリー受付中です", n):
                print (n)
                flag = False

            if index[0] == attraction_name and re.match('\d+分', n) and int(n.replace("分","")) <= int(waiting_time):
                send_line_notify(attraction_name, waiting_time, n)
                FLAG = False
        if flag:
            print ("休止中") 
    
        print ("_________________")

b.close()
```



実行例
スペース・マウンテンの待ち時間が２０分以下になったときにLINEで通知を受け取りたい場合

```
python3 waitTime.py スペース・マウンテン 20
```

おわり