TL;DR (5行)
- 頻繁に手動で更新するようなWebページ(今回は例としてZOZOTOWNを使います)を自動チェックしたい
- Fargateを使ってSeleniumの実行環境を作ります
- ローカルのコンテナをECRにpushしてFargateにデプロイします
- Seleniumによるブラウザ操作をスケジュール設定し,バッチ処理として動作させます
- バッチ処理の結果は,CloudWatchにてチェック
注意
本記事はFargate+Selenium導入紹介を目的としています.
筆者は内定者で許可もとっているので,題材として自社サービスのZOZOTOWNを使ってます!
もし,記事内容を流用する場合はマナーや規約に違反しない範囲でお願いします!
使用するサービスの説明
Fargateとは
通常コンテナをEC2で運用する場合,インスタンス管理する必要がありますが,Fargateの場合,インスタンス管理はAmazon側に任せ,コンテナを登録するだけでサーバレスにコンテナを動作させることができるサービスです.
サーバレスで有名なサービスとしてLambdaがありますが,こちらはコンテナを使えなかったり,タイムアウトなどの制約から柔軟性に欠ける部分があります.
一方で,Fargateはローカルで実行しているコンテナをそのまま登録して使えるため,多様なサービスの提供が可能です.
Seleniumとは
Webアプリのテストを自動化するためのブラウザ駆動型テストツールです.
PythonやRuby,Javaなど多様な言語に対応しており,簡単にテストスクリプトを作成することができます.
アーキテクチャ
今回は,AWS上に以下のようなアーキテクチャを構築します.
Dockerfileとメインコードの作成
Selenium + Pythonでテストスクリプトを作成します.
Dockerfileの作成
FROM joyzoursky/python-chromedriver:3.8-alpine3.10-selenium
WORKDIR /usr/src
ADD main.py /usr/src
CMD ["python", "main.py"]
今回,Selenium+Headless Chromeを使うにあたって,
ベースイメージはこちらを使っています.
joyzoursky/python-chromedriver:3.7-alpine3.8-selenium
https://hub.docker.com/r/joyzoursky/python-chromedriver/
メインコードの作成
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.common.exceptions import TimeoutException, ElementClickInterceptedException, NoSuchElementException
def check_coupon(driver, my_favorite_brand):
# ZOZOのクーポンページに遷移
driver.get("https://zozo.jp/coupon/")
i = 1
while True:
try:
coupon_brand = driver.find_element_by_xpath(f'//*[@id="body"]/div[3]/ul/li[{i}]/a/figure/div[2]').text
if coupon_brand == my_favorite_brand:
return True
i += 1
except NoSuchElementException:
return False
if __name__ == '__main__':
try:
# Headless Chromeの設定
options = webdriver.ChromeOptions()
options.add_argument('--no-sandbox')
options.add_argument("--disable-setuid-sandbox")
options.add_argument('--window-size=1420,1080')
options.add_argument('--headless')
options.add_argument('--disable-gpu')
# Headless Chromeブラウザに接続
driver = webdriver.Chrome(options=options)
# seleniumの動作タイムアウトを15秒間に設定
driver.implicitly_wait(15)
# 好きなブランド
my_favorite_brand = "Carlie e felice"
# クーポンのチェック
if check_coupon(driver, my_favorite_brand):
print("見つけたよ! ", my_favorite_brand)
else:
print("今日は見つけられなかった・・・")
# 例外処理
except ElementClickInterceptedException as ecie:
print(f"exception!\n{ecie}")
except TimeoutException as te:
print(f"timeout!\n{te}")
finally:
# 終了
driver.close()
driver.quit()
クーポンページにブランド:Carlie e feliceがあるかどうかチェックします.
Requests + Beautiful Soup 4でスクレピングしても良かったのですが,今回はSeleniumを使った環境を構築したかったので,まさかり投げないでください;;
ローカル環境でコンテナの実行
# コンテナの構築
$ docker build -t zozo_check_coupons .
# コンテナの実行
$ docker run -it --rm zozo_check_coupons
見つけたよ! Carlie e felice
無事,ローカル環境で実行できたことを確認したら,次はこのコンテナをAmazon ECRにプッシュします.
ECRはAWS上のプライベートなDocker Hubみたいなイメージです.
AWSの必要環境の構築
ECRの環境を構築
ECRのリポジトリ作成
ECRに今回管理したいコンテナ専用のリポジトリを作成します.
このときの,リポジトリのURIはコンテナをプッシュするときに,使うのでメモしておきましょう.
ECRにログイン
$ aws ecr get-login --region ap-northeast-1 --no-include-email
docker login -u AWS -p ...
.
.
. .dkr.ecr.ap-northeast-1.amazonaws.com
# 返ってきたdocker login ~をコピペして打ち込む
$ docker login -u AWS -p ...
Login Succeeded
Login Succeededと表示されたらOKです
ECRにプッシュ
先程,メモしたリポジトリのURLをコピペし,作成したリポジトリにプッシュします
# リポジトリのURIでタグを付ける
$ docker build -t xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/zozo_check_coupons .
# タグをつけたコンテナをECRにプッシュする
$ docker push xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/zozo_check_coupons
イメージのURIは,タスク定義で使うので,メモしておきましょう
ECSでクラスター作成
コンテナを動作させる環境であるクラスターを作ります
- クラスター名を入力し,VPCの作成にチェックします.
ECSでタスクの定義
次に,タスクの定義をします.
- 以下のようにタスクを定義します
タスク実行ロールが何もない場合は,下の余談を参照し,作成してください
- コンテナ追加を選択し,コンテナ名と先程プッシュしたコンテナイメージのURIをここでコピペします
- メモリとCPUを設定
余談 : CLIでタスク定義
- タスク実行に必要なロールを定義
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
- 定義ファイルを用いて,ロールの作成
$ aws iam --region ap-northeast-1 create-role --role-name ecsTaskExecutionRole --assume-role-policy-document file://task-execution-assume-role.json
- タスクの定義ファイルを作成
{
"family": "zozo-check-coupons-task",
"networkMode": "awsvpc",
"containerDefinitions": [
{
"name": "zozo-check-coupons-task",
"image": "xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/zozo_check_coupons:latest",
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-region": "ap-northeast-1",
"awslogs-group": "/ecs/zozo_check_coupons-task",
"awslogs-stream-prefix": "ecs"
}
}
}
],
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::xxxxxxxx:role/ecsTaskExecutionRole"
}
- 定義ファイルをもとにタスクを作成
$ aws ecs register-task-definition --cli-input-json file://task-config.json
これで,打ち間違い,漏れなくタスクを定義することができます
詳しくは,こちらを参照ください
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-cli-tutorial-fargate.html
スケジュール実行の作成
次に,いよいよ定義したタスクをスケジュールで実行していきます.
- 作成したクラスターを選択し,タスクのスケジューリングを選択,そして作成を押します
- 以下のように設定しました.固定時間は,クーポン更新が24時間だったので24に設定しました
CloudWatchでログをチェック
タスクが完了すると,CloudWatchの方にログが送られます
確認すると,以下のようなログがありました!
今日は無いみたいですね...
おわりに
Fargate + Seleniumの環境を作りました!
Fargateはコンテナを使って動かしたコンテナをそのまま登録できるので,かなり柔軟性はいいです.
ですが,クローリングをする場合,CPUやメモリにページの読み込みが遅くなり,プログラムによるブラウザ操作がうまく行かなかったり,タイムアウトが発生してしまう場合があるので,sleepを入れる等の対策は十分にしたほうが良さそうです.
最後に,本記事は導入目的で紹介しているので,流用する場合はマナーや規約をよく読んでからお願いします!
参考文献