AWS Lambdaを使って粗品の「電池の切れかけた蟹」のチケット販売開始通知をLINEに送信してみた

## はじめに
筆者は粗品が好きで、単独ライブ「電池の切れかけた蟹」のチケットを毎回とっていますが、最近チケット販売の告知がなくなり、気付いたら販売終了してた....
となることが何度かありました。

そこで、チケットが販売されたら通知がくるものを作りたいと考え、
下記サービスについては初心者ですが、色々調べながら頑張って作ってみました。


## 環境・利用サービス
・Windows11
・Python 3.12
・AWS Lambda
・AWS ECR
・Docker
・LINE Notify


## とりあえずPython書いてみる

チケット販売が開始しているかを確認するため、pythonでseleniumを使って
吉本のお笑いライブチケット販売サイトの「[FANYチケット](https://yoshimoto.funity.jp/)」へクローリングを行います。

https://yoshimoto.funity.jp/

FANYチケットサイトをデベロッパーツールで確認したところ、
「芸人/公演名/会場で探す！」という検索欄で「粗品」と検索し、「電池の切れかけた蟹」というボックスがあれば、チケット販売あり、なければチケット販売なし、という通知を送るようにすれば、目的通りの動作ができそうです。

また、通知方法は色々ありますが、毎日見ているLINEで通知が来ると都合が良いので、
今回はLINE Notifyを使います。
```python:python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import requests


# LINE Notifyの設定
LINE_NOTIFY_TOKEN = 'LINE notifyのトークン'
LINE_NOTIFY_API = 'LINE notifyのAPI'

def send_line_notify(message):
    headers = {
        'Authorization': f'Bearer {LINE_NOTIFY_TOKEN}',
    }
    data = {
        'message': message,
    }
    response = requests.post(LINE_NOTIFY_API, headers=headers, data=data)
    if response.status_code == 200:
        print("LINE Notify: メッセージが送信されました")
    else:
        print("LINE Notify: メッセージの送信に失敗しました")



# ChromeDriverのパスを指定
driver_path = 'chromeドライバーのパス'

# ChromeOptionsを使用してChromeDriverのパスを指定
options = webdriver.ChromeOptions()
options.add_argument(f"--binary={driver_path}")

# WebDriverをセットアップ
driver = webdriver.Chrome(options=options)

try:
    # 指定されたURLにアクセス
    driver.get('https://yoshimoto.funity.jp/')
    
    # 検索ボックスに「粗品」と入力し検索
    search_box = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.XPATH, '//*[@id="kw"]'))
    )
    search_box.send_keys('粗品')
    search_box.send_keys(Keys.RETURN)
    
    # 検索結果を待つ
    time.sleep(3)  # ページが読み込まれるまで待つ
    
    # 検索結果を確認
    try:
        # 「電池の切れかけた蟹」と書いているボックスを探す
        element = driver.find_element(By.XPATH, '//*[contains(text(),"電池の切れかけた蟹")]')
        if element:
            message = "チケットが販売されています"
            print(message)
        else:
            message = "チケットなし"
            print(message)
    except:
        message = "チケットなし"
        print(message)
    
    # LINE Notifyで結果を通知
    send_line_notify(message)
    
finally:
    # ブラウザを閉じる
    driver.quit()
```

 
### 実行結果
LINEの通知が届き、しっかりと動作しています。
あとはこのコードを定期実行すれば完成です。

![IMG_9965.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832357/0bd31d18-9859-9f36-f046-776fad6b1308.jpeg)




## AWS Lambdaの実装
上記のコードを定期実行するためには色々とやり方はあると思いますが、
今回はお金をかけずサーバレスで実行したかったので、
勉強中でもあるAWS Lambdaを使ってみようと思いました。



### AWS Lambdaとは

https://aws.amazon.com/jp/lambda/

AWS Lambdaは、Amazon Web Services（AWS）が提供するサーバーレスコンピューティングサービスの一つです。
AWS Lambdaを用いることでコードをサーバレスで実行することが可能です。
今回の場合は１日に１回しかコードを実行しないので、無料枠で利用することができます。

### 関数の作成
早速、関数を作成し、seleniumやrequestsなどの外部ライブラリもレイヤー追加してみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832357/808b732f-28ae-2c59-7a6e-c31faa75b310.png)

### Lambdaがエラーで動かず....
なぜかエラーが出てしまい実行できませんでした。
色々調べたところLambdaではpython3.7のランタイムでしかseleniumが動作しないようです。
しかし、python3.7のランタイムは2023年に終了しており、Lambda上でseleniumを動かすことは現状できないようです。

### 解決策
Lambda上で直接seleniumを使うことはできませんが、Dockerコンテナを作成し、
DockerコンテナをLambdaで実行すれば解決できそうです。






## Dockerイメージの作成
### Dockerとは

https://www.docker.com/ja-jp/

Docker は、ソフトウェアのコンテナ化を実現するオープンプラットフォームです。Dockerを使用することで、アプリケーションとその依存関係を一つのコンテナとしてパッケージ化し、どこでも一貫して動作するようにすることができます。
### Dockerfile作成
Dockerfileとは、Dockerイメージを構築するためのファイルです。
今回はseleniumやchromedriverなどをインストールしていきます。

Dockerは初心者だったため、作成には[こちら](https://github.com/umihico/docker-selenium-lambda)を参考に作成しました。

```dockerfile:Dockerfile
FROM public.ecr.aws/lambda/python:3.12 as build
RUN dnf install -y unzip && \
    curl -Lo "/tmp/chromedriver-linux64.zip" "https://storage.googleapis.com/chrome-for-testing-public/126.0.6478.126/linux64/chromedriver-linux64.zip" && \
    curl -Lo "/tmp/chrome-linux64.zip" "https://storage.googleapis.com/chrome-for-testing-public/126.0.6478.126/linux64/chrome-linux64.zip" && \
    unzip /tmp/chromedriver-linux64.zip -d /opt/ && \
    unzip /tmp/chrome-linux64.zip -d /opt/

FROM public.ecr.aws/lambda/python:3.12
RUN dnf install -y atk cups-libs gtk3 libXcomposite alsa-lib \
    libXcursor libXdamage libXext libXi libXrandr libXScrnSaver \
    libXtst pango at-spi2-atk libXt xorg-x11-server-Xvfb \
    xorg-x11-xauth dbus-glib dbus-glib-devel nss mesa-libgbm
RUN pip install selenium==4.22.0
RUN pip install requests
COPY --from=build /opt/chrome-linux64 /opt/chrome
COPY --from=build /opt/chromedriver-linux64 /opt/
COPY main.py ./
CMD [ "main.handler" ]
```


※ディレクトリ構造は以下になっており、main.pyは先ほどのコードです。
```
denkani/
├── Dockerfile
└── main.py
```
### Dockerイメージのビルド
ターミナルにてDocker buildコマンドでDockerイメージを作成します。
Docker Desktopにてイメージが作成されたことを確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832357/f6176ee5-5447-0cfd-beea-3cf1ed07df34.png)

## Amazon ECRへプッシュ
### Amazon ECRとは

https://aws.amazon.com/jp/ecr/

Amazon ECR (Elastic Container Registry) は、AWS（Amazon Web Services）が提供する、Dockerコンテナイメージの保存、管理、デプロイを行うためのコンテナレジストリサービスです。ECRを利用することで、セキュアかつスケーラブルな方法でコンテナイメージを保存し、デプロイできます。

### AWS CLIを使用しECRへプッシュ
AWS CLIにてコマンドを実行することで、先ほど作成したDockerイメージをECRへプッシュすることができます。
ECRにて下記のようなリポジトリにイメージが作成できていればプッシュが完了しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832357/f9ab9ed7-d1cd-4479-eb9f-a96d7e7153d2.png)



## ECRにプッシュしたDockerイメージをLambdaへ
Lambda関数作成時に、コンテナイメージから作成を選択し、先ほどECRで作成した
コンテナイメージURIを入力し「イメージを参照」をクリックして、関数を作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832357/9e9b5bb3-bb33-32ed-522f-071b7bae47a6.png)

コンテナの大きさによって時間がかかる場合がありますが、無事関数の作成ができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832357/8777975c-8e34-18dd-9bfd-13e228f8277c.png)


ちなみに、デフォルトでは関数のタイムアウトが3秒になっていますが
seleniumには短すぎるため、1分に変更しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832357/d93a339a-cb94-e0e3-1099-13b67cb68d8f.png)



## 定期実行のためのトリガー作成
Lambdaのトリガー追加から、定期実行のためのトリガーを作成します。
トリガーのソースはEventBridgeを使用し、1日おきに実行するようにしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832357/dad0ff32-5bb9-4cff-42dc-026c9d2f29a8.png)

## トリガー追加後の実行結果
トリガーを追加することで一日おきにチケット販売が開始されたかどうか、
自動でLINEに通知を送ることができました。

![IMG_9966.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832357/8c1813bc-665d-75f6-b968-61a295dea062.jpeg)

## まとめ
Lambdaを使用し、電蟹のチケットが販売されているかどうかを
1日おきにLINEに通知することができるようになりました。
これでチケット購入を忘れることがなくなりました。

今回はseleniumについてあまり知識がなかったので、もう少し勉強をして
次は自動でチケットを買えるようにしたいです。