LINEで天気予報の情報を複数のサイトから定期的に取得し、LINEで通知するアプリを作成したいと思います。PythonのスクレイピングとAPI通信、LINEアカウント、AWSを利用してアプリを作成していきます。
もともと1つの天気予報サイトを確認してたんですが、はずれることが多く複数のサイトから情報を集められれば、信頼度も高まると思います。また、雨が降りそうであれば前日に教えてくれると助かるなと思い、今回のアプリを作ってみました。
構成
Pythonファイルではselenium、requestsを使用して、天気予報サイトからデータを取得しLINE側にAPI通信する設計です。
Pythonの実行環境はサーバーレスのAWSLambdaを使用して、定期実行するトリガーはEventBridgeを使用して、特定時刻に処理が走るようにしています。
LINE側はLINE公式アカウントを作成して、MessagingAPI経由でメッセージを配信しています。
MessagingAPIとは、LINEを介してユーザーと双方向にやり取りできる開発者向けの高度な機能です。友達情報の分析や1対1のチャットなど双方向のコミュニケーションが可能になります。
全体の流れ
- LINE公式アカウントを作成する
- LINE公式アカウントのMessagingAPIを有効にする
- MessagingAPIのアクセストークンを発行する
- Pythonファイルを作成
- AWSLambdaで関数の作成
- EventBridgeで定期実行の設定
- 動作確認
詳細解説
1. LINE公式アカウントを作成する
こちらの手順はLINEDeveopersの公式ガイドがわかりやすいので以下を確認ください。
2. LINE公式アカウントのMessagingAPIを有効にする
こちらの手順もLINEDeveopersの公式ガイドがわかりやすいので以下を確認ください。
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に登録します。関数の作成からアップロード方法は前回の記事で解説していますので、以下を確認ください。
6. EventBridgeで定期実行の設定
トリガーの追加からEventBridgeを選択します。
夜の23時に翌日の天気予報の情報を送信してほしいので、以下の時刻で設定を行っています。
cron(00 14 * * ? *)
7. 動作確認
夜の23時になると翌日の天気予報が通知されてきています!
まとめ
これまで学習したPython、Lambdaを使って、自分が欲しいと思う実用的なアプリを作ってみました。費用もほとんどかからず、それほど難しい操作もなく作成できました。LINE経由のアプリの作成方法も学べたので、他にもLINEをインターフェースとしたアプリを作ってみたいと思います。