フリーランチ食べたい

No Free Lunch in ML and Life. Pythonや機械学習のことを書きます。

Serverlessを使って簡単にAWS Lambda Layers上でHeadless Chromeを動かす

この記事は Serverless2 Advent Calendar 2018 - Qiita20日目です。

f:id:mergyi:20181222231323p:plain

  • 今年開催された re:Invent2018でAWS Lambda Layersが発表されました。
  • このアップデートによってC拡張のライブラリや容量の大きいライブラリの利用がより便利になりました。
  • その中でも特に自分が最初に「使ってみたい!」と思ったHeadless Chrome on Seleniumの使い方を解説します。
  • 自分が普段使っているServerless Frameworkは既にAWS Lambda Layersに対応しており、これを使ったやり方になります。

最終的なフォルダ構成&Versions

ここから作業していきますが、先に最終的なフォルダ構成を書いておきます。

tree -L 2
├── lambda # Layerを使うアプリケーションのディレクトリ
│   ├── handler.py
│   └── serverless.yml
└── selenium-layer # Layerのディレクトリ
    ├── chromedriver
    │   ├── chromedriver
    │   └── headless-chromium
    ├── selenium
    │   └── python
    └── serverless.yml

また利用するスタックのVersionはこちらです。

  • serverless-framework: 1.35.1
  • Selenium: 3.141.0
  • ChromeDriver: 2.37
  • serverless-chrome: 1.0.0-41

Layersの作成

まずLayerを作成します。 最初にLayer作成用のディレクトリを作成して、そこで作業しましょう。

cd ${project_dir}
mkdir selenium-layer
cd selenium-layer

必要なファイル群をローカルに準備

それでは、必要なファイル群を準備していきましょう

Selenium

Seleniumはpipでインストールします。パスの python/ 以下はLayerで使うための決まったパスになります。

pip install -t selenium/python/lib/python3.6/site-packages selenium

Seleniumはそうではないですが、numpyなどのC拡張ライブラリをインストールする場合はDocker Imageのlambci/docker-lambdaを使う必要があります。下のような感じになると思います。

docker run --rm -v $(pwd):/var/task -w /var/task lambci/lambda:build-python3.6 pip install -t numpy/python/lib/python3.6/site-packages numpy

Headless Chrome/Chrome Driver

次にHeadless ChromeChrome Driverをインストールします。 まず作業場所を移動します。

cd ${project_dir}/selenium-layer
mkdir headless-chrome
cd headless-chrome

Headless Chrome

Headless ChromeAWS Lambda用に切り出されたserverless-chromeを使います。

GitHub - adieuadieu/serverless-chrome: 🌐 Run headless Chrome/Chromium on AWS Lambda (maybe Azure, & GCP later)

curl -SL https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-41/stable-headless-chromium-amazonlinux-2017-03.zip > headless-chromium.zip
unzip headless-chromium.zip
rm headless-chromium.zip

これでOKです。

Chrome Driver

Chrome Driverは公式のDriverを使います。

Downloads - ChromeDriver - WebDriver for Chrome

curl -SL https://chromedriver.storage.googleapis.com/2.37/chromedriver_linux64.zip > chromedriver.zip
unzip chromedriver.zip
rm chromedriver.zip

これでOKです。

AWS LambdaにLayerを作成

必要なファイルが準備できたので、AWS Lambdaにデプロイしていきたいと思います。 serverless.ymlを下記のように作成します。

cd ${project_dir}/selenium-layer
vim serverless.yml
service: selenium-layer

provider:
  name: aws
  runtime: python3.6
  stage: dev
  region: ap-northeast-1

layers:
  selenium:
    path: selenium
    description: selenium layer
    CompatibleRuntimes: 
      - python3.6
  chromedriver:
    path: chromedriver
    description: chrome driver layer
    CompatibleRuntimes:
      - python3.6

resources:
  Outputs:
    SeleniumLayerExport:
      Value:
        Ref: SeleniumLambdaLayer
      Export:
        Name: SeleniumLambdaLayer
    ChromedriverLayerExport:
      Value:
        Ref: ChromedriverLambdaLayer
      Export:
        Name: ChromedriverLambdaLayer

その後はdeployするだけです。

sls deploy

AWS Consoleを開くとLayerが出来ていることがわかります。

f:id:mergyi:20181222181704p:plain
AWS ConsoleでLayerが作成できていることを確認

またresources以下で下記のように設定していることでCloudFormationのStackをExportすることができ、他のserverless frameworkのサービスからも簡単に参照できるようになります。 Ref で参照している SeleniumLambdaLayer は serverless frameworkが自動で作っている名前で {Layer名}LambdaLayer という名前で作られるようです。

resources:
  Outputs:
    SeleniumLayerExport:
      Value:
        Ref: SeleniumLambdaLayer
      Export:
        Name: SeleniumLambdaLayer
    ChromedriverLayerExport:
      Value:
        Ref: ChromedriverLambdaLayer
      Export:
        Name: ChromedriverLambdaLayer

f:id:mergyi:20181222184436p:plain
StackがExportされていることを確認

※参考: https://serverless.com/framework/docs/providers/aws/guide/variables#reference-cloudformation-outputs

Layerを利用するサービスの作成

Layerを作成できたので、Layerを使うサービスを作成します。

cd ${project_dir}
mkdir lambda
cd lambda

準備

serverless.ymlを作成します。

vim serverless.yml
service: crawler-with-selenium

provider:
  name: aws
  runtime: python3.6
  stage: dev
  region: ap-northeast-1
  timeout: 900
  environment:
    SELENIUM_LAYER_SERVICE: selenium-layer

functions:
  hello:
    handler: handler.hello
    layers:
      - ${cf:${self:provider.environment.SELENIUM_LAYER_SERVICE}-${opt:stage, self:provider.stage}.SeleniumLayerExport}
      - ${cf:${self:provider.environment.SELENIUM_LAYER_SERVICE}-${opt:stage, self:provider.stage}.ChromedriverLayerExport}

次にhandler.pyを作成します。このBlogにアクセスしてタイトルを取ってくるだけのシンプルなスクリプトです。

vim hander.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def hello(event, context):
    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('/opt/chromedriver', chrome_options=options)

    driver.get('https://blog.ikedaosushi.com/')
    body = f"Blog title is: {driver.title}"

    driver.close();
    driver.quit();

    response = {
        "statusCode": 200,
        "body": body
    }

    return response

デプロイ&実行

あとはデプロイして実行するだけです。 デプロイします。

sls deploy

デプロイが成功するとAWS ConsoleからLayerが正しく参照できていることが確認できます。

f:id:mergyi:20181222194745p:plain
関数からLayerが参照できている

最後に実行します。

sls invoke -f hello
# {
#   "statusCode": 200,
#    "body": "Blog title is: フリーランチ食べたい"
# }

無事、実行することができました。

終わりに

  • Serverless FrameworkからAWS Lambda Layersを使ってHeadless Chrome on Seleniumを動かす方法を解説しました
  • 今までSeleniumを使う案件ではLambdaの利用を躊躇してしまっていたのですが、AWS Lambda Layersを使うことでガンガン使っていけそうですね
  • 個人的には今年のre:inventで一番うれしいアップデートでした
  • これからもServerlessを楽しんでいきましょう!

参考記事