Lambda(Python)でSeleniumが動かないのでバージョンを調整して解決した件

当記事は、半年以上前に投稿されたものです。そのため、古い技術や情報をもとに書かれている可能性があります。参照する際は十分に注意していただければです。

はじめに

Lambda(Python)でスクレイピングしたくて Selenium とか ChromeDriver でゴニョゴニョしようとしたのですが、案の定エラーに詰まって動きませんでした。

Lambda 上で headless-chrome を使うために導入した serverless-chrome と Python とのバージョン齟齬、Selenium と ChromeDriver のバージョン齟齬など、今回は Python、Selenium、ChromeDriver、serverless-chrome の各バージョンを揃える必要がありました。

serverless-chrome の Issue に上記の解決法が載っているで、今回はこの Issue を参考に AWS SAM や Lambda Layer を使って簡単なスクレイピングをしてみました。

Serverless Chrome?

Serverless Chromeには、AWS LambdaでヘッドレスChromeの実行を開始するために必要なものがすべて含まれています(Azure機能とGCP機能は間もなく)。

このプロジェクトの目的は、サーバーレス関数の呼び出し中にヘッドレスChromeを使用するための足場を提供することです。サーバーレスChromeは、Chromeバイナリの構築とバンドルを処理し、サーバーレス機能の実行時にChromeが実行されていることを確認します。

さらに、このプロジェクトは、一般的なパターンのいくつかのサンプルサービスも提供します(例:ページのスクリーンショットを撮る、PDFに印刷する、一部を削るなど)。

adieuadieu/serverless-chrome の README の翻訳より

バージョン調整(2020/04/14 時点)

Issue を参考に動作確認を行ったバージョン関係です。

注意することは Python 3.8 では動作しないことでした。

サンプル

とりまディレクトリ構造

あくまでもサンプルです。

sam init してゴニョゴニョした後に完成するディレクトリ構造です。

不要なものは消しています。

$ tree .
.
├── layer
│   └── bin
│       ├── chromedriver
│       └── headless-chromium
├── sample
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
└── template.yaml

chromedriver等の実行ファイルをダウンロード

# LambdaLayer用のディレクトリ作成
$ mkdir -p layer/bin

# ダウンロード Chrome Driver 2.43
$ wget https://chromedriver.storage.googleapis.com/2.43/chromedriver_linux64.zip -P layer/bin
$ unzip layer/bin/chromedriver_linux64.zip

# ダウンロード Serverless Chrome v1.0.0-55
$ wget https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-55/stable-headless-chromium-amazonlinux-2017-03.zip -P layer/bin
$ unzip layer/bin/stable-headless-chromium-amazonlinux-2017-03.zip

サンプル template.yaml

注意したいのは Python のランタイムが 3.6 であることです。

layer は、コンテナ上で /opt/ 直下に配置されます。

layer/bin/*** であれば /opt/bin/*** に置かれます。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Timeout: 30

Resources:
  ServerlessFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: sample-selenium-function
      CodeUri: sample/
      Handler: app.lambda_handler
      Runtime: python3.6
      Layers:
        - !Ref ServerlessLayer

  ServerlessLayer:
    Type: AWS::Serverless::LayerVersion
    Properties:
      LayerName: sample-selenium-layer
      ContentUri: layer/

requirements.txt に Selenium ライブラリを追加

Lambda Layer をせっかく使っているので layer/python/ 等に固めるが良いのかなとも思っていますが、今回は requirements.txt で単純にライブラリ管理しています。

cat << EOF >> sample/requirements.txt
selenium
EOF

サンプル app.py(Lambda)

Yahooニュースを対象に class から要素を print する簡単なサンプルになります。 詳しい操作方法はこちらの API ドキュメントを参考にしてもらえればです。

import json
from selenium import webdriver

CHROME_DRIVER_PATH = "/opt/bin/chromedriver"
HEADLESS_CHROMIUM_PATH = "/opt/bin/headless-chromium"

TARGET_URL = "https://news.yahoo.co.jp/"

def lambda_handler(event, context):

    options = webdriver.ChromeOptions()
    options.binary_location = HEADLESS_CHROMIUM_PATH
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument('--single-process')

    chrome_driver = webdriver.Chrome(
        executable_path = CHROME_DRIVER_PATH,
        chrome_options = options)

    chrome_driver.get(TARGET_URL)

    print(chrome_driver.find_element_by_class_name('topics_title').text)

    chrome_driver.close()
    chrome_driver.quit()

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "success",
        }),
    }

出力結果

$ sam build
$ sam local invoke
>> トピックス

おわりに

全体的にバージョン低めなので慎重に運用する必要がありそうだなと思っていますが、比較的簡単にスクレイピングできてしまう Lambda はやっぱりすごいですね。

ひよっこですが、簡単なサービスをどんどん Lambda で組んでみたいです。

参考文献


Canji

クラウド周りをちょこまかしたい注意散漫人間。個人開発を楽しんでいたあの頃。