PythonによるスクレイピングでLifehack　運行情報を取得して通知する

# はじめに
本記事は、PythonによるスクレイピングでのLifehackをお届けします。

**電車に乗ろうとしたら遅れていた。**

誰もがある経験ですが、そのようなときは情報を取りに行くのではなく、自動で通知される仕組みがあると便利です。

電車の運行情報を提供するアプリの場合、メール通知などがよくあると思いますが、いちいちメール見るのがめんどくさいです。また、アプリのインストールが必要です。なので、運行情報を提供するWebサイトから、利用する電車の運行情報をスクレイピングで取得して、電車が遅れている場合はLINEで通知します。:train:

## Lifehackフロー
まずはLifehackフローについて。

1. Webサービス等の確認（※）
2. 対象Webサイトの選定及び確認
3. ライブラリの選定、プログラミング

（※）コードを書くのは最後の手段。まずは実現したいことが既にWebサービス等でないことを確認する。

## 実践
**【お題】電車が遅れている場合、運行情報を取得して通知する**

お題に対して、Lifehackフロー通りに進めていきます。

1. 上記で書いた通りに、電車の運行情報を提供するアプリでメール通知はめんどくさいので、運行情報を提供するWebサイトから、利用する電車の運行情報をスクレイピングで取得して、Messaging API で通知する。
2. 調べた結果、主要路線情報がたくさんのっているYahoo路線情報に決定。また、Yahoo路線情報のページの場合、運行情報のデータはテーブルになっていることを確認。
3. 使用するライブラリはBeautifulSoupに決定。tableタグの中に運行情報のデータがあるので、リストにして取り出す。

### 検証

- プログラム（unkoujyouhou.py）

```python
#! /usr/bin/env python3
# -*- coding: utf-8 -*-

# スクレイピングに必要なモジュールをインポート
import urllib.request as req
import sys
sys.path.append('/home/pi/.local/lib/python3.5/site-packages/')
from bs4 import BeautifulSoup

url = "https://transit.yahoo.co.jp/traininfo/area/4/"
res = req.urlopen(url)
soup = BeautifulSoup(res, "lxml") 
result = []

table = soup.select("table")

for table_list in table:
    tr_list = table_list.find_all("tr")
    for tr in tr_list:
        result_row = []
        td_list = tr.find_all(["td", "th"])
        for td in td_list:
            cell = td.get_text()
            result_row.append(cell)
        result.append(result_row)

for i, row in enumerate(result):
    print(str(i) + ":" + ",".join(row))
```

- 実行例（標準出力）

```bash
# python3 unkoujyouhou.py 
0:路線,状況,詳細
1:山手線,平常運転,事故・遅延情報はありません
2:京浜東北根岸線,[◯]平常運転,荷物挟まり対応を行った影響で、一...
3:湘南新宿ライン,平常運転,事故・遅延情報はありません
4:東海道本線[東京～熱海],平常運転,事故・遅延情報はありません
5:横須賀線,平常運転,事故・遅延情報はありません
6:横浜線,平常運転,事故・遅延情報はありません
7:相模線,平常運転,事故・遅延情報はありません
8:南武線[川崎～立川],平常運転,事故・遅延情報はありません
9:鶴見線,平常運転,事故・遅延情報はありません
10:南武線[浜川崎～尻手],平常運転,事故・遅延情報はありません
11:中央線(快速)[東京～高尾],[◯]平常運転,青梅線内で踏切内点検を行った影響...
12:中央総武線(各停),平常運転,事故・遅延情報はありません
13:中央本線[高尾～大月],[◯]平常運転,青梅線内で踏切内点検を行った影響...
14:青梅線[立川～青梅],平常運転,事故・遅延情報はありません
15:青梅線[青梅～奥多摩],平常運転,事故・遅延情報はありません
16:五日市線,平常運転,事故・遅延情報はありません
17:宇都宮線[東京～宇都宮],平常運転,事故・遅延情報はありません
18:宇都宮線[宇都宮～黒磯],平常運転,事故・遅延情報はありません
19:高崎線,平常運転,事故・遅延情報はありません
20:埼京川越線[大崎～川越],平常運転,事故・遅延情報はありません
21:八高川越線[八王子～川越],平常運転,事故・遅延情報はありません
22:八高線[高麗川～高崎],平常運転,事故・遅延情報はありません
23:日光線,平常運転,事故・遅延情報はありません
24:烏山線,平常運転,事故・遅延情報はありません
25:常磐線(快速)[品川～取手],平常運転,事故・遅延情報はありません
26:常磐線(各停),平常運転,事故・遅延情報はありません
27:常磐線[品川～水戸],平常運転,事故・遅延情報はありません
28:常磐線[水戸～いわき],平常運転,事故・遅延情報はありません
29:総武線(快速)[東京～千葉],平常運転,事故・遅延情報はありません
30:総武本線[千葉～銚子],平常運転,事故・遅延情報はありません
31:内房線,平常運転,事故・遅延情報はありません
32:外房線,平常運転,事故・遅延情報はありません
33:成田線[我孫子～成田],平常運転,事故・遅延情報はありません
34:成田線[佐倉～成田空港・銚子],平常運転,事故・遅延情報はありません
35:京葉線,平常運転,事故・遅延情報はありません
36:武蔵野線,平常運転,事故・遅延情報はありません
37:東金線,平常運転,事故・遅延情報はありません
38:久留里線,平常運転,事故・遅延情報はありません
39:鹿島線,平常運転,事故・遅延情報はありません
40:吾妻線,平常運転,事故・遅延情報はありません
41:伊東線,平常運転,事故・遅延情報はありません
42:上越線[高崎～水上],平常運転,事故・遅延情報はありません
43:信越本線[高崎～横川],平常運転,事故・遅延情報はありません
44:水郡線,平常運転,事故・遅延情報はありません
45:水戸線,平常運転,事故・遅延情報はありません
46:両毛線,平常運転,事故・遅延情報はありません
47:上野東京ライン,平常運転,事故・遅延情報はありません
48:路線,状況,詳細
49:東武スカイツリーライン,平常運転,事故・遅延情報はありません
50:東武亀戸線,平常運転,事故・遅延情報はありません
51:東武大師線,平常運転,事故・遅延情報はありません
52:東武日光線[南栗橋～東武日光],平常運転,事故・遅延情報はありません
53:東武アーバンパークライン,平常運転,事故・遅延情報はありません
54:東武東上線,平常運転,事故・遅延情報はありません
55:東武越生線,平常運転,事故・遅延情報はありません
56:東武宇都宮線,平常運転,事故・遅延情報はありません
57:東武鬼怒川線,平常運転,事故・遅延情報はありません
58:東武小泉線,平常運転,事故・遅延情報はありません
59:東武佐野線,平常運転,事故・遅延情報はありません
60:東武桐生線,平常運転,事故・遅延情報はありません
61:東武伊勢崎線[久喜～伊勢崎],平常運転,事故・遅延情報はありません
62:路線,状況,詳細
63:西武池袋線,平常運転,事故・遅延情報はありません
64:西武秩父線,平常運転,事故・遅延情報はありません
65:西武新宿線,平常運転,事故・遅延情報はありません
66:西武西武園線,平常運転,事故・遅延情報はありません
67:西武国分寺線,平常運転,事故・遅延情報はありません
68:西武多摩湖線,平常運転,事故・遅延情報はありません
69:西武有楽町線,平常運転,事故・遅延情報はありません
70:西武豊島線,平常運転,事故・遅延情報はありません
71:西武狭山線,平常運転,事故・遅延情報はありません
72:西武拝島線,平常運転,事故・遅延情報はありません
73:西武多摩川線,平常運転,事故・遅延情報はありません
74:西武山口線,平常運転,事故・遅延情報はありません
75:路線,状況,詳細
76:東京メトロ銀座線,平常運転,事故・遅延情報はありません
77:東京メトロ丸ノ内線,平常運転,事故・遅延情報はありません
78:東京メトロ日比谷線,平常運転,事故・遅延情報はありません
79:東京メトロ東西線,平常運転,事故・遅延情報はありません
80:東京メトロ千代田線,平常運転,事故・遅延情報はありません
81:東京メトロ有楽町線,平常運転,事故・遅延情報はありません
82:東京メトロ半蔵門線,平常運転,事故・遅延情報はありません
83:東京メトロ南北線,平常運転,事故・遅延情報はありません
84:東京メトロ副都心線,平常運転,事故・遅延情報はありません
85:路線,状況,詳細
86:東急東横線,平常運転,事故・遅延情報はありません
87:東急目黒線,平常運転,事故・遅延情報はありません
88:東急田園都市線,平常運転,事故・遅延情報はありません
89:東急大井町線,平常運転,事故・遅延情報はありません
90:東急多摩川線,平常運転,事故・遅延情報はありません
91:東急池上線,平常運転,事故・遅延情報はありません
92:東急世田谷線,平常運転,事故・遅延情報はありません
93:路線,状況,詳細
94:京王線,平常運転,事故・遅延情報はありません
95:京王新線,平常運転,事故・遅延情報はありません
96:京王相模原線,平常運転,事故・遅延情報はありません
97:京王高尾線,平常運転,事故・遅延情報はありません
98:京王競馬場線,平常運転,事故・遅延情報はありません
99:京王動物園線,平常運転,事故・遅延情報はありません
100:京王井の頭線,平常運転,事故・遅延情報はありません
101:路線,状況,詳細
102:京成本線,平常運転,事故・遅延情報はありません
103:京成東成田線,平常運転,事故・遅延情報はありません
104:京成押上線,平常運転,事故・遅延情報はありません
105:京成千葉線,平常運転,事故・遅延情報はありません
106:京成千原線,平常運転,事故・遅延情報はありません
107:京成金町線,平常運転,事故・遅延情報はありません
108:成田スカイアクセス線,平常運転,事故・遅延情報はありません
109:路線,状況,詳細
110:都営浅草線,平常運転,事故・遅延情報はありません
111:都営三田線,平常運転,事故・遅延情報はありません
112:都営新宿線,平常運転,事故・遅延情報はありません
113:都営大江戸線,平常運転,事故・遅延情報はありません
114:都電荒川線,平常運転,事故・遅延情報はありません
115:日暮里・舎人ライナー,平常運転,事故・遅延情報はありません
116:路線,状況,詳細
117:京急本線,平常運転,事故・遅延情報はありません
118:京急空港線,平常運転,事故・遅延情報はありません
119:京急大師線,平常運転,事故・遅延情報はありません
120:京急逗子線,平常運転,事故・遅延情報はありません
121:京急久里浜線,平常運転,事故・遅延情報はありません
122:路線,状況,詳細
123:小田急小田原線,平常運転,事故・遅延情報はありません
124:小田急江ノ島線,平常運転,事故・遅延情報はありません
125:小田急多摩線,平常運転,事故・遅延情報はありません
126:路線,状況,詳細
127:ブルーライン,平常運転,事故・遅延情報はありません
128:グリーンライン,平常運転,事故・遅延情報はありません
129:路線,状況,詳細
130:関東鉄道常総線,平常運転,事故・遅延情報はありません
131:関東鉄道竜ケ崎線,平常運転,事故・遅延情報はありません
132:路線,状況,詳細
133:みなとみらい線,平常運転,事故・遅延情報はありません
134:こどもの国線,平常運転,事故・遅延情報はありません
135:路線,状況,詳細
136:相鉄本線,平常運転,事故・遅延情報はありません
137:相鉄いずみ野線,平常運転,事故・遅延情報はありません
138:路線,状況,詳細
139:芝山鉄道線,平常運転,事故・遅延情報はありません
140:路線,状況,詳細
141:銚子電鉄線,平常運転,事故・遅延情報はありません
142:路線,状況,詳細
143:上毛電鉄線,平常運転,事故・遅延情報はありません
144:路線,状況,詳細
145:ひたちなか海浜鉄道湊線,平常運転,事故・遅延情報はありません
146:路線,状況,詳細
147:ニューシャトル,平常運転,事故・遅延情報はありません
148:路線,状況,詳細
149:伊豆急行線,平常運転,事故・遅延情報はありません
150:路線,状況,詳細
151:上信電鉄線,平常運転,事故・遅延情報はありません
152:路線,状況,詳細
153:野岩鉄道会津鬼怒川線,平常運転,事故・遅延情報はありません
154:路線,状況,詳細
155:山万ユーカリが丘線,平常運転,事故・遅延情報はありません
156:路線,状況,詳細
157:ゆりかもめ線,平常運転,事故・遅延情報はありません
158:路線,状況,詳細
159:ディズニーリゾートライン,平常運転,事故・遅延情報はありません
160:路線,状況,詳細
161:真岡鐵道線,平常運転,事故・遅延情報はありません
162:路線,状況,詳細
163:わたらせ渓谷鐵道線,平常運転,事故・遅延情報はありません
164:路線,状況,詳細
165:鹿島臨海鉄道大洗鹿島線,平常運転,事故・遅延情報はありません
166:路線,状況,詳細
167:つくばエクスプレス線,平常運転,事故・遅延情報はありません
168:路線,状況,詳細
169:富士急行線,平常運転,事故・遅延情報はありません
170:路線,状況,詳細
171:りんかい線,平常運転,事故・遅延情報はありません
172:路線,状況,詳細
173:流鉄流山線,平常運転,事故・遅延情報はありません
174:路線,状況,詳細
175:秩父鉄道線,平常運転,事故・遅延情報はありません
176:路線,状況,詳細
177:埼玉高速鉄道線,平常運転,事故・遅延情報はありません
178:路線,状況,詳細
179:東葉高速線,平常運転,事故・遅延情報はありません
180:路線,状況,詳細
181:新京成線,平常運転,事故・遅延情報はありません
182:路線,状況,詳細
183:北総線,平常運転,事故・遅延情報はありません
184:路線,状況,詳細
185:箱根登山鉄道線,平常運転,事故・遅延情報はありません
186:路線,状況,詳細
187:江ノ島電鉄線,平常運転,事故・遅延情報はありません
188:路線,状況,詳細
189:湘南モノレール線,平常運転,事故・遅延情報はありません
190:路線,状況,詳細
191:多摩都市モノレール線,平常運転,事故・遅延情報はありません
192:路線,状況,詳細
193:千葉都市モノレール線,平常運転,事故・遅延情報はありません
194:路線,状況,詳細
195:東京モノレール線,平常運転,事故・遅延情報はありません
196:路線,状況,詳細
197:伊豆箱根鉄道大雄山線,平常運転,事故・遅延情報はありません
198:路線,状況,詳細
199:小湊鉄道線,平常運転,事故・遅延情報はありません
200:路線,状況,詳細
201:いすみ鉄道線,平常運転,事故・遅延情報はありません
202:路線,状況,詳細
203:シーサイドライン,平常運転,事故・遅延情報はありません
```

最初は、このようにtableタグの中にある運行情をリストに格納して、リストからインデックスで路線情報を指定して取得しようと思いました。しかし、当ページでは何らかの異常が発生した場合、ページ上の「現在運行情報のある路線」に別途表示され、インデックスがずれてしまいます。

![スクリーンショット 2018-09-09 19.48.28.png](https://qiita-image-store.s3.amazonaws.com/0/221759/c9f7510e-b0e4-368d-4b50-edf517e31fdc.png)

```bash
0:路線,状況,詳細
1:内房線,[!]運転状況,15:28頃、京葉線内で発生し…
2:京葉線,[!]運転見合わせ,15:28頃、新木場～葛西臨海…
3:武蔵野線,[!]運転状況,15:28頃、京葉線内で発生し…
4:路線,状況,詳細
5:山手線,平常運転,事故・遅延情報はありません
（略）
203:小湊鉄道線,平常運転,事故・遅延情報はありません
204:路線,状況,詳細
205:いすみ鉄道線,平常運転,事故・遅延情報はありません
206:路線,状況,詳細
207:シーサイドライン,平常運転,事故・遅延情報はありません
```

これでは意図しない結果を得てしまうため、[路線情報（※）のページ](https://transit.yahoo.co.jp/traininfo/detail/21/0/)を直接指定します。

（※）例として山手線

![スクリーンショット 2018-09-04 00.34.20.png](https://qiita-image-store.s3.amazonaws.com/0/221759/7b9cdaf5-3ef3-e41d-671b-1f040deabf37.png)

**CSSセレクタはChromeのデベロッパーツールで特定します。**

- プログラム（unkoujyouhou2.py）

```python
#! /usr/bin/env python3
# -*- coding: utf-8 -*-

# スクレイピングに必要なモジュールをインポート
import urllib.request as req
import sys
sys.path.append('/home/pi/.local/lib/python3.5/site-packages/')
from bs4 import BeautifulSoup

url = "https://transit.yahoo.co.jp/traininfo/detail/21/0/"
res = req.urlopen(url)
soup = BeautifulSoup(res, "lxml") 

status = soup.select_one("#mdServiceStatus > dl > dt").text
print(status)
```

- 実行例（標準出力）

```bash
# python3 unkoujyouhou2.py
[○]平常運転
```
CSSセレクタを指定して、textプロパティで山手線の運行情報の状態を取り出すことができました。

コードもスッキリ。

### 応用（Messaging APIとの連携）
仕上げに、Messaging APIと連携して、状態が平常運転以外の場合は通知します。

- プログラム（unkoujyouhou3.py）

```python
#! /usr/bin/env python3
# -*- coding: utf-8 -*-

# スクレイピングに必要なモジュールをインポート
import urllib.request as req
import sys
sys.path.append('/home/pi/.local/lib/python3.5/site-packages/')
from bs4 import BeautifulSoup

# Messaging APIのモジュールをインポート
from linebot import LineBotApi
from linebot.models import TextSendMessage
from linebot.exceptions import LineBotApiError

# channel access tokenを指定
line_bot_api = LineBotApi('<channel access token>')

# user IDとプッシュメッセージを指定
def message1():
        try:
            line_bot_api.push_message('<to>', TextSendMessage(text=str(train) + ":" + str(status) + "   " + str(info)))
        except LineBotApiError as e:
        # error handle
          print("Error occurred")

url = "https://transit.yahoo.co.jp/traininfo/detail/21/0/"
res = req.urlopen(url)
soup = BeautifulSoup(res, "lxml") 

train = soup.select_one("#main > div.mainWrp > div.labelLarge > h1").text
print(train)
status = soup.select_one("#mdServiceStatus > dl > dt").text
print(status)

if not status == "[○]平常運転":
    info = soup.select_one("#mdServiceStatus > dl > dd > p").text 
    print(info)
    message1()
```



- 実行例（標準出力）

```bash
山手線
[○]平常運転
```
平常運転の場合は通知は行いません。

```bash
東京メトロ東西線

[!]運転状況
台風21号の影響で、現在も列車に遅れや運転変更が出ています。なお、快速の運転を中止しています。（9月4日 22時00分掲載）
```

**平常運転以外の場合は、異常が発生（※）しているため、運転状況をLINEに通知します。**

（※）本記事執筆時点の東西線のデータを利用

- LINE通知画面　　　　　　　　　　　　

<img width="375" alt="写真 2018-09-04 22 09 48.png" src="https://qiita-image-store.s3.amazonaws.com/0/221759/6f8a17a3-a715-0bf7-ad92-68025b2a9a7c.png">

<img width="375" alt="写真 2018-09-04 22 10 12.png" src="https://qiita-image-store.s3.amazonaws.com/0/221759/38933a69-41c8-67c1-2f71-2a979868b08f.png">


- 自動化  
自動化するために、RaspberryPiでcronを設定します。
例として、平日7時と18時に設定する場合

```bash
0 7,18 * * 1-5 /usr/bin/python3 /home/pi/python/unkoujyouhou3.py
```


**これで忙しい朝は、電車が遅れている場合、自動的に情報が取得できます。**:point_up_tone1:

## Tips

- パーサーの違い   
[公式](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)では、Pythonのhtml.parserより、lxmlのパーサを推奨しています。lxmlのHTMLパーサの方が早いです。

本記事のプログラム（unkoujyouhou.py）でも、以下の通り（※）に僅かながら違いが出ました。

（※）timeコマンドの引数にプログラムを指定して実行した例

`# time python3 unkoujyouhou.py`

```bash
# html.parser
real	0m2.261s
user	0m1.670s
sys 	0m0.050s

# lxmlのパーサ
real	0m2.024s
user	0m1.560s
sys 	0m0.030s
```

- lxmlのインストール    
`$ pip3 install lxml`

# おわりに
ちょっとのスクレイピングとAPIの組み合わせでLifehackできます。