はじめに
AWS SAMを使ってAWS LAMDA上でselenium3を使う方法をご紹介いたしましたが、当初目的が別にありました。
きっかけはこちらの方の記事でした。
selenium4の最新版を利用すれば、新しく追加されたselenium-managerという機能があり、簡易的なChromeブラウザと、そのバージョンにあったChromDriverを自動でインストールしてくれます。
また古いHeadless-Chromiumはselenium4に対応していないため、最新のchromeに対応させたかったのですが、なぜか手元のマシン(M2 Macbookpro)で動かず、手詰まりになっていました。
この記事は動作するようになったコードの紹介と、その解決方法についてのまとめです。
こんな方におすすめ
- AWS Lambda python3.8以上でselenium4を使いたい方
- AWS SAMを使っている方
- Apple Silicon製のMac(M1,M2チップ)でAWS Lambda seleniumのローカルテストがうまくいかない方
つくったもの
- AWS SAMを利用して、AWS Lambdaで動作するpython版selenium4
- selenium4のselenium-managerを利用して、chromeブラウザとchromedriverを自動選定でインストール
- AWS SAMなので、ローカル実行でテストができる
- Apple Silicon製のMacでも動作
動作検証環境
- MacBookPro (Apple M2 Pro)
- Docker Desktop 4.31.0 (153195)
- SAM CLI, version 1.100.0
レポジトリ
ソースコードはこちらから
手順
操作の流れです。ローカルに構築し、ローカルテスト後にデプロイ。デプロイしたものをコンソールでテストしました。
テストコードは書いていませんので、実行テストのみです。
SAMでテンプレートを作成
SAM CLIでフォルダにアプリケーションの雛形を作ります。
sam --init --name python-selenium4
❯ sam init --name sam-python-selenium4
You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Choose an AWS Quick Start application template
1 - Hello World Example
2 - Data processing
3 - Hello World Example with Powertools for AWS Lambda
4 - Multi-step workflow
5 - Scheduled task
6 - Standalone function
7 - Serverless API
8 - Infrastructure event management
9 - Lambda Response Streaming
10 - Serverless Connector Hello World Example
11 - Multi-step workflow with Connectors
12 - GraphQLApi Hello World Example
13 - Full Stack
14 - Lambda EFS example
15 - Hello World Example With Powertools for AWS Lambda
16 - DynamoDB Example
17 - Machine Learning
Template: 1
Use the most popular runtime and package type? (Python and zip) [y/N]: n
Which runtime would you like to use?
1 - aot.dotnet7 (provided.al2)
2 - dotnet6
3 - go1.x
4 - go (provided.al2)
5 - graalvm.java11 (provided.al2)
6 - graalvm.java17 (provided.al2)
7 - java17
8 - java11
9 - java8.al2
10 - java8
11 - nodejs18.x
12 - nodejs16.x
13 - nodejs14.x
14 - python3.9
15 - python3.8
16 - python3.7
17 - python3.11
18 - python3.10
19 - ruby3.2
20 - ruby2.7
21 - rust (provided.al2)
Runtime: 17
What package type would you like to use?
1 - Zip
2 - Image
Package type: 2
Based on your selections, the only dependency manager available is pip.
We will proceed copying the template using pip.
Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]: n
Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: n
-----------------------
Generating application:
-----------------------
Name: sam-python-selenium4
Base Image: amazon/python3.11-base
Architectures: x86_64
Dependency Manager: pip
Output Directory: .
Configuration file: sam-python-selenium4/samconfig.toml
Next steps can be found in the README file at sam-python-selenium4/README.md
Commands you can use next
=========================
[*] Create pipeline: cd sam-python-selenium4 && sam pipeline init --bootstrap
[*] Validate SAM template: cd sam-python-selenium4 && sam validate
[*] Test Function in the Cloud: cd sam-python-selenium4 && sam sync --stack-name {stack-name} --watch
ランタイムは現時点ではpythonが3.11までしか選択できないので、3.11を、パッケージタイプはimageにします。
ファイル構造
❯ tree
.
├── README.md
├── __init__.py
├── events
│ └── event.json
├── hello_world
│ ├── Dockerfile
│ ├── __init__.py
│ ├── app.py
│ └── requirements.txt
├── samconfig.toml
├── template.yaml
└── tests
├── __init__.py
└── unit
├── __init__.py
└── test_handler.py
雛形を変更していきます。
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
python3.12
Sample SAM Template for sam-python-selenium4
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
Timeout: 60
MemorySize: 1024
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
PackageType: Image
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /hello
Method: get
Metadata:
Dockerfile: Dockerfile
DockerContext: ./hello_world
DockerTag: python3.11-v1
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
GlobalsでMemorySizeとTimeoutを変更しています。
requirements.txt
hello_world > requirements.txtを編集します。
requests
boto3>=1.34.11
urllib3<2
selenium==4.21.0
Dockerfileでインストールするpythonのモジュールです。seleniumは4.21.0をインストールしました。
Dockerfile
hello_world > Dockerfileを編集します。
FROM public.ecr.aws/lambda/python:3.12-x86_64
COPY app.py requirements.txt ./
RUN python3.12 -m pip install -r requirements.txt -t .
# selenium-managerを使ってChromeとChromeDriverをダウンロードする。
RUN /var/task/selenium/webdriver/common/linux/selenium-manager --browser chrome --cache-path /opt
# Chromeの依存関係をインストールする。
# 参考: https://qiita.com/hideki/items/d1ff83e7e82afc0c0502
RUN dnf install -y atk cups-libs gtk3 libXcomposite alsa-lib \
libXcursor libXdamage libXext libXi libXrandr libXScrnSaver \
libXtst pango at-spi2-atk libXt xorg-x11-server-Xvfb \
xorg-x11-xauth dbus-glib dbus-glib-devel nss mesa-libgbm \
libgbm libxkbcommon libdrm
# Command can be overwritten by providing a different command in the template directly.
CMD ["app.lambda_handler"]
ポイントとなるところに、sam init
でDockerfileに出力されるAWS Lambdaのイメージ「public.ecr.aws/lambda/python:3.12
」ではなく、x86-64用のAWS Lambdaイメージ「public.ecr.aws/lambda/python:3.12-x86_64
」に変更しています。後ほど理由を後述します。
requirements.txtのseleniumをインストール後、同梱しているselenium-managerを使い、chromeとchromeDriverをコンテナ内部の/optフォルダにダウンロードします。
このほかChromeの実行に必要な依存関係をインストールします。
app.py
hello_world > pythonの実行ファイルを編集します。
import json
from tempfile import mkdtemp
import glob
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
class ChromeInstance:
def __init__(self):
self.options = webdriver.ChromeOptions()
self.options.add_argument('--headless=new')
self.options.add_argument('--disable-gpu')
self.options.add_argument('--disable-dev-shm-usage')
self.options.add_argument('--disable-dev-tools')
self.options.add_argument('--no-zygote')
self.options.add_argument('--window-size=1280x1696')
self.options.add_argument(f"--user-data-dir={mkdtemp()}")
self.options.add_argument(f"--data-path={mkdtemp()}")
self.options.add_argument(f"--disk-cache-dir={mkdtemp()}")
self.options.add_argument('--no-sandbox')
self.options.add_argument('--hide-scrollbars')
self.options.add_argument('--enable-logging')
self.options.add_argument('--log-level=0')
self.options.add_argument('--v=99')
self.options.add_argument('--single-process')
self.options.binary_location = glob.glob("/opt/chrome/linux64/*/chrome")[0]
service = ChromeService(glob.glob("/opt/chromedriver/linux64/*/chromedriver")[0])
self.driver = webdriver.Chrome(service=service, options=self.options)
def lambda_handler(event, context):
# call chrome_headless instance.
chrome = ChromeInstance()
try:
if chrome.driver:
# Googleにアクセスして検索
chrome.driver.get("https://www.google.com")
search_box = chrome.driver.find_element(By.NAME, "q")
search_box.send_keys('Selenium')
search_box.submit()
title = chrome.driver.title
print(title)
return {
"statusCode": 200,
"body": json.dumps({
"message": "title: " + title
}),
}
except Exception as e:
print(f"処理中にエラーが発生しました: {e}")
finally:
if chrome.driver:
chrome.driver.quit()
globを使って/linux64
以下にある/opt
フォルダ内へインストールされた、chromeとchromeDriverを検索して、Chromeを起動します。
/*
の部分はselenium-managerが選定するバージョンによって変わります。
その後はドライバーをインスタンスとして呼び出して、googleを開いて「selenim」と検索し
、検索結果のタイトルを出力します。
アプリケーション構築
AWS SAM CLIでアプリケーションを構築します。
プロジェクトディレクトリのtemplate.yamlのある階層で次を実行します。
sam build --use-container
Dockerコンテナを利用して依存関係をインストールするので、--use-container
をつけています。
❯ sam build --use-container
Starting Build inside a container
# 省略
Successfully built 4fb36f1704ed
Successfully tagged helloworldfunction:python3.12-v1
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
ダウンロードなどもあるので、構築にしばらく時間がかかります。プロジェクトディレクトリに.aws-samディレクトリが出力されました。
ローカルテスト
イベントを必要しないので、プロジェクトディレクトリで次のコマンドで実行してテストします。
sam local invoke
実行結果
❯ sam local invoke
Invoking Container created from helloworldfunction:python3.12-v1
Building image.................
Using local image: helloworldfunction:rapid-x86_64.
START RequestId: f23f3ebc-720f-4762-9306-aa689cb2ceab Version: $LATEST
Selenium - Google 検索
END RequestId: f23f3ebc-720f-4762-9306-aa689cb2ceab
REPORT RequestId: f23f3ebc-720f-4762-9306-aa689cb2ceab Init Duration: 1.13 ms Duration: 6459.17 ms Billed Duration: 6460 ms Memory Size: 1024 MB Max Memory Used: 1024 MB
{"statusCode": 200, "body": "{\"message\": \"title: Selenium - Google \\u691c\\u7d22\"}"}
検索結果のタイトル、Selenium - Google 検索
と無事出力されました。
アプリケーションをAWSにデプロイ
プロジェクトディレクトリでSAM CLIを使い、次のコマンドを実行してデプロイします。
sam deploy --guided
対話形式でデプロイ設定を進めますが、コマンドの詳細はSAM CLIのチュートリアルにも同様の手順がありますのでそちらをご覧ください。
❯ sam deploy --guided
省略
Successfully created/updated stack - python-selenium4 in ap-northeast-1
デプロイ完了まで待ちます。
AWS コンソールで実行
コンソール実行の手順は省略いたします。
無事テスト成功です!!
手元のマシン(M2 Macbookpro)で動かなかった理由とその解決方法
AWS SAMのpython用コンテナベースイメージはLinux, x86-64, ARM 64の3種類のCPUアーキテクチャが用意されています。
標準でインストールされるイメージはLinuxとなっており、ビルド時に出力された詳細だとlinux/amd64と出力されます。
Apple Silicon(ARM64)用のdocker-desktopでは、amd64もx86-64も両方サポートしているものの、エミュレート結果に若干の違いがあるようです。
ベースイメージがLinuxでもx86-64でもAWSのECRにアップロードしてしまえば、スクレイピング用のChromeは動作しますが、AWS SAMのようにローカルにDocker imageを作成して、Apple Silicon製Macの環境で立ち上げたコンテナでは、アーキテクチャがAmd64の場合にchromeがクラッシュしてしまうようです。
x86-64に変更したところ、すんなりとテストができるようになったので、もしApple Silicon Macでdockerを使ったサンプルがうまく動かない場合は、Dockerのコンテナに使われているアーキテクチャがなにかもチェックしたほうがよさそうです。
ちなみにIntelベースのmacbookproを引っ張り出して試したところ、Amd64でもローカルで実行できました。
またこのタイミングでdocker-desktopも最新版にしました。Rosettaも念の為にインストールしています。
SAMにこだわった理由
AWS SAMはDockerを使ったfunctionのローカルテストがとても簡単にできます。
serverless flameworkは一旦imageを作成して、そこからdocker runで起動し、別のターミナルでコマンド送信して試す方法になります。cdkでの実装もこのテストパターンのようです。
立ち上げてテストして、落としてといった工程になってしまうので、Lambdaに関しては、AWS SAMのほうがテストは楽だと思いました。(プラグインなどを使えば楽にできるようですが)
最後に
思わずアーキテクチャの勉強にもなってしまいましたが、ようやく心に引っかかっていたselenium4をサーバレスで動かせました。
サーバレスでスクレイピングをしたい方の一助になれば幸いです。
参考
この記事のお陰で、どうやってselenium-managerを利用して、コンテナに収めるのかがつかめました。