1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

YAMAP エンジニアAdvent Calendar 2022

Day 5

AWS Lambda + Serverless Framework + Selenium でつまずいたこと

Last updated at Posted at 2022-12-04

これは YAMAP エンジニア Advent Calendar 2022 5日目の記事です。

はじめに

以前に Heroku で Selenium を動かすということをやっていたのですが、諸般の事情により Heroku から AWS Lambda への移行が必要となりました。簡単にできるだろうと高をくくっていたのですが、いくつかハマりポイントがあったのでここに記しておきます。

やりたいこと

とある Web サイトを表示して、スクリーンショットを AWS S3 に保存します。

結論

最終的には以下の構成となりました。

環境/バージョン情報

  • serverless
    • Framework Core: 3.24.1
    • Plugin: 6.2.2
    • SDK: 4.3.2
  • ChromeDriver 2.40
  • Headless Chromium v1.0.0-45
  • AWS Lambda
    • Amazon Linux
    • Python 3.7

ディレクトリ構成

lambda: Selenium を実行する Lambda 関数
selenium-layer: Selenium 関連と font ファイルをアップロードする Lambda Layer

$ tree -L 2
.
├── lambda
│   ├── handler.py
│   └── serverless.yml
├── node_modules
└── selenium-layer
    ├── driver
    ├── fonts
    ├── selenium
    └── serverless.yml

Selenium と font を Lambda Layer にデプロイする

  • serverless framework をインストール
$ npm install -g serverless
  • headless-chrome をインストール
$ CHROMEVERSION="v1.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 をインストール
$ 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/
  • Selenium をインストール
$ pip install -t selenium/python/lib/python3.7/site-packages 'selenium<4'
  • selenium-layer/fonts 配下にフォントファイルを配置
    フォントは IPAex フォント を利用しました。
.
└── selenium-layer
    └── fonts
        └── .fonts
            ├── ipaexg.ttf
            └── ipaexm.ttf
  • selenium-layer/serverless.ymlを編集
service: selenium-layer

custom:
  pythonVer: python3.7
  stage: dev
  region: ap-northeast-1

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

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

resources:
  Outputs:
    SeleniumLayerExport:
      Value:
        Ref: SeleniumLambdaLayer
      Export:
        Name: SeleniumLambdaLayer
    ChromedriverLayerExport:
      Value:
        Ref: ChromedriverLambdaLayer
      Export:
        Name: ChromedriverLambdaLayer
    FontsLayerExport:
      Value:
        Ref: FontsLambdaLayer
      Export:
        Name: FontsLambdaLayer
  • デプロイする
$ sls deploy

Lambda 関数をデプロイする

  • lambda/serverless.ymlを編集
service: example

custom:
  pythonVer: python3.7
  timeout: 30
  memorySize: 1024
  stage: dev
  region: ap-northeast-1
  seleniumLayer: selenium-layer

frameworkVersion: '3'

useDotenv: true

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

  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:ListBucket"
        - "s3:GetObject"
        - "s3:PutObject"
        - "s3:DeleteObject"
      Resource:
        Fn::Join:
          - ""
          - - "arn:aws:s3:::"
            - ${env:AWS_S3_BUCKET_NAME}
            - "/*"

  environment:
    AWS_S3_BUCKET_NAME: ${env:AWS_S3_BUCKET_NAME}
    REGION: ${self:custom.region}

package:
  include: 
    - ".fonts/**"

functions:
  hello:
    handler: handler.hello
    reservedConcurrency: 1
    layers:
      - ${cf:${self:custom.seleniumLayer}-${self:custom.stage}.SeleniumLayerExport}
      - ${cf:${self:custom.seleniumLayer}-${self:custom.stage}.ChromedriverLayerExport}
      - ${cf:${self:custom.seleniumLayer}-${self:custom.stage}.FontsLayerExport}

resources:
  Resources:
    Bucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${env:AWS_S3_BUCKET_NAME}
  • .env を編集する
AWS_S3_BUCKET_NAME=xxxxxx
  • handler.py を編集する
import traceback
import requests
import json
import time
import datetime
import boto3
import os

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


# .fonts を参照するため
os.environ["HOME"] = "/opt/"


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

    try:
        driver = webdriver.Chrome(executable_path="/opt/chromedriver", chrome_options=options)
    except Exception as e:
        print(traceback.format_exc())
    return driver


def upload_s3(file_path):
    s3 = boto3.client("s3")
    bucket_name = os.environ["AWS_S3_BUCKET_NAME"]
    s3.upload_file(file_path, bucket_name, file_path)


def hello(event, context):
    try:
        driver = create_driver()
        wait = WebDriverWait(driver, 10)
        driver.get("https://example.com")

        page_width = driver.execute_script("return document.body.scrollWidth")
        page_height = driver.execute_script("return document.body.scrollHeight")
        driver.set_window_size(page_width, page_height)

        file_path = "/tmp/example.png"
        driver.save_screenshot(file_path)

        upload_s3(file_path)
        os.remove(saved_file_path)
    except Exception as e:
        print(traceback.format_exc())
        return error_response()
  • デプロイする
$ sls deploy

つまずいたこと

Selenium 起動時に Timeout エラーになる

エラーログ上は Timeout しか出力されないので調査が難航しました。
こちらは以下のバージョンを使用することで解決しました。
(もっと新しいバージョンの組み合わせでも動作するかもしれませんが、時間が無く未調査です。。)

  • Python のバージョンを 3.8 -> 3.7 に変更
  • Selenium のバージョンを 4系 -> 3系に変更
  • headless-chrome のバージョンを v1.0.0-45 に変更
  • chromedriver のバージョンを 2.40 に変更

日本語が文字化けする

Heroku で実行したときも同じ問題が発生しました。 .fonts ディレクトリにフォントファイルを配置したら良いのですが、Lambda Layer を利用する際は参照先の設定に注意が必要です。上記のパス指定で解決しました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?