AWS LambdaとMessaging APIでLINEに自動通知機能を設定

## 作ったもの
お出かけイベントサイトから情報を取得し、LINEで自動配信する仕組み

## 全体の流れ
[AWSアカウント作成 & ログイン]
[Messaging APIを利用する準備]
[実行するスクリプトの作成]
[Lambda関数の作成]
[ライブラリ (requests, bs4, jpholiday, line-bot-sdk) の準備]
[LINEチャネルアクセストークン、通知先のユーザーIDを環境変数に設定]
[EventBridge でのスケジュールルールを作成]
[動作確認、テスト]

- [AWSアカウント作成 & ログイン]
ルートユーザーを作成
https://aws.amazon.com/jp/register-flow/
IAMユーザーを作成
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_users_create.html

<br>

- [Messaging APIを利用する準備]
チャネルアクセストークンと通知先のユーザーIDが必要
https://developers.line.biz/ja/docs/messaging-api/getting-started/
※ユーザーIDはU[0-9a-f]{32}の正規表現にマッチする文字列
https://developers.line.biz/ja/docs/messaging-api/getting-user-ids/#what-is-user-id

<br>

- [実行するスクリプトの作成]
Pythonでイベントサイトの情報をスクレイピングするスクリプトを作成
```
#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import requests
from bs4 import BeautifulSoup
from datetime import date, timedelta
import jpholiday
from urllib.parse import urlencode

from linebot import LineBotApi
from linebot.models import TextSendMessage

# 1. 週末の日付計算
def get_weekend_dates():
    """
    毎週金曜に実行される想定。
    翌日(土曜)を開始日、日曜を一旦の終了日とし、
    月曜が祝日の場合はそちらを終了日にする。
    """
    # 毎週の金曜の日付
    today = date.today()
    # 土曜
    start_date = today + timedelta(days=1)
    # 日曜
    sunday = start_date + timedelta(days=1)
    # 月曜
    monday = sunday + timedelta(days=1)
    
    if jpholiday.is_holiday(monday):
        end_date = monday
    else:
        end_date = sunday    
    return start_date, end_date

# 2. いこーよ (iko-yo.net) のイベント検索ページからHTMLを取得
def fetch_ikoyo_events(start_date, end_date):
    """
    いこーよ の検索パラメータに合わせてrequestsを送る。
    start_date / end_date は datetime.date 型。
    
    """
    
    base_url = "https://iko-yo.net/events"
    
    # クエリパラメータの設定
    params = {
        "end_date[day]":     end_date.day,
        "end_date[month]":   end_date.month,
        "end_date[year]":    end_date.year,
        
        # prefecture_ids[] は固定値 (沖縄) 　適宜変更
        "prefecture_ids[]": "47",
        
        "start_date[day]":   start_date.day,
        "start_date[month]": start_date.month,
        "start_date[year]":  start_date.year, 
    }
    
    query_str = urlencode(params, doseq=True)
    final_url = f"{base_url}?{query_str}"
    
    response = requests.get(final_url)
    if response.status_code != 200:
        return None
    return response.text

# 3. HTMLを解析してイベント情報を抽出
def parse_ikoyo_events(html):
    """
    いこーよ イベント検索のHTMLをBeautifulSoupで解析し、
    タイトル、開催日程、場所、URL等をリストにまとめて返す。
    
    実際のHTML構造に合わせてクラス名等を適宜変更してください。
    """
    soup = BeautifulSoup(html, "html.parser")
    events = []
    
    for item in soup.select(".c-grid__item"):
        
        a_tag = item.select_one("a")
        
        title_tag = item.select_one(".c-grid__title.c-link--underline.p-event-list__title")
        
        date_tag = item.select_one(".c-container--xs.u-text--bold.u-text--orange_e")
        
        place_tag = item.select_one(".c-container--xs.u-text--sm.u-text--gray_8")
        
        if not title_tag:
            continue
        
        title = title_tag.get_text(strip=True)
        link = ""
        if a_tag and a_tag.has_attr("href"):
            link = "https://iko-yo.net" + a_tag["href"]
            
            
        date_str = date_tag.get_text(strip=True) if date_tag else "日程不明"
        place_str = place_tag.get_text(strip=True) if place_tag else "場所不明"
        
        events.append({
            "title": title,
            "link": link,
            "date": date_str,
            "place": place_str,
        })
    
    return events
        
    
# 4. メイン処理 スクレイピング後、Messaging APIでLINE通知
def main():
    # 1. 週末の日付を決定 (土曜〜日曜 or 祝日なら月曜)
    start_date, end_date = get_weekend_dates()
    
    # 2. いこーよからHTML取得 (例: 都道府県指定なし→全国)
    html = fetch_ikoyo_events(start_date, end_date)
    if not html:
        print("イベント情報の取得に失敗しました")
        return
    
    # 3. スクレイピングでイベント情報をリスト化
    events = parse_ikoyo_events(html)
    if not events:
        print("イベント情報が見つかりませんでした")
    else:
        lines = [f"【週末のおでかけ情報】\n{start_date} 〜 {end_date}\n"]
        for i, e in enumerate(events, start=1):
            msg = (
                f"{i}. {e['title']}\n"
                f"   日程 : {e['date']}\n"
                f"   場所 : {e['place']}\n"
                f"   URL  : {e['link']}\n"
            )
            lines.append(msg)     
        text_message = "\n".join(lines)
    
    # 4. LINE Messaging API: プッシュ通知
    #    - 「YOUR_CHANNEL_ACCESS_TOKEN」 → 実際のチャネルアクセストークンに置き換え　Lambda環境変数に設定
    #    - 「USER_ID」 → 送りたい相手のユーザID（またはグループID）　Lambda環境変数に設定
    
    # 環境変数から読み込む
    channel_access_token = os.environ["LINE_ACCESS_TOKEN"]
    user_id = os.environ["LINE_USER_ID"]
    
    line_bot_api = LineBotApi(channel_access_token)
    
    # 5. メッセージ送信
    line_bot_api.push_message(
        user_id, 
        TextSendMessage(text=text_message)
        )
    print("LINE通知完了")
    
#  Lambda用のエントリポイント
def lambda_handler(event, context):
    """
    Lambdaで実行されたときに呼ばれる関数。
    main()を呼ぶだけでOK。
    """
    main()
    return {
        "statusCode": 200,
        "body": "Success"
    }
# ローカルで python main.py 実行時
if __name__ == "__main__":
    main()
```

- [Lambda関数の作成]
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/getting-started.html

<br>


- [ライブラリ (requests, bs4, jpholiday, line-bot-sdk) の準備]
Lambda Layers を使って設定



1.ローカル環境でフォルダを用意し、必要なライブラリをインストール
```
mkdir layer-dir
cd layer-dir
pip install requests beautifulsoup4 jpholiday line-bot-sdk -t python
```
2.zipに圧縮
```
zip -r layer.zip python
```
3.Lambdaでレイヤーの作成
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/creating-deleting-layers.html

<br>

- [LINEチャネルアクセストークン、通知先のユーザーIDを環境変数に設定]
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-envvars.html

<br>

- [EventBridge でのスケジュールルールを作成]
https://docs.aws.amazon.com/ja_jp/eventbridge/latest/userguide/eb-run-lambda-schedule.html

<br>

- [動作確認、テスト]
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/testing-functions.html