AWS-Lambda + Python + CronでWEBスクレイピングを定期的に実行する

# AWS-Lambda + Python + CronでWEBスクレイピングを定期的に実行する

## はじめに

`Mac環境の記事ですが、Windows環境も同じ手順になります。環境依存の部分は読み替えてお試しください。`

### 目的

この記事を最後まで読むと、次のことができるようになります。

| No.  | 概要              | キーワード                              |
| :--- | :---------------- | :-------------------------------------- |
| 1    | コーディング      | Python                                  |
| 2    | WEBスクレイピング | Selenium,chromedriver,headless-chromium |
| 3    | Lambda設定        | Lambda                                  |

### 実行環境

| 環境           | Ver.    |
| :------------- | :------ |
| macOS Catalina | 10.15.3 |
| Python         | 3.7.3   |
| selenium       | 3.141.0 |

### ソースコード

実際に実装内容やソースコードを追いながら読むとより理解が深まるかと思います。是非ご活用ください。

[GitHub](https://github.com/nsuhara/python-lambda-selenium.git)

### 関連する記事

- [Selenium](https://selenium-python.readthedocs.io/)

## AWS-Lambdaの特徴

本サービスは従量課金制となります。ご留意ください。

- [特徴](https://aws.amazon.com/jp/lambda/features/)
- [料金](https://aws.amazon.com/jp/lambda/pricing/)

## 全体の流れ

1. Pythonコードを書く
2. Lambdaへアップロードするためのzipを作成する
3. Lambda関数を作成する
4. Lambda関数へzipをアップロードする
5. (補足)Layersを使ってアップロードする
6. Lambda関数の環境変数を設定する
7. (補足)Layersを使った場合の環境変数を設定する
8. 定期的に実行するためCronを設定する

## 1. Pythonコードを書く

### コーディング

```app/lambda_function.py
"""app/lambda_function.py
"""
import os

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By


def lambda_handler(event, context):
    """lambda_handler
    """
    print('event: {}'.format(event))
    print('context: {}'.format(context))

    headless_chromium = os.getenv('HEADLESS_CHROMIUM', '')
    chromedriver = os.getenv('CHROMEDRIVER', '')

    options = Options()
    options.binary_location = headless_chromium
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument('--single-process')
    options.add_argument('--disable-dev-shm-usage')

    driver = webdriver.Chrome(executable_path=chromedriver, options=options)
    driver.get('https://info.finance.yahoo.co.jp/fx/')
    usd_jpy = driver.find_element(By.ID, 'USDJPY_top_bid').text
    driver.close()
    driver.quit()

    return {
        'status_code': 200,
        'usd_jpy': usd_jpy
    }


if __name__ == '__main__':
    print(lambda_handler(event=None, context=None))
```

`Lambdaで動作させる場合は、Options()の設定が必要となります。`

## 2. Lambdaへアップロードするためのzipを作成する

### スクリプト作成

- 環境に合わせて`chromedriver`と`headless-chromium`のバージョン/パスを変更する必要があります。
- 2020/5時点で動作確認済み。

```make_upload.sh
rm upload.zip
rm -r upload/
rm -r download/

mkdir -p download/bin
curl -L https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip -o download/chromedriver.zip
curl -L https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-55/stable-headless-chromium-amazonlinux-2017-03.zip -o download/headless-chromium.zip
unzip download/chromedriver.zip -d download/bin
unzip download/headless-chromium.zip -d download/bin

mkdir upload
cp -r download/bin upload/bin
cp app/lambda_function.py upload/
pip install -r app/requirements.txt -t upload/
cd upload/
zip -r ../upload.zip --exclude=__pycache__/* .
cd ../

rm -r upload/
rm -r download/
```

### upload.zip作成

```command_line.sh
sh make_upload.sh
```

## 3. Lambda関数を作成する

### 前提条件

- AWSアカウント作成済み
- Lambda関数のロール作成済み

### Lambda関数作成

1. AWSにログインする
2. サービスからLambdaを開く
3. サブメニューの`関数`を選択して`関数の作成`をクリックする
4. `一から作成`を選択して、`関数名`、`ランタイム`、`実行ロール`を入力して`関数の作成`をクリックする

## 4. Lambda関数へzipをアップロードする

`10MBを超えたためS3経由でアップロードしました。`

### アップロード

1. Lambda関数の`関数コード`セクションを表示する
2. `.zipファイルをアップロード`を選択する
3. `アップロード`から`upload.zip`を選択して`保存`をクリックする

### zipサイズが10MBを超える場合のアップロード

1. S3に`upload.zip`をアップロードする
2. Lambda関数の`関数コード`セクションを表示する
3. `Amazon S3からファイルをアップロード`を選択する
4. `Amazon S3のリンクURL`を入力して`保存`をクリックする

## 5. (補足)Layersを使ってアップロードする

### アップロードの流れ

1. `upload.zip`から`bin`を切り離してbin単体でzipを作成する
2. `Layers`に`bin`を登録する
3. Lambda関数に`Layers`を追加する
4. `.zipファイルをアップロード`を選択して`upload.zip`をアップロードする

### 5-1. `upload.zip`から`bin`を切り離してbin単体でzipを作成する

```bin.sh
bin.zip
├── chromedriver
└── headless-chromium
```

### 5-2. `Layers`に`bin`を登録する

1. サブメニューの`レイヤー`を選択して`レイヤーの作成`をクリックする
2. 任意の`名前`を入力する
3. `アップロード`をクリックして、`bin.zip`を選択する
4. `互換性のあるランタイム - オプション`を選択して`作成`をクリックする

### 5-3. Lambda関数に`Layers`を追加する

1. Lambda関数の`Designer`セクションの中心に表示される`Layers`を選択する
2. 下段に表示されるレイヤーの`レイヤーの追加`をクリックする
3. Layersに登録した`bin.zipの名前`を選択して`追加`をクリックする

### 5-4. `.zipファイルをアップロード`を選択して`upload.zip`をアップロードする

## 6. Lambda関数の環境変数を設定する

### 環境変数設定

1. Lambda関数の`環境変数`セクションを表示する

| キー              | 値                              |
| :---------------- | :------------------------------ |
| CHROMEDRIVER      | /var/task/bin/chromedriver      |
| HEADLESS_CHROMIUM | /var/task/bin/headless-chromium |

## 7. (補足)Layersを使った場合の環境変数を設定する

### Layers環境変数設定

1. Lambda関数の`環境変数`セクションを表示する

| キー              | 値                         |
| :---------------- | :------------------------- |
| CHROMEDRIVER      | /opt/bin/chromedriver      |
| HEADLESS_CHROMIUM | /opt/bin/headless-chromium |

## 8. 定期的に実行するためCronを設定する

### Cron作成

1. Lambda関数の`Designer`セクションを表示して`トリガーを追加`をクリックする
2. トリガーの設定で、`CloudWatch Events/EventBridge`を選択する
3. ルールで、`新規ルールの作成`を選択する
4. ルール名で、任意の`ルール名`を入力する
5. ルールタイプで、`スケジュール式`を選択する
6. スケジュール式で、`cron(0 17 ? * MON-FRI *)`を入力して`追加`をクリックする

### Cron式の例

| 頻度                                                      | 式                           |
| :-------------------------------------------------------- | :--------------------------- |
| 毎日午前 10:15 (UTC)                                      | cron(15 10 * * ? *)          |
| 毎週月曜日から金曜日まで午後 6:00                         | cron(0 18 ? * MON-FRI *)     |
| 毎月 1 日の午前 8:00                                      | cron(0 8 1 * ? *)            |
| 平日の 10 分ごと                                          | cron(0/10 * ? * MON-FRI *)   |
| 月曜日から金曜日まで午前 8:00 から午後 5:55 まで 5 分ごと | cron(0/5 8-17 ? * MON-FRI *) |
| 毎月最初の月曜日の午前 9 時                               | cron(0 9 ? * 2#1 *)          |