AWS×Pythonで天気予報LINEBotを作ってみた

## 前置き
毎朝、帰宅時間の18時に雨が降るかどうかをLINEで通知するBotを作成しました。この記事では、その作成手順を解説します。

## GitHub

https://github.com/alta-jp24/WeatherLine


## 0. 記事の構成
1. 天気予報API（livedoor天気互換）の動作確認
2. 天気予報APIで降水確率をPythonで取得する
3. LINE Messaging APIの設定
4. AWS Lambdaの設定
5. AWS Lambdaのテスト
6. 天気予報APIの結果をLINEで通知する
7. AWS EventBridgeの設定 
8. まとめ
9. 参考文献
    - []の数字は参考文献です。

## 1. 天気予報API（livedoor天気互換）の動作確認[1][2]
天気予報APIのリクエストURLは以下の通りです：

```plaintext:リクエストパラメータ
https://weather.tsukumijima.net/api/forecast/city/<場所のID>
```

### 動作確認手順
1. APIドキュメントに従い、リクエストURLに場所のIDを指定します。
   - 場所のIDは全国の地点定義表[3]に記載されています。
2. 福井市（ID: 180010）を指定して実行します。

#### 実行結果
以下は、福井市の天気予報を取得した際のレスポンス例です。

```plaintext:福井市
https://weather.tsukumijima.net/api/forecast/city/180010
```
![天気予報API_livedoor天気互換の動作確認2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1052965/aa15fe0e-a092-8f4f-5081-1faf2a75dd93.png)

```json:レスポンス例 JSON
{
    "publicTime": "2024-12-27T13:00:00+09:00",
    "publicTimeFormatted": "2024/12/27 13:00:00",
    "publishingOffice": "福井地方気象台",
    "title": "福井県 福井 の天気",
    "link": "https://www.jma.go.jp/bosai/forecast/#area_type=offices&area_code=180000",
    "description": {
        "publicTime": "2024-12-27T10:41:00+09:00",
        "publicTimeFormatted": "2024/12/27 10:41:00",
        "headlineText": "",
        "bodyText": "　日本付近は冬型の気圧配置となっています。\n\n　福井県は、おおむね雨や雪となっています。\n\n　２７日は、冬型の気圧配置が続く見込みです。\n　このため、嶺北は雪か雨、嶺南は雨か雪で、共に雷を伴う所があるでしょう。\n\n　２８日は、引き続き冬型の気圧配置となる見込みです。\n　このため、雪か雨で、雷を伴う所があるでしょう。",
        "text": "　日本付近は冬型の気圧配置となっています。\n\n　福井県は、おおむね雨や雪となっています。\n\n　２７日は、冬型の気圧配置が続く見込みです。\n　このため、嶺北は雪か雨、嶺南は雨か雪で、共に雷を伴う所があるでしょう。\n\n　２８日は、引き続き冬型の気圧配置となる見込みです。\n　このため、雪か雨で、雷を伴う所があるでしょう。"
    },
    "forecasts": [
        {
            "date": "2024-12-27",
            "dateLabel": "今日",
            "telop": "雪か雨",
            "detail": {
                "weather": "雪か雨　所により　雷　を伴う",
                "wind": "北の風　やや強く　後　南の風　やや強く　海上　では　西の風　強く",
                "wave": "４メートル"
            },
            "temperature": {
                "min": {
                    "celsius": null,
                    "fahrenheit": null
                },
                "max": {
                    "celsius": "8",
                    "fahrenheit": "46.4"
                }
            },
            "chanceOfRain": {
                "T00_06": "--%",
                "T06_12": "--%",
                "T12_18": "90%",
                "T18_24": "90%"
            },
            "image": {
                "title": "雪か雨",
                "url": "https://www.jma.go.jp/bosai/forecast/img/400.svg",
                "width": 80,
                "height": 60
            }
        },
        ･･･
    ],
    "location": {
        "area": "中部",
        "prefecture": "福井県",
        "district": "嶺北",
        "city": "福井"
    },
    "copyright": {
        "title": "(C) 天気予報 API（livedoor 天気互換）",
        "link": "https://weather.tsukumijima.net/",
        "image": {
            "title": "天気予報 API（livedoor 天気互換）",
            "link": "https://weather.tsukumijima.net/",
            "url": "https://weather.tsukumijima.net/logo.png",
            "width": 120,
            "height": 120
        },
        "provider": [
            {
                "link": "https://www.jma.go.jp/jma/",
                "name": "気象庁 Japan Meteorological Agency",
                "note": "気象庁 HP にて配信されている天気予報を JSON データへ編集しています。"
            }
        ]
    }
}
```

出力結果より

```JSON:JSON
"chanceOfRain": {
                "T00_06": "--%",
                "T06_12": "--%",
                "T12_18": "90%",
                "T18_24": "90%"
            }
```

ここで、`chanceOfRain`プロパティの`T12_18`部分を使用して18時の降水確率を取得します。

## 2. 天気予報APIで降水確率をPythonで取得する[4][5]
以下のPythonコードを使用してAPIから降水確率を取得します。

```Python:python
import requests

url = requests.get("https://weather.tsukumijima.net/api/forecast/city/180010")
data = url.json()

chanceOfRain = data["forecasts"][0]["chanceOfRain"]["T12_18"]

print(f"T12_18の降水確率: {chanceOfRain}")
```
#### 実行結果
```powershell:powershell
$ \WeatherLine> python3 .\get_chanceOfRain.py
T12_18の降水確率: 90%
```

### 改良版：例外処理と条件分岐を追加
以下のコードでは、降水確率が50%以上の場合のみ結果を出力します。

```python:python
import requests

try:
    response = requests.get("https://weather.tsukumijima.net/api/forecast/city/180010",timeout=5)
    response.raise_for_status()

    data = response.json()

    chanceOfRain_str = data["forecasts"][0]["chanceOfRain"]["T12_18"].strip("%")
    chanceOfRain = int(chanceOfRain_str)
    
    if chanceOfRain >= 50:
        print(f"T12_18の降水確率は{chanceOfRain}%です。雨の可能性があります。")

except requests.exceptions.Timeout:
    print("リクエストがタイムアウトしました。")

except requests.exceptions.HTTPError as e:
    print(f"HTTPエラーが発生しました: {e.response.status_code} {e.response.reason}")

except requests.exceptions.RequestException as e:
    print(f"リクエスト中にエラーが発生しました: {e}")
```
#### 実行結果（福井市の場合）
```PowerShell:PowerShell
$ \WeatherLine> python3 .\get_chanceOfRain.py
T12_18の降水確率は90%です。雨の可能性があります。

```

これで動作確認とローカル環境で設定が終わったため、あとはLINE Messaging APIの設定とAWS Lambdaの設定をしクラウドで動作確認を行います。

## 3. LINE Messaging APIの設定
1. LINE Developersでプロバイダーを作成[6]
2. Messaging APIチャンネルを作成
3. チャネルアクセストークンを発行

LINE公式アカウントを使用し、Messaging APIの設定をする。

## 4. AWS Lambdaの設定
#### 1. AWS Management ConsoleでLambda関数を作成


- 関数名: `WeatherLine`
- ランタイム: Python 3.13
    
![Lambda設定1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1052965/149af8e2-8184-0c6e-d0af-a9766bbc41e4.png)
#### 2. 必要なライブラリ（requests）をインストールし、レイヤーとしてアップロード
- レイヤーの準備(ローカル)
    
    ```
    $ pip install requests -t ./python
    $ zip -r python.zip ./python
    ```
- 名前を「requests」にして、「python.zip」をアップロード
![Lambda設定3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1052965/f5f921cc-bce6-2385-dfec-257ae5a75c3e.png)


:::note info
ハマったところ No module named 'requests' [9]
フォルダ名は必ず"python.zip"とすること。
他のフォルダ名だとエラーがでて使えない！！
:::

#### 3. 環境変数にLINEチャネルアクセストークンを設定[7]
![Lambda設定2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1052965/8dd585d6-634d-011f-b783-777add57da2b.png)

## 5. AWS Lambdaのテスト[8]
以下は、LINEで固定メッセージを送信するテストコードです。

```Python:python
import os
import requests

def lambda_handler(event, context):
    # 環境変数の取得
    CHANNEL_ACCESS_TOKEN = os.environ['CHANNEL_ACCESS_TOKEN']
    
    # LINEの返信APIにメッセージを送信
    headers = {
        'Content-Type': 'application/json',
        'Authorization': f'Bearer {CHANNEL_ACCESS_TOKEN}'
    }

    # メッセージ内容の作成
    body = {
        "messages": [
            {
                "type": "text",
                "text": "TEST Message!!"  # LINEに送信するメッセージ
            }
        ]
    }

    # リクエストの送信
    response = requests.post(
        'https://api.line.me/v2/bot/message/broadcast',
        headers=headers,
        json=body
    )

    return {
        'statusCode': response.status_code,
        'body': response.text
    }

```
### 実行結果:LINE
LINEに「テストメッセージ」が送信されます。
![LINE_test1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1052965/5578aac4-f7a2-e5d0-ebe7-608261399f26.png)

## 6. 天気予報APIの結果をLINEで通知する
天気予報APIから取得した降水確率をLINEで通知するLambda関数です。

```python:python
import os
import requests

def lambda_handler(event, context):
    # 環境変数の取得
    CHANNEL_ACCESS_TOKEN = os.environ['CHANNEL_ACCESS_TOKEN']
    
    # LINE APIのヘッダー
    headers = {
        'Content-Type': 'application/json',
        'Authorization': f'Bearer {CHANNEL_ACCESS_TOKEN}'
    }

    # 天気予報APIを呼び出して降水確率を取得
    message = ""
    try:
        # タイムアウトを5秒に設定
        response = requests.get("https://weather.tsukumijima.net/api/forecast/city/180010", timeout=5)
        response.raise_for_status()

        data = response.json()

        # 降水確率を取得（T12_18の値を取得）
        chanceOfRain_str = data["forecasts"][0]["chanceOfRain"]["T12_18"].strip("%")
        
        if chanceOfRain_str == "--":
            message = "T12_18の降水確率が未設定または不明です。"
        else:
            chanceOfRain = int(chanceOfRain_str)
            if chanceOfRain >= 50:
                message = f"本日18時の降水確率は{chanceOfRain}%です。雨の可能性があります。"
            else:
                message = f"本日18時の降水確率は{chanceOfRain}%です。雨の可能性は低いです。"

    except requests.exceptions.Timeout:
        message = "天気予報の取得に失敗しました（タイムアウト）。"
    except requests.exceptions.HTTPError as e:
        message = f"天気予報の取得に失敗しました（HTTPエラー: {e.response.status_code} {e.response.reason}）。"
    except requests.exceptions.RequestException as e:
        message = f"天気予報の取得中にエラーが発生しました: {str(e)}"

    # LINEにメッセージを送信
    body = {
        "messages": [
            {
                "type": "text",
                "text": message  # 天気予報の結果をLINEに送信
            }
        ]
    }

    try:
        line_response = requests.post(
            'https://api.line.me/v2/bot/message/broadcast',
            headers=headers,
            json=body
        )
        line_response.raise_for_status()
    except requests.exceptions.RequestException as e:
        return {
            'statusCode': 500,
            'body': f"Error sending message to LINE: {str(e)}"
        }

    # 正常終了
    return {
        'statusCode': line_response.status_code,
        'body': line_response.text
    }
```
### 実行結果:LINE
![LINE_test2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1052965/e26bdf84-58c3-0eb6-0bb6-1b627b16a128.png)


## 7. AWS EventBridgeの設定[10]
1. AWS Management ConsoleでEventBridgeを開き、新規ルールを作成
2. スケジュール式に以下を設定
(日本時間、平日8時に実行)
```plaintext
cron(0 8 ? * MON-FRI *)
```
![Amazon EventBridge設定1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1052965/208075a1-bee3-9ef4-22ab-6e571fed5165.png)
3. Lambda関数をターゲットに設定

### 実行結果:LINE
![Amazon EventBridge設定2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1052965/607c3d1a-67e1-caad-b700-e2c384a636a5.png)



## 8. まとめ
この記事では、天気予報APIを活用して、帰宅時間18時の降水確率をLINEで通知するBotを作成する手順を解説しました。今後は、降水確率が高い場合に雨雲レーダー情報を追加通知する機能の実装を検討しています。

## 9. 参考文献

[1]

https://qiita.com/wagira/items/b9e901c3aef8acbc2373

[2]

https://weather.tsukumijima.net/

[3]

https://weather.tsukumijima.net/primary_area.xml

[4]

https://www.delftstack.com/ja/howto/python/python-get-json-from-url/

[5]

https://www.tech-teacher.jp/blog/python-strip/

[6]

https://developers.line.biz/ja/

[7]

https://qiita.com/N_H_tennis/items/b30ed6370ccf959407f5

[8]

https://developers.line.biz/ja/reference/messaging-api/#send-broadcast-message

[9]

https://soypocket.com/it/aws-lambda-python-error-no-module-named/

[10]

https://qiita.com/ys_kmr/items/228b94376cf9404e9a33