サーバーレスなSelenium実行環境でブラウザのスクショをS3に保存する【AWS LambdaとServerless Framework編】

# 0. はじめに

こんにちは。都内でエンジニアをしている、[@gkzvoice](https://twitter.com/gkzvoice)です。
今回はホコリを被っていた「AWS Lamda上でSelenium/Headless Chrome」の実行環境の構築手順を供養しようと思います笑。
~~勤怠打刻プログラムを作ろうかなと思ったのですが、あれがあれする気もしなくもないと考えて。~~

なお、本記事では、Serverless Frameworkを使いますが、そのインストール手順や一般的な使い方は、深く取り扱いません。
手前味噌ですが、下記の記事をご参照ください。

- [AWS LambdaとServerless Frameworkで爆速で作るTwitterbot](https://qiita.com/gkzz/items/23a7d03aeadcec341700)

# 1. 目次
- 2. 環境/バージョン情報
- 3. AWS Lambda上でSelenium環境を構築するポイント
- 4. chromedriverとheadless-chromeのインストール方法
- 5. Seleniumファイルのデプロイ手順とそのポイント
- 6. Lambdaファイルのデプロイ手順とそのポイント
- 7. chromedriverとheadless-chromeのバージョン選定ミスを防ぐ方法
- 8. 文字化け対策
- 9. 参考資料

# 2.環境/バージョン情報

### ローカル開発環境 
- serverless
    - Framework Core: 1.60.4
    - Plugin: 3.2.6
    - SDK: 2.2.1
    - Components Core: 1.1.2
    - Components CLI: 1.4.0
- **`ChromeDriver 2.40`**
- **`Headless Chromium v1.0.0-45`**

### AWS Lambda
- **`Amazon Linux`**
- Python 3.7



### Selenium環境の作成後のディレクトリ

```
gkz@localhost ~/serverless-scraper (master) $ tree -L 2
.
├── install.sh
├── lambda     ## 「6. Lambdaファイルのデプロイ手順とそのポイント」で解説
│   ├── config
│   ├── handler.py  ## Lambdaが実行するプログラム
│   ├── node_modules
│   ├── package.json
│   ├── package-lock.json
│   └── serverless.yml
├── LICENSE
├── README.md
└── selenium-layer ## 「4. chromedriverとheadless-chromeのインストール方法とその注意点」と
　　　　　　　　　　　 ##「5. Seleniumファイルのデプロイ手順とそのポイント」で解説
    ├── config
    ├── .fonts     ## 「8. 文字化け対策」で解説    
    ├── driver     ## chromedriverとchromeを保管
    ├── node_modules
    ├── package.json
    ├── package-lock.json
    ├── selenium　## Pythonのseleniumライブラリを保管
    └── serverless.yml

8 directories, 10 files
```

※本記事で紹介するサンプルコードはこちらのリポジトリにて公開しているので、ご利用ください。

- [gkzz/serverless-scraper](https://github.com/gkzz/serverless-scraper)

```
gkz@localhost ~ $ git clone https://github.com/gkzz/serverless-scraper.git \
&& cd serverless-scraper
```

# 3. AWS Lambda上でSelenium環境を構築するポイント

- serverless deployコマンドを2回叩くこと
- 1回目は**`selenium-layer/[selenium|driver]をLayersに`**デプロイするため
- 2回目の**`lambda/handler.pyをFunctionsに`**デプロイするため

```
gkz@localhost ~/serverless-scraper (master) $ ls */serverless.yml
lambda/serverless.yml  selenium-layer/serverless.yml
```

### Selenium関連ファイルがLayersに配置している様子

![Screenshot from 2020-08-26 13-11-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/4e3ac41a-2477-ce70-b8aa-2ceadf92b6b2.png)

### FunctionsにはLambdaが実行するプログラムが配置されている(おなじみ？)

![Screenshot from 2020-08-26 13-13-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/584154a9-e743-e0ff-a048-0fb9205dfc02.png)

### Lambda Layerについて
クラスメソッドさんの下記の記事でわかりやすく解説されていたので、適宜ご参照ください。

>従来のLambdaではそれぞれの関数ごとにパッケージングしてデプロイしなければならなかった、複数のLambda関数で共有するカスタムコードやライブラリをビジネスロジックから使うことができるようになります。 
>
>新しいLambda Layerのアップデートでは、共有コンポーネントを一つのZIPファイルに固めてアップロードすることができます。既存の関数は通常参照を変更する必要がありません。レイヤーはバージョンでマネージされてイミュータブルです。削除されたり許可されないバージョンへの呼び出しは拒否されます。

出所：[【速報】【アップデート】Lambdaが複数のファンクションで共有するコードを持てるようになりました(Lambda Layer) #reinvent](https://dev.classmethod.jp/articles/reinvent2018-keynote-2-lambdalayer/)


# 4. headless-chromeとchromedriverのインストール方法

- AWS Lambdaで使うSelenium関連のファイルの格納ディレクトリの作成
  - ここでは **`selenium-layer/driver`**とする
  - [gkzz/serverless-scraper](https://github.com/gkzz/serverless-scraper)をクローンした方は不要
  - このselenium-layerがAWS Lambdaの**`Lambda Layer`**配下に配置するディレクトリ

```
gkz@localhost ~/serverless-scraper (master) $ mkdir -p selenium-layer/driver
```

- headless-chromeのインストール

```
gkz@localhost ~/serverless-scraper (master) $ CHROMEVERSION="v.1.0.0-45" \
&& CHROMEFILE=https://github.com/adieuadieu/serverless-chrome/releases/download/${CHROMEVERSION}/stable-headless-chromium-amazonlinux-2017-03.zip \
&& curl -SL $CHROMEFILE > headless-chromium.zip \
&& unzip headless-chromium.zip \
&& rm headless-chromium.zip \
&& mv headless-chromium selenium-layer/driver/
```

- chromedriverのインストール


```
gkz@localhost ~/serverless-scraper (master) $ DRIVERVERSION="2.40" \
&& CHROMEDRIVER=https://chromedriver.storage.googleapis.com/${DRIVERVERSION}/chromedriver_linux64.zip \
&& curl -SL $CHROMEDRIVER > chromedriver.zip \
&& unzip chromedriver.zip \
&& rm -rf chromedriver.zip \
&& mv chromedriver selenium-layer/driver/
```

つまずきポイントは、headless-chromeとchromedriverのバージョン選定方法ですが、こちらについては、[7. chromedriverとheadless-chromeのバージョン選定ミスを防ぐ方法](https://qiita.com/gkzz/private/5ca5b1a430b63c8bd625#7-chromedriverとheadless-chromeのバージョン選定ミスを防ぐ方法)で後述します。


# 5. Seleniumファイルのデプロイ手順とそのポイント

## 5-1. デプロイ手順

- serverless frameworkをインストール

```
gkz@localhost ~/serverless-scraper (master) $ sudo npm install -g serverless
```

- **`Lambda Layer`** 配下に配置するSeleniumファイルの作成

```
gkz@localhost ~/serverless-scraper (master) $ cd selenium-layer
gkz@localhost ~/serverless-scraper/selenium-layer (master) $ npm init
```
- seleniu-layer/serverless.ymlで使う環境変数をconfig/.envに定義

```selenium-layer/config/.env
# base
STAGE=dev
REGION=ap-northeast-1
```

- Serverless Frameworkのプラグインのインストール

```
gkz@localhost ~/serverless-scraper/selenium-layer (master) $ sudo npm install --save serverless-dotenv-plugin
```

- seleniumを以下のディレクトリにインストール
  - **`selenium/python/lib/python3.7/site-packages selenium`** 
  - ここで指定したディレクトリ(ここではselenium)がLambda Layers配下に配置される

```
gkz@localhost ~/serverless-scraper/selenium-layer (master) $ pip install -t selenium/python/lib/python3.7/site-packages selenium
```

- selenium-layer/serverless.ymlを編集

```selenium-layer/serverless.yml
service: selenium-layer

custom:
  dotenv:
    basePath: ./config/
  pythonVer: python3.7
  stage: ${env:STAGE}
  region: ${env:REGION}

provider:
  name: aws
  runtime: ${self:custom.pythonVer}
  stage: ${self:custom.stage}
  region: ${self:custom.region}

plugins:
  - serverless-dotenv-plugin

layers:
  selenium:
    path: selenium
    description: selenium layer
    CompatibleRuntimes:
      - ${self:custom.pythonVer}
  chromedriver:
    path: driver
    description: chrome driver layer
    CompatibleRuntimes:
      - ${self:custom.pythonVer}

## 「5-2. Selenium-layerとLambda間でLayerを共有する仕組み」
resources:
  Outputs:
    SeleniumLayerExport:
      Value:
        Ref: SeleniumLambdaLayer
      Export:
        Name: SeleniumLambdaLayer
    ChromedriverLayerExport:
      Value:
        Ref: ChromedriverLambdaLayer
      Export:
        Name: ChromedriverLambdaLayer
```

- Selenium関連ファイルをデプロイ！

```
gkz@localhost ~/serverless-scraper/selenium-layer (master) $ serverless deploy
```

※Selenium関連ファイルをデプロイすると、Layersにchromedriverとseleniumが配置されていることが確認できます。


## 5-2. Selenium-layerとLambda間でLayerを共有する仕組み

- layersでselenium関連のファイルを格納し、resourcesで次にデプロイするLambda側のserverless.ymlで活用する
 
  - Exportで出力するSeleniumLambdaLayerやChromedriverLambdaLayerをRefで取得し、
  後続のLambda側のserverless.ymlのlayersで参照出来るようにする

- このLayer周りに関する記述がServerless Frameworkのドキュメントで見当たらず、下記の記事を参考にデバッグしながら実装したので正確に説明できているか自信がないです。
  - [Serverlessを使って簡単にAWS Lambda Layers上でHeadless Chromeを動かす](https://blog.ikedaosushi.com/entry/2018/12/22/231421) 

- 参考資料などありましたら、コメントにてご共有いただけるとうれしいです。

  - Refなどの書き方はCloudFormationからきているというのは聞いたことがあるのですが。。

>同じスタックのリソース(もしくはパラメータ)を参照したい場合は Ref を使う 
>別のスタックのリソース(もしくは何かしらの値)を参照したい場合は エクスポートされている名前、値を確認した上で ImportValue を使う

参考：[CloudFormation の参照周りで意識すべきポイント・Tips](https://dev.classmethod.jp/articles/cloudformation-tips-focused-on-refs/)


# 6. Lambdaファイルのデプロイ手順とそのポイント

## 6-1. デプロイ手順

- 必要なプラグインをインストール

```
gkz@localhost ~/serverless-scraper (master) $ cd lambda
gkz@localhost ~/serverless-scraper/lambda (master) $ npm init
$ sudo npm install --save serverless-python-requirements \
> && sudo npm install --save serverless-dotenv-plugin \
> && sudo npm install --save serverless-offline

```

- lambda/serverless.ymlで使う環境変数をconfig/.envに定義

```lambda/config/.env
# base
STAGE=dev
REGION=ap-northeast-1

# project dir
projectDir=serverless-scraper

```

- lambda/serverless.ymlを編集

```lambda/serverless.yml
service: lambda

custom:
  dotenv:
    basePath: ./config/
  projectDir: ${env:projectDir}
  pythonVer: python3.7
  stage: ${env:STAGE}
  region: ${env:REGION}
  timeout: 900
  TZ: Asia/Tokyo
  seleniumLayer: selenium-layer

provider:
  name: aws
  runtime: ${self:custom.pythonVer}
  stage: ${self:custom.stage}
  region: ${self:custom.region}
  timeout: ${self:custom.timeout}」
  # selenium側のserverless.ymlの「service」の値
  environment:
    SELENIUM_LAYER_SERVICE: ${self:custom.seleniumLayer}
    TZ: ${self:custom.TZ}
  iamRoleStatements:
    - Effect: 'Allow'
      Action:
        - "lambda:InvokeFunction"
        - "lambda:InvokeAsync"
        - "s3:ListBucket"
        - "s3:GetObject"
        - "s3:PutObject"
        - "s3:DeleteObject"
      # S3に保存するスクリーンショットのパス
      Resource:
        Fn::Join:
          - ""
          - - "arn:aws:s3:::"
            - ${self:custom.projectDir}-${self:custom.stage}
            - "/*"

plugins:
  - serverless-python-requirements
  - serverless-dotenv-plugin
  - serverless-offline

functions:
  main:
    handler: handler.main
　　 # seleniumとChromeのLayersにおけるパス
    layers:
      - ${cf:${self:custom.seleniumLayer}-${self:custom.stage}.SeleniumLayerExport}
      - ${cf:${self:custom.seleniumLayer}-${self:custom.stage}.ChromedriverLayerExport}
    environment:
      S3BUCKET: ${self:custom.projectDir}-${self:custom.stage}
    events:
#      - schedule: cron(0/40 * * * ? *)  # 毎日40分おき
      - schedule: cron(3 * * * ? *) #毎日NN時03分おき
package:
  include: 
    - '.fonts/**'

resources:
  Resources:
    Bucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:custom.projectDir}-${self:custom.stage}
```

- lambda/handler.pyを編集

```lambda/handler.py
import os
import json
import random
import time
import datetime
import logging
import traceback
import boto3

# selenium
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By 
from selenium.webdriver.support.ui import WebDriverWait 
from selenium.webdriver.support import expected_conditions as EC 
from selenium.common.exceptions import TimeoutException 
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys


# set up Logger
import logging
import sys
logger = logging.getLogger()
for h in logger.handlers:
    logger.removeHandler(h)

handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(logging.Formatter(
    '%(levelname)s %(asctime)s [%(funcName)s] %(message)s'))
logger.addHandler(handler)
logger.setLevel(logging.INFO)
#logger.setLevel(logging.DEBUG)


def set_selenium_options():
    """ Set selenium options """
    options = Options()
    options.binary_location = '/opt/headless-chromium'
    options.add_argument('--headless')
    options.add_argument('--window-size=1280,1024')
    options.add_argument('--no-sandbox')
    options.add_argument('--single-process')
    options.add_argument('--disable-dev-shm-usage')
    options.add_argument("--homedir=/tmp")

    return webdriver.Chrome('/opt/chromedriver', chrome_options=options)


def wait_until_element_present(driver, key, location):
    """ Wait until element is presented at location """
    elm = None
    counter = 4
    timeup = 20
    while counter < timeup:
        try:
            elm = WebDriverWait(driver, counter).until(
                EC.presence_of_element_located((key, location)))
        except NoSuchElementException as e:
            logger.warn("[WARN] {e}".format(e=e))
            logger.warn("counter: {val}".format(val=counter))
            counter += 2
            continue
        except TimeoutException as e:
            logger.warn("[WARN] {e}".format(e=e))
            logger.warn("counter: {val}".format(val=counter))
            counter += 2
            continue
        else:
            break
    
    return elm

def get_text_by_xpath(driver, location):
    return driver.find_element_by_xpath(location).text


def save_screenshot(driver, filename):
    """ Save screenshot at Amazon S3 """
    s3client = boto3.client('s3')
    driver.save_screenshot("/tmp/" + filename)
    bucket = os.environ.get("S3BUCKET", "")
    s3client.upload_file(
        Filename="/tmp/" + filename,
        Bucket=bucket,
        Key=filename
    )
    return


def terminate_driver(driver):
    """ Terminate driver """
    driver.close()
    driver.quit()
    return


def main(event, context):
    """ Entrypoint of lambda """

    # Debug event to CloudWatch log
    starttime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    logging.info("starttime: {}".format(starttime))
    logging.info(json.dumps(event))

    driver = None
    target = None
    url = "https://gkzz.github.io"
    location = '//*[@id="featured"]/article/ul[1]/li[2]/p'

    try:        
        driver = set_selenium_options()
        driver.maximize_window()
        driver.get(url)
        presented = wait_until_element_present(
            driver, By.XPATH, location)
        save_screenshot(
            driver, 'ss_' + datetime.datetime.now().strftime('%Y%m%d_%H%M%S' + '.png'))
        if presented is not None:
            target = driver.find_element_by_xpath(location).text

        endtime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        logging.info("endtime: {}".format(endtime))
        terminate_driver(driver)
        return {
            "statusCode": 200,
            "body": target
        }
    except Exception as e:
        logger.error("[ERROR] {e}".format(e=e))
        terminate_driver(driver)
        return {
            "statusCode": 400,
            "body": e
        }

```
- lambda/handler.pyとlambda/.fonts/*.ttfファイルをデプロイ！

```
$ serverless deploy
```

- ローカルから実行してみる

```
gkz@localhost ~/serverless-scraper/lambda (master)$ sls invoke -f main
Serverless: DOTENV: Loading environment variables from ./config/.env:
Serverless: 	 - STAGE
Serverless: 	 - REGION
Serverless: 	 - projectDir
{
    "statusCode": 200,
    "body": "Current: Software Engineer at AP Communications Co., Ltd."
}
```
- スクリーンショットがS3に保存されていることが確認できました！！

![Screenshot from 2020-08-26 13-14-16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/cfa57e30-ec03-24d1-1097-74baa004d7a5.png)

![ss_20200822_022710.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/7a7964c1-e67c-26ca-3dd1-ea1971e005e2.png)



# 7. chromedriverとheadless-chromeのバージョン選定ミスを防ぐ方法

## 7-1. ChromeとChromeDriverのバージョン指定を誤った場合、デプロイ時にこのようなエラーを引く

- ローカルからAWS Lambdaを実行

```
gkz@localhost ~/serverless-scraper/lambda (master) $ serverless invoke -f main
Serverless: DOTENV: Loading environment variables from ./config/.env:
Serverless:      - STAGE
Serverless:      - REGION
Serverless:      - projectDir
{
    "errorMessage": "'NoneType' object has no attribute 'close'",
    "errorType": "AttributeError",
    "stackTrace": [
        "  File \"/var/task/handler.py\", line 133, in main\n    terminate_driver(driver)\n",
        "  File \"/var/task/handler.py\", line 95, in terminate_driver\n    driver.close()\n"
    ]
}
 
  Error --------------------------------------------------
 
  Error: Invoked function failed
      at AwsInvoke.log (/usr/local/lib/node_modules/serverless/lib/plugins/aws/invoke/index.js:105:31)
      at AwsInvoke.tryCatcher (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/util.js:16:23)
      at Promise._settlePromiseFromHandler (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:547:31)
      at Promise._settlePromise (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:604:18)
      at Promise._settlePromise0 (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:649:10)
      at Promise._settlePromises (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:729:18)
      at _drainQueueStep (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:93:12)
      at _drainQueue (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:86:9)
      at Async._drainQueues (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:102:5)
      at Immediate.Async.drainQueues [as _onImmediate] (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:15:14)
      at processImmediate (internal/timers.js:456:21)
 
     For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.
 
  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com
 
  Your Environment Information ---------------------------
     Operating System:          linux
     Node Version:              12.18.3
     Framework Version:         1.79.0
     Plugin Version:            3.7.1
     SDK Version:               2.3.1
     Components Version:        2.34.6
 
```


issueでも、バージョン指定のミスが原因とコメントがありました。

> After a lot of tooling around, I got it to work on Lambda. The problem was **`with incompatible versions of serverless-chrome, chromedriver, and Selenium`**. These are the versions that play well together in Lambda. Why is beyond me:

> chromedriver v.2.37
severless-chrome v.0.0-37
selenium 2.53.6 (for Python)

参考：[Chrome not reachable with Selenium Python · Issue #133 · adieuadieu/serverless-chrome](https://github.com/adieuadieu/serverless-chrome/issues/133#issuecomment-382743975)

### 7-2. バージョンの確認方法

#### 例）Chromeのバージョンはv1.0.0-55としたとき、対応するChromeDriverのバージョンを確認する

![serverless-scraper-setup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/8bea829d-02eb-d482-919c-2d873b5aa2ba.png)



- 1) [スクリーンショットの左側の「Chromeのバージョン確認ページ」](https://github.com/adieuadieu/serverless-chrome/releases)からChromeの任意のバージョンを決める
  - ここでは**`v1.0.0-55`**とする
- 2) [スクリーンショットの左側の「Chromeのバージョン確認ページ」](https://chromedriver.chromium.org/downloads)で決めたChromeのバージョンの少し下のbeta/dev/stableのいずれかからChromeのバージョン名とzipリンクを控えておく


|例         |          |
|----------|-----------|
|バージョン名|chromium 67.0.3396.99 (stable channel) for amazonlinux:2017.03|
|zipリンク|[https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-50/stable-headless-chromium-amazonlinux-2017-03.zip](https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-50/stable-headless-chromium-amazonlinux-2017-03.zip)|


- 3) [スクリーンショットの右側の「ChromeDriverのバージョン確認ページ」](https://github.com/adieuadieu/serverless-chrome/releases)から、**`2で控えていたChromeの任意のバージョンを探す`**

例）バージョン名が**`chromium 67.0.3396.99 (stable channel) for amazonlinux:2017.03`**の場合

![Screenshot from 2020-08-29 15-56-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/0bbc0a6e-fbdb-e0f4-3a99-df03155ccd69.png)

- 4) これでChromeとChromeDriverのバージョンはこのように確認できた。

|例         |          |
|----------|-----------|
|Chromeのバージョン名|chromium 67.0.3396.99 (stable channel) for amazonlinux:2017.03|
|zipリンク|[https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-50/stable-headless-chromium-amazonlinux-2017-03.zip](https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-50/stable-headless-chromium-amazonlinux-2017-03.zip)|
|ChromeDriverのバージョン名|ChromeDriver 2.41|
|zipリンク|https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip|


出所：

- [Releases · adieuadieu/serverless-chrome](https://github.com/adieuadieu/serverless-chrome/releases)
- [Downloads - ChromeDriver - WebDriver for Chrome](https://chromedriver.chromium.org/downloads)


### ダウンロードリンクが公開されていない場合

本記事執筆時点ではChromeDriver 2.41のダウンロードリンクが公開されていませんでした。
そこで、本記事では[Wayback Machine ](https://web.archive.org)を使ってダウンロードリンクを調べました。

![Screenshot from 2020-08-29 16-08-39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/341653/8b00aa19-bf1b-9f19-5f6e-4d8c8cbf5987.png)

参考：[web.archive.org/web/20180731191946| Downloads - ChromeDriver - WebDriver for Chrome](https://web.archive.org/web/20180731191946/http://chromedriver.chromium.org/downloads)


# 8. 文字化け対策

下記の記事を参考に`lambda/.fonts`配下に[IPAexフォント ダウンロードページ
Download IPAex Fonts](https://ipafont.ipa.go.jp/old/ipaexfont/download.html)からipaexg.ttfとipaexm.ttfを配置します。

```
gkz@localhost ~/serverless-scraper (master) $ ls lambda/.fonts/
ipaexg.ttf  ipaexm.ttf
```

>最終的に、”.fonts”ディレクトリをLambdaのパッケージに含めて一緒にデプロイすることで解決しました

出所：[Headless ChromeとSeleniumをLambdaで動かす](https://xp-cloud.jp/blog/2020/01/22/6637/)

# 9. 参考資料

- サンプルコード
  - [github.com/gkzz/serverless-scraper](https://github.com/gkzz/serverless-scraper)
- Lambda Layerについて
  - [【速報】【アップデート】Lambdaが複数のファンクションで共有するコードを持てるようになりました(Lambda Layer) #reinvent](https://dev.classmethod.jp/articles/reinvent2018-keynote-2-lambdalayer/)
- seleniumとChrome、ChromeDriverの使い方
  - [Serverlessを使って簡単にAWS Lambda Layers上でHeadless Chromeを動かす](https://blog.ikedaosushi.com/entry/2018/12/22/231421)
- Refなどの書き方
  - [CloudFormation の参照周りで意識すべきポイント・Tips](https://dev.classmethod.jp/articles/cloudformation-tips-focused-on-refs/)
-  chromedriverとheadless-chromeのバージョン選定ミスに関するIssue 
  - [Chrome not reachable with Selenium Python · Issue #133 · adieuadieu/serverless-chrome](https://github.com/adieuadieu/serverless-chrome/issues/133#issuecomment-382743975)
- 文字化け対策
  - [Headless ChromeとSeleniumをLambdaで動かす](https://xp-cloud.jp/blog/2020/01/22/6637/)  

## P.S. Twitterもやってるのでフォローしていただけると泣いて喜びます:)
[@gkzvoice](https://twitter.com/gkzvoice)