こんにちは✋電車遅延がある日は、在宅勤務にしたい...というモチベーションで、電車遅延通知の仕組みを組んだので、守備録です。
まとめ
- AWS LambdaでJRのHPを確認(PythonのBeautifulsoupを使用)
- 遅延がある場合はAmazon SNSでSMS通知
- Lambdaへの毎朝のトリガーはAmazon EventBridge
SMSの発行
個人的に、Email、ライン、スラックなどは通知を切ってしまっているので、唯一通知を切っていないSMSで通知することにしました。
Amazon SNSを使うと簡単にSMSを発行できます(SMS以外にもEmailも発行できます)。
チュートリアルに従って、topicとsubscriptionを作ります。topic一つに対して、複数のsubscription(今回はSMSの電話番号)が紐づくという構成の様です、topicは送信先(subscription)を束ねる単位といったところでしょうか。
チュートリアルはEmailになっていますが、SMSも同じでした。
作成したtopicのARNは後で使います:
Pythonコード
HTMLの解読
Yahooの路線情報を読むことも考えましたが、より詳細な遅延情報が取得できるJRのHPを読むことにしました。
requestsでHTMLを読み込んで、BeautifulSoupで構文解析します:
import requests
from bs4 import BeautifulSoup
url = 'https://traininfo.jreast.co.jp/train_info/kanto.aspx'
session = requests.Session()
response = session.get(url)
response.encoding = response.apparent_encoding
soup = BeautifulSoup(response.content, "html.parser")
TRタグが以下の黄色枠なので、これを全て確認することを考えます:
TRタグ内を確認すると、遅延がない場合は:
<tr>
<th class="ta-l"><div>
<p class="rosen">
<span class="rosen_color sotetsuline"></span>
<span class="name">相鉄線直通列車</span>
</p>
<p class="btn pc"><a href="/train_info/line.aspx?gid=1&lineid=sotetsuline">詳細</a></p>
</div></th>
<td><a href="/train_info/line.aspx?gid=1&lineid=sotetsuline">
<div class="status normal"><p class="statusIcon"><img alt="平常運転" src="/train_info/img/ico_info_normal.svg"/></p><p>平常運転</p></div>
</a></td>
</tr>
遅延がある場合は:
<tr>
<th class="ta-l"><div>
<p class="rosen">
<span class="rosen_ico">
<img alt="JA" src="/train_info/img/ico_rosen_ja.svg">
</img></span>
<span class="name">埼京線</span>
</p>
<p class="btn pc"><a href="/train_info/line.aspx?gid=1&lineid=saikyoline">詳細</a></p>
</div></th>
<td><a href="/train_info/line.aspx?gid=1&lineid=saikyoline">
<div class="status delay"><p class="statusIcon"><img alt="遅延" src="/train_info/img/ico_info_delay.svg"/></p><p>遅延</p></div>
<p class="mt10 sp_mt5 sp_mr25">埼京線は、新宿駅でのお客さま転落の影響で、下り線の一部列車に遅れがでています。</p>
</a></td>
</tr>
となっていました。ここから考えると:
-
tr_tag.find("span", {"class": "name"}).text
で電車名が取得できます -
tr_tag.find("td").find_all("p")[1].text
が"平常運転"
なら遅延なしです - 遅延がある場合は
tr_tag.find("td").find_all("p")[2].text
で遅延内容が取得できます
確認したい路線のリストをtarget_trains
として与え、これら以外は無視することにすると、Forループ全体は:
target_trains = ["京葉線", "山手線"]
delay_messages = []
for tr_tag in soup.find_all("tr"):
train_name = tr_tag.find("span", {"class": "name"}).text
if train_name not in target_trains:
continue
if tr_tag.find("td").find_all("p")[1].text == "平常運転":
continue
delay_message = tr_tag.find("td").find_all("p")[2].text
delay_messages.append(delay_message)
SMSの発行
boto3を使ってSMSを発行できます:
import boto3
sns = boto3.client('sns', region_name="ap-northeast-1")
res = sns.publish(
TopicArn="arn:aws:sns:ap-northeast-1:724...:DelayNotification",
Message=sms_message
)
ここで、region_name
は先ほど作成したAmazon SNSのtopicを作成したリージョンを指定します。TopicArn
にはtipicのARNを指定します。
これを実行するには権限の設定が必要になります。LambdaのFunctionのコンソールから、ConfigurationタブのなかのPermissionsタブを開き、Role nameをクリックします:
これでroleの設定画面が開くので、Add permissions→Create inline policyとします:
SNS:Publishの権限が必要なので、以下のように設定します:
ResourcesはtipicのARNを設定するとより教科書的ですが、簡単に上記としました。最後にinline policyの名前を指定するところは何でもOKです(SNSなど)。
全体
AWS Lambdaにアップするにはdef lambda_handler(event, context):
の中に実行したいコードを書く必要があります。
また、スクリプトファイル名はlambda_function.py
とする必要があります。
遅延がない場合はSMSを発行しないで終了する構成にしました。
以上を踏まえて、全体は以下になります:
import requests
import boto3
from bs4 import BeautifulSoup
def lambda_handler(event, context):
url = 'https://traininfo.jreast.co.jp/train_info/kanto.aspx'
session = requests.Session()
response = session.get(url)
response.encoding = response.apparent_encoding
soup = BeautifulSoup(response.content, "html.parser")
target_trains = ["京葉線", "山手線"]
delay_messages = []
for tr_tag in soup.find_all("tr"):
train_name = tr_tag.find("span", {"class": "name"}).text
if train_name not in target_trains:
continue
if tr_tag.find("td").find_all("p")[1].text == "平常運転":
continue
delay_message = tr_tag.find("td").find_all("p")[2].text
delay_messages.append("🚃" + delay_message)
if delay_messages:
sms_message = "\n".join(delay_messages)
else:
sms_message = "🚃遅延はありません🚃"
return {
'statusCode': 200,
'body': sms_message,
}
sns = boto3.client('sns', region_name="ap-northeast-1")
res = sns.publish(
TopicArn="arn:aws:sns:ap-northeast-1:724...:DelayNotification",
Message=sms_message
)
return {
'statusCode': 200,
'body': sms_message,
'res': res
}
AWS Lambdaへのアップ
公式チュートリアルに従ってLambdaのfunctionを作成できます。
今回は標準外のパッケージ(requestsとBeautifulSoup)を使うので、AWS Lambdaのコンソール上からコードを打ちこむだけでは動かず、パッケージを含めてZIPファイルを作り、それをアップする必要があります。ZIPファイルを使ってでデプロイする公式のチュートリアルに従って以下にようにしました:
zip my-deployment-package.zip lambda_function.py
pip install --target ./package urllib3==1.26.15 requests beautifulsoup4
cd package
zip -r ../my-deployment-package.zip .
ここで、urllib3==1.26.15
としないとうまくいかなくてはまりました(こうしないと、2023/05時点ではrequests
と整合がとれないようです)。
これでできたmy-deployment-pakage.zip
を、LambdaコンソールのUplard fromボタンからアップします:
Amazon EventBridgeの設定
平日朝に定期実行したいので、EventBridgeを使ってLambdaを定期実行するようにします。
チュートリアルに従ってruleを作成します。Rule typeではScheduleを選択します:
Schedule patternは以下のようにしました:
Cron表現で指定しています、これで平日の07:30に実行されます。注意点としては、Day of monthとDay of the weekは、片方だけを指定して、もう片方は「?」にする必要があります。
Flexibule time windowはOffにしました:
同時に沢山のイベントトリガーをかけるような場合はFlexibule time windowを指定すると、処理が時間的に分散してよいことがあるようです。今回は1日に1回実行するのみなのでOffとしています。
Time zoneは東京を指定します:
Select targetでLambdaを選択し、Deploy済みのLambda functionを指定します(ここで、EventBridgeを作っているリージョンとLambdaをdeployしたリージョンが一致していないと、deployしたfunctionが選択肢に現れれないので注意です)。
おわりに
これで、遅延がある場合には、出勤前にSMS通知が来るようになりました。この先の発展版としては、Googleカレンダーを読んで、在宅予定日のみ動作するようにしようかと思っています。
Happy coding!