Python+Lambdaで天気予報をLINEから通知するアプリを簡単に作ってみた

Python+Lambdaで天気予報をLINEから通知するアプリを簡単に作ってみた キャリアアップ

LINEで天気予報の情報を複数のサイトから定期的に取得し、LINEで通知するアプリを作成したいと思います。PythonのスクレイピングとAPI通信、LINEアカウント、AWSを利用してアプリを作成していきます。

もともと1つの天気予報サイトを確認してたんですが、はずれることが多く複数のサイトから情報を集められれば、信頼度も高まると思います。また、雨が降りそうであれば前日に教えてくれると助かるなと思い、今回のアプリを作ってみました。

構成

Pythonファイルではselenium、requestsを使用して、天気予報サイトからデータを取得しLINE側にAPI通信する設計です。
Pythonの実行環境はサーバーレスのAWSLambdaを使用して、定期実行するトリガーはEventBridgeを使用して、特定時刻に処理が走るようにしています。
LINE側はLINE公式アカウントを作成して、MessagingAPI経由でメッセージを配信しています
MessagingAPIとは、LINEを介してユーザーと双方向にやり取りできる開発者向けの高度な機能です。友達情報の分析や1対1のチャットなど双方向のコミュニケーションが可能になります。

これまでLINE上で簡単にメッセージを配信するサービスとして、LINENotifyが提供されていましたが、2024年3月にサービス終了するためMessagingAPIを使用しています。

全体の流れ

  1. LINE公式アカウントを作成する
  2. LINE公式アカウントのMessagingAPIを有効にする
  3. MessagingAPIのアクセストークンを発行する
  4. Pythonファイルを作成
  5. AWSLambdaで関数の作成
  6. EventBridgeで定期実行の設定
  7. 動作確認

詳細解説

1. LINE公式アカウントを作成する

こちらの手順はLINEDeveopersの公式ガイドがわかりやすいので以下を確認ください。

Messaging APIを始めよう

2. LINE公式アカウントのMessagingAPIを有効にする

こちらの手順もLINEDeveopersの公式ガイドがわかりやすいので以下を確認ください。

Messaging APIを始めよう

3. MessagingAPIのアクセストークンを発行する

・LINEDeveopersでMessagingAPIチャネルのページに行きます。

・MessagingAPI設定>チャネルアクセストークンで発行します。※以下のキャプチャは発行済みのため再発行となります。

・発行したアクセストークンをAPI通信の認証で使用します。

4. Pythonファイルを作成

# ライブラリインポート--------------
from bs4 import BeautifulSoup
import requests
import time

# 変数定義--------------------------
## APIエンドポイント・アクセストークン
ENDPOINT="https://api.line.me/v2/bot/message/broadcast"
TOKEN="発行したアクセストークン"

## 天気予報サイトURL
YAHOO="https://weather.yahoo.co.jp/weather/jp/13/4410.html"
TENKI="https://tenki.jp/forecast/3/16/4410/13116/"

## 降水確率基準・傘要否の判定
RAIN_SD=40
is_rain=False

## LINE送信メッセージ格納リスト
messages=[]

# メイン処理-----------------------
def lambda_handler(event,context):
    get_weather(YAHOO)
    get_weather(TENKI)
    if is_rain:
        messages.append({"type":"text","text":"傘が必要です!"})
    send_message()

# 天気予報情報スクレイピング処理---
def get_weather(site):
    global is_rain
    ## サイトにアクセスし各要素情報取得
    response=requests.get(site)
    soup=BeautifulSoup(response.text,"html.parser")
    try:
        if site==YAHOO:
            site_name="Yahoo天気"
            date=soup.select_one("#main > div.forecastCity > table > tr > td:nth-child(2) > div > p.date > span:nth-child(1)").get_text()
            day=soup.select_one("#main > div.forecastCity > table > tr > td:nth-child(2) > div > p.date > span:nth-child(2)").get_text()
            weather=soup.select_one("#main > div.forecastCity > table > tr > td:nth-child(2) > div > p.pict").get_text().replace("\n","").strip()
            max_temp=soup.select_one("#main > div.forecastCity > table > tr > td:nth-child(2) > div > ul > li.high > em").get_text()
            min_temp=soup.select_one("#main > div.forecastCity > table > tr > td:nth-child(2) > div > ul > li.low > em").get_text()
            rain_rate1=int(soup.select_one("#main > div.forecastCity > table > tr > td:nth-child(2) > div > table > tr.precip > td:nth-child(2)").get_text().replace("%",""))
            rain_rate2=int(soup.select_one("#main > div.forecastCity > table > tr > td:nth-child(2) > div > table > tr.precip > td:nth-child(3)").get_text().replace("%",""))
            rain_rate3=int(soup.select_one("#main > div.forecastCity > table > tr > td:nth-child(2) > div > table > tr.precip > td:nth-child(4)").get_text().replace("%",""))
            rain_rate4=int(soup.select_one("#main > div.forecastCity > table > tr > td:nth-child(2) > div > table > tr.precip > td:nth-child(5)").get_text().replace("%",""))
        elif site==TENKI:
            site_name="tenki"
            date_data=soup.select_one("#main-column > section > div.forecast-days-wrap.clearfix > section.tomorrow-weather > h3").get_text()
            date=date_data.split("\xa0")[1].split("(")[0]
            day=date_data.split("\xa0")[1].split("(")[1].split(")")[0]
            weather=soup.select_one("#main-column > section > div.forecast-days-wrap.clearfix > section.tomorrow-weather > div.weather-wrap.clearfix > div.weather-icon > p").get_text()
            max_temp=soup.select_one("#main-column > section > div.forecast-days-wrap.clearfix > section.tomorrow-weather > div.weather-wrap.clearfix > div.date-value-wrap > dl > dd.high-temp.temp > span.value").get_text()
            min_temp=soup.select_one("#main-column > section > div.forecast-days-wrap.clearfix > section.tomorrow-weather > div.weather-wrap.clearfix > div.date-value-wrap > dl > dd.low-temp.temp > span.value").get_text()
            rain_rate1=int(soup.select_one("#main-column > section > div.forecast-days-wrap.clearfix > section.tomorrow-weather > div.precip-table > table > tr.rain-probability > td:nth-child(2)").get_text().replace("%",""))
            rain_rate2=int(soup.select_one("#main-column > section > div.forecast-days-wrap.clearfix > section.tomorrow-weather > div.precip-table > table > tr.rain-probability > td:nth-child(3)").get_text().replace("%",""))
            rain_rate3=int(soup.select_one("#main-column > section > div.forecast-days-wrap.clearfix > section.tomorrow-weather > div.precip-table > table > tr.rain-probability > td:nth-child(4)").get_text().replace("%",""))
            rain_rate4=int(soup.select_one("#main-column > section > div.forecast-days-wrap.clearfix > section.tomorrow-weather > div.precip-table > table > tr.rain-probability > td:nth-child(5)").get_text().replace("%",""))
            
        ## 降水確率基準を超える場合にTrueにする
        if rain_rate2>=RAIN_SD or rain_rate3>=RAIN_SD or rain_rate4>=RAIN_SD:
            is_rain=True
        
        ## 取得要素を送信メッセージのJSON形式に変換
        get_data=f"[{site_name}]\n{date}({day}) {weather}\n最高気温{max_temp}度 最低気温{min_temp}度\n降水確率{rain_rate1}%{rain_rate2}%{rain_rate3}%{rain_rate4}%"
        message={"type":"text","text":get_data}
        
    except:
        ## エラーメッセージの登録
        get_data=f"\n[{site_name}]\n取得に失敗しました"
        print("取得エラー")
        
    finally:
        ## 送信メッセージリストに追加
        messages.append(message)

# API送信--------------------------
def send_message():
    headers={
        "Authorization":f"Bearer {TOKEN}",
        "Content-Type":"application/json"
    }
    
    body={
        "messages":messages
        }
    
    try:
        response=requests.post(ENDPOINT,headers=headers,json=body)
        
    except:
        print("送信に失敗しました。")
    
    finally:
        print(response.status_code)
        print(response.text)

①ライブラリインポート

beautifulsoupとrequestsをインポートします

②変数定義

APIのエンドポイント、トークン情報や天気予報サイトURL、降水確率判定基準などを定義しておきます。今回はYahoo天気とtenk.jpのサイトから情報を取得しています。

③メイン処理

天気予報サイトのURLを引数で渡し、get_weather関数で各サイトの天気予報情報を取得します。is_rainにTrueが登録されている場合は、傘が必要のメッセージをLINEメッセージのリストに追加します。sendmessage関数を呼び出し、API通信を行いLINEでメッセージを発信します。

④天気予報情報スクレイピング処理

requestsのgetメソッドでサイト情報を取得し、beautifulsoupでHTMLの解析を行ってます。
select_one()は、要素をCSSセレクタで指定でき、一致した最初の要素を返します。
get_text()は、HTML要素の文字列を取得します
replace(“%”,” “)split(“(“)[0]strip()などで不要な要素を消してデータ整形しています。
取得したデータはLINEメッセージの要素に追加しますが、try~exceptで例外処理を入れております

⑤API送信

こちらのAPIではLINE公式アカウントと友だちになっているすべてのユーザーに、メッセージを送信します。ヘッダー情報には、先ほどLINEDeveopersで発行したアクセストークンを使用し、ボディ情報は天気予報サイトから取得したmessagesを使用します。requestsのPOSTメソッドでLINEへメッセージを送信し、try~exceptで例外処理を定義しいます。

APIのレファレンスについては、公式ドキュメントがわかりやすいです。

ブロードキャストメッセージを送る

5. AWSLambdaで関数の作成

作成したPythonをファイルをAWSLambdaに登録します。関数の作成からアップロード方法は前回の記事で解説していますので、以下を確認ください。

前回の記事でも解説していますが、外部ライブラリをLambdaレイヤーに設定する必要があります。requestsとbeautifulsoupのARNを設定しています。

6. EventBridgeで定期実行の設定

トリガーの追加からEventBridgeを選択します。
夜の23時に翌日の天気予報の情報を送信してほしいので、以下の時刻で設定を行っています。
cron(00 14 * * ? *)

7. 動作確認

夜の23時になると翌日の天気予報が通知されてきています!

まとめ

これまで学習したPython、Lambdaを使って、自分が欲しいと思う実用的なアプリを作ってみました。費用もほとんどかからず、それほど難しい操作もなく作成できました。LINE経由のアプリの作成方法も学べたので、他にもLINEをインターフェースとしたアプリを作ってみたいと思います。

タイトルとURLをコピーしました