AWS Lambdaでselenium×chromeを動かす時の AWS Lambda Layers のつくり方

# この記事では
AWS Lambdaから、selenium×chromeでブラウザ操作したい時に、
Layer の作り方でハマったので投稿します。

# やりたいこと
AWS Lambdaでselenium×chromeを動かしたい。

# 環境
windows 10 Pro
python 3.7
chromdriver 2.37
headless-chromium 64.0.3282.167 

# 記載しないこと
  -AWS lambda の関数作成、呼出方法
  -selenium webdriverによるブラウザ操作ができるpythonプログラムの作り方。

# つまづいたところ
  1.AWS Lambda Layersを作る単位
  2.AWS Lambda Layersへの搭載方法と呼び出し方

##1.AWS Lambda Layersを作る単位
まず、AWS Lambda Layers とはlamda から共通的に使える共通関数のようなものです。
下記のようにLambda本体から参照される形で呼び出すことができます。
機能の1部を切り出すことで、lambda本体の軽くすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445755/c60b4796-14d0-77b1-0699-f03b6af04c3c.png)
軽くすることで、例えば下記のような、モジュール一式の容量が大きすぎるためにコードが表示できないデメリットを回避することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445755/a01f8892-c1b3-c0aa-94cc-1b8f4b877501.png)



今回は、python からselenium でchromeDriverで実装したかったので、
以下2点のLayer を作ります。

**1.seleniumのライブラリを格納するLayer**
**2.chromeDriverを格納するLayer**

↓Layer の構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445755/a7670c43-8dd1-d4ec-94da-2881912b171b.png)

以下に作り方を記載します。

###1.seleniumのライブラリを格納するLayer
####1.搭載するライブラリ一式の準備
任意のフォルダで下記コマンドを実行します。
既にpip install selenium をしていると思いますが、
搭載するライブラリを準備する作業なので、実行モジュールとは別の場所で行います。


```python:seleniumモジュールの準備
   pip install -t ./python/lib/python3.7/site-packages selenium
```

####2.以下の構成ができあがります。（windows環境で実施しています。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445755/3b1c55a9-7409-c6e2-55c1-9fe7eacdd40b.png)

####3.zip 圧縮
python フォルダからzip圧縮します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445755/6388ec03-c212-f223-ff85-751674d3d1a5.png)

####4.AWS Lambda Layers の作成
lambdaのコンソール画面から、Layers を新規作成します。
作成時に、3のzipファイルを指定して作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445755/77b85b3d-4745-1c1c-ad9f-e71865bfafc6.png)

####5.Lambda関数へのLayer追加
Lambda関数から4で作ったLayers を参照設定します。
Layer の設定画面から**カスタムレイヤー**を選択すると、選択肢に表示されるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445755/0cf93199-9e31-cced-c28b-fea90954df15.png)


####6.import で呼び出し
lambdaからimport で呼び出します。

```python:import文
from selenium import webdriver
```
もし、上記構成を間違った場合、下記エラーとなります。

```python:error文
[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'selenium'
```

###2.chromeDriverを格納するLayer

####1.driverの準備
公式配布されているdriver (2点)を準備します。
chromdriver 配布元：https://chromedriver.storage.googleapis.com/index.html?path=2.37/
headless-chromium 配布元：https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-37/stable-headless-chromium-amazonlinux-2017-03.zip

####2.zip 圧縮
上記2点を同じフォルダに配置し、linux環境でzip 圧縮します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445755/cb876ddf-6c44-decd-f4ba-8f12979f7b78.png)

もし、windows環境でzip圧縮した場合、lamdaを実行しても下記のエラーとなります。
linux 環境でも、各ファイルのパーミッションが777でないと、下記エラーとなります。

```python:error文
[ERROR] WebDriverException: Message: 'chromedriver' executable may have wrong permissions. Please see https://sites.google.com/a/chromium.org/chromedriver/home
```

windowsPCの方で、linux 環境でzip圧縮するには、下記方法があります。どちらでもいけました。
1.WSL環境でのzip ※パーミッション777にしておく
2.Docker desktopで適当な環境を作る
 （たまたま使っていた、こちらのphp環境を利用しました。 https://hub.docker.com/_/php ）


####3.Lambda関数へのLayer追加
Lambda関数から4で作ったLayers を参照設定します。
※先ほどと同じ手順となります。

####4.lambdaから呼出
lambdaから 以下パスを指定して呼びます。
AWS Lambda layersの仕様で、**/opt** に配置されるため、下記のように指定します。

```python:import文
driver = webdriver.Chrome(executable_path ="/opt/chromedriver", chrome_options=options)

```

もし、optが無いなどパスを誤った場合、エラーとなります。

```python:error文
[ERROR] WebDriverException: Message: 'chromedriver' executable needs to be in PATH. Please see https://sites.google.com/a/chromium.org/chromedriver/home
```

###もう一つ注意点
lamda もseleniumも処理が遅いので、lamdaのtimeout値を長めに設定してやります。
デフォルトは、3秒ですので、ほぼtimeoutし、下記のエラーとなります。

```python:error文
Task timed out after XX.XX seconds
```
timeout値の設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/445755/c2cba2bd-b122-aafe-a953-6d72a23243f8.png)



# 実装
lamdaのコードは、最終的に下記となります。

```python:lamda_function.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def lambda_handler(event, context):
    LINE_NOTIFY_URL = "https://notify-api.line.me/api/notify"

    options = Options()
    options.binary_location = '/opt/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 ='/opt/chromedriver', chrome_options=options)
    driver.get("https://xxxxxxxxxxx")

```

# まとめ
他の記事を見ていると、serverless.ymlやcloudFormationを使ったものが多かったのですが、
私はそのあたりの知識が弱かったため、上記方法をとりました。
初めてサーバレスでseleniumを組み込む方の参考になれば幸いです。