EventBridgeScheduler, ECS, Docker, Seleniumを使い1時間ごとに阿部寛のHPのスクリーンショットを撮影しS3に保存する方法についてまとめました。
スクレイピング用のDockerイメージを作成
以下の記事を参考にしながらスクレイピング用のDockerイメージを作成します。
環境変数からスクレイピング先のURLと保存先のバケットを読み取ります。
参考: Selenium×dockerでテスト自動化してみた
.
├── Dockerfile
└── app
└── main.py
FROM --platform=linux/x86_64 python:3.12.1-alpine3.19
ENV PYTHONIOENCODING utf-8
WORKDIR /app
RUN apk add --update \
wget \
# Add chromium and dependences
udev \
ttf-freefont \
chromium \
chromium-chromedriver \
# Add Japanese font
&& mkdir noto \
&& wget -P /app/noto https://noto-website.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip \
&& unzip /app/noto/NotoSansCJKjp-hinted.zip -d /app/noto \
&& mkdir -p /usr/share/fonts/noto \
&& cp /app/noto/*.otf /usr/share/fonts/noto \
&& chmod 644 -R /usr/share/fonts/noto/ \
&& fc-cache -fv \
&& rm -rf /app/noto \
# Add selenium
&& pip install selenium \
&& pip install boto3
COPY ./app .
CMD [ "python", "main.py" ]
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import os
import shutil
import configparser
import datetime
import boto3
def save_screenshot(target_url: str, bucket_name: str):
folder = '/opt/app'
service = Service(executable_path=r'/usr/bin/chromedriver')
options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--disable-gpu')
options.add_argument('--window-size=1200x1800')
prefs = {"download.default_directory" : folder}
options.add_experimental_option("prefs",prefs)
driver = webdriver.Chrome(service=service, options=options)
driver.get(target_url)
file_name = str(datetime.datetime.now()) + '.png'
driver.save_screenshot(file_name)
driver.quit()
client = boto3.client('s3',region_name='ap-northeast-1')
client.upload_file(file_name, bucket_name, file_name)
if __name__ == '__main__':
target_url = os.environ['TARGET_URL']
bucket_name = os.environ['BUCKET_NAME']
save_screenshot(target_url, bucket_name)
ECRの作成、イメージのプッシュ
スクレイピング用のDockerイメージ保存するためのECRのプライベートリポジトリを作成します。
Dockerイメージをビルドし、ECRリポジトリにプッシュします。
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin {YOUR_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -t web-crawler .
docker tag web-crawler:latest 397693451628.dkr.ecr.ap-northeast-1.amazonaws.com/web-crawler:latest
docker push {YOUR_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/web-crawler:latest
ECS,IAMロールの作成
スクレイピング用のDockerイメージを動かすためのECSクラスター、タスク定義、IAMロールを作成します。
ECSクラスターの作成
ECSクラスターを作成します。
タスクIAMロールの作成
スクレイピングするタスクを動かすためのIAMロール(タスクロール)を作成します。
スクリーンショットをS3に保存する必要があるのでS3へのファイルアップロードを許可する権限を付与します。(画像中ではFullAccess権限を与えていますが、サンドボックス環境以外では適切な権限を設定してください)
作成した web-crawer-task-role
をタスク定義設定時に使用します。
タスク定義の作成
スクレイピング用のタスク定義を作成します。
タスクロールの項に先ほど作成した web-crawer-task-role
を設定し、コンテナの詳細欄にはECRのURIを設定します。
料金節約のためCPUとメモリサイズは調整しましたが他はデフォルトの設定を使用します。
S3バケットの作成
スクリーンショットを保存するための S3バケットを作成します。
takoikatakotako-web-crawer-bucket
という名前で作成しました。
EventBridgeSchedulerの作成
定期実行のためのEventBridgeSchedulerを作成します。
EventBridgeSchedulerを用いて定期的なタスクを実行し、オーバーライドオプションで環境変数の上書きを行います。
{
"containerOverrides":[
{
"name":"ojichat",
"environment":[
{
"name":"TARGET_URL",
"value":"http://abehiroshi.la.coocan.jp/"
},
{
"name":"BUCKET_NAME",
"value":"takoikatakotako-web-crawer-bucket"
}
]
}
]
}
阿部寛のHPのスクリーンショットが保存されることを確認します。
詰まったところ
ECSタスクを変更する場合、新しいロールを作成する必要がある
EventBridgeSchedulerで生成されるIAMロールのポリシーにECSタスクのARNが含まれるため、コンソールからECSタスクを更新すると新しいロールを作成する必要があります。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecs:RunTask"
],
"Resource": [
"arn:aws:ecs:ap-northeast-1:397693451628:task-definition/web-crawer-task-definition:*",
"arn:aws:ecs:ap-northeast-1:397693451628:task-definition/web-crawer-task-definition"
],
"Condition": {
"ArnLike": {
"ecs:cluster": "arn:aws:ecs:ap-northeast-1:397693451628:cluster/web-crawler-cluster"
}
}
}
]
...
}
デバッグにCloudTrailが必要な場合がある
上手く動かない場合、CloudTrailを使用してデバッグする必要があります。
CloudTrail イベント履歴から RunTask などを確認する必要があります。