LoginSignup
1
4

More than 1 year has passed since last update.

AWS Lambda + Amazon SNSで電車遅延情報を毎朝通知

Last updated at Posted at 2023-05-26

こんにちは✋電車遅延がある日は、在宅勤務にしたい...というモチベーションで、電車遅延通知の仕組みを組んだので、守備録です。

まとめ

  • 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は後で使います:

image.png

Pythonコード

HTMLの解読

Yahooの路線情報を読むことも考えましたが、より詳細な遅延情報が取得できるJRのHPを読むことにしました。

JRのページのイメージ:
image.png

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タグが以下の黄色枠なので、これを全て確認することを考えます:

image.png

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&amp;lineid=sotetsuline">詳細</a></p>
</div></th>
<td><a href="/train_info/line.aspx?gid=1&amp;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&amp;lineid=saikyoline">詳細</a></p>
</div></th>
<td><a href="/train_info/line.aspx?gid=1&amp;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をクリックします:

image.png

これでroleの設定画面が開くので、Add permissions→Create inline policyとします:

image.png

SNS:Publishの権限が必要なので、以下のように設定します:

image.png

ResourcesはtipicのARNを設定するとより教科書的ですが、簡単に上記としました。最後にinline policyの名前を指定するところは何でもOKです(SNSなど)。

全体

AWS Lambdaにアップするにはdef lambda_handler(event, context):の中に実行したいコードを書く必要があります。

また、スクリプトファイル名はlambda_function.pyとする必要があります。

遅延がない場合はSMSを発行しないで終了する構成にしました。

以上を踏まえて、全体は以下になります:

lambda_function.py
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ボタンからアップします:

image.png

Amazon EventBridgeの設定

平日朝に定期実行したいので、EventBridgeを使ってLambdaを定期実行するようにします。

チュートリアルに従ってruleを作成します。Rule typeではScheduleを選択します:

image.png

Schedule patternは以下のようにしました:

image.png

Cron表現で指定しています、これで平日の07:30に実行されます。注意点としては、Day of monthとDay of the weekは、片方だけを指定して、もう片方は「?」にする必要があります。

Flexibule time windowはOffにしました:

image.png

同時に沢山のイベントトリガーをかけるような場合はFlexibule time windowを指定すると、処理が時間的に分散してよいことがあるようです。今回は1日に1回実行するのみなのでOffとしています。

Time zoneは東京を指定します:

image.png

Select targetでLambdaを選択し、Deploy済みのLambda functionを指定します(ここで、EventBridgeを作っているリージョンとLambdaをdeployしたリージョンが一致していないと、deployしたfunctionが選択肢に現れれないので注意です)。

おわりに

これで、遅延がある場合には、出勤前にSMS通知が来るようになりました。この先の発展版としては、Googleカレンダーを読んで、在宅予定日のみ動作するようにしようかと思っています。

Happy coding!

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4