AWS上でWebスクレイピング環境を構築

本記事は、たくさんの先輩方の知見に基づいて作成できています。

やりたいこと

  • 定期的に特定のサイトから情報取得する

  • 取得した情報をAWS上に保存する

  • 保存した情報を外部から参照できるようにする

手順(環境構築)

  1. AWS Management Console にログイン

  2. EC2 pyenv, selenium インストール
    https://note.com/yuu________/n/nbe90710b842b

  3. EC2 chrome インストール
    https://note.com/yuu________/n/n75d185d30e26

  4. EventBridge でEC2を起動、実行、終了を設定
    ・EC2の起動・終了は、ロールくらいなので割愛。

  5. EventBridge でEC2実行 の注意点
    ・「Systems Manager Run Commandでシェルスクリプト実行」以降の
     Rulesを作成して実行する部分を参考にする。
    https://www.d-make.co.jp/blog/2021/11/20/ec2-cron-to-eventbridge-rule/
    python実行する際に注意点は、Pathが有効になっていないこと。コンソールログインの状態と、Rulesから実行される状態は異なる。
    パス設定をスクリプト内に含める必要あります。

  6. Run-Commandの権限
    権限でちょっとはまったのでメモ
    https://qiita.com/haraitai00/items/1ff4da073dfdde214f0b

ソースコード

Rulesから実行するスクリプト

#!/bin/bash

export HOME=/home/xxxxxxx
if [ -f "$HOME/.bash_profile" ]; then
    source "$HOME/.bash_profile"
fi


DATE=`date`

# コピー先のS3バケット
S3_BUCKET=s3://xxxxxxxx/yyyyyyyy/


#---------------------------
# Pythonスクリプトを実行
#---------------------------
echo "[$DATE]grubs data..." >auto-exe.log
#echo "[$DATE]$PATH" >>auto-exe.log
pyenv global 3.12.3
python gtg.py
pyenv global system


#--------------------------
# CSVファイルをS3へ
#--------------------------
CSV_FILES=$(ls *.csv)

# ファイルが存在する場合のみ処理を行う
if [ -n "$CSV_FILES" ]; then
    for FILE in $CSV_FILES; do
        # CSVファイルをS3にコピー
        aws s3 cp "$FILE" "${S3_BUCKET}$FILE"

        # コピーが成功したかどうかをチェック
        if [ $? -eq 0 ]; then
            # 成功した場合、CSVファイルを削除
            rm "$FILE"
            echo "[$DATE]$FILE をS3にコピーし、ローカルから削除しました。" >>auto-exe.log
        else
            # 失敗した場合、エラーメッセージを表示
            echo "[$DATE]$FILE のS3へのコピーに失敗しました。" >>auto-exe.log
        fi
    done
else
    echo "[$DATE]CSVファイルが見つかりませんでした。" >>auto-exe.log
fi

Lambda 関数(レイヤーにpandas、boto3を追加)

import json
import boto3
import pandas as pd
from io import StringIO

s3_client = boto3.client('s3')

def lambda_handler(event, context):
    bucket_name = 'xxxxxxxx'  # S3 バケット名を指定
    prefix = 'yyyyyyyyy/'  # CSV ファイルが置いてあるプレフィックスを指定

    # S3 から CSV ファイルの一覧を取得
    response = s3_client.list_objects_v2(Bucket=bucket_name, Prefix=prefix)
    files = [item['Key'] for item in response.get('Contents', []) if item['Key'].endswith('.csv')]

    data_frames = []

    # 各 CSV ファイルを読み込み、データフレームに追加
    for file_key in files:
        csv_obj = s3_client.get_object(Bucket=bucket_name, Key=file_key)
        body = csv_obj['Body'].read().decode('utf-8')
        data = StringIO(body)
        df = pd.read_csv(data)
        data_frames.append(df)

    # データフレームを結合
    if data_frames:
        combined_df = pd.concat(data_frames, ignore_index=True)
    else:
        combined_df = pd.DataFrame()  # 空のデータフレーム

    # JSON 応答の作成
    json_result = combined_df.to_json(orient='records')

    return {
        'statusCode': 200,
        'body': json_result,
        'headers': {
            'Content-Type': 'application/json'
        }
    }

使用したサービス

  • EC2:Python seleniumを実行し、csv形式のデータにする。

  • S3:csvファイル置き場

  • Lambda:API Gateway: 外部から保存した情報を参照(json応答)

  • EventBridge:Scheduler: EC2の起動・終了/ Rules: スクリプト実行

  • IAM:ロール管理

困ったこと

  • Lambdaのレイヤー作成
    スクレイピングをLambda関数で行おうとしたが、EC2へ

  • IAM設定
    AWSをはじめて触ったこともあり、権限の与え方が最初は困惑

  • Rules経由で実行したときだけ動かなかった
    権限不足など重なったこともありパスが効いていないことに気付くのが遅かった

雑感

 はじめは、Lambda関数、S3だけで構築しようとしましたがLambda関数のレイヤー作成やバージョン互換が非常に面倒だと感じたので、EC2上に構築することにしました。Lambda関数が複雑だとレイヤーの保守やバージョン互換性、メモリサイズなど気にすることが多いです。疎結合で設計上は美しいかもしれませんが無駄に複雑になると思いました。
 この定期的に実行する環境は、スクリプト内に処理を追加すれば拡張できて、データ置き場をS3からDynamoDBにすることも容易です。AWSって便利ですね。


この記事が気に入ったらサポートをしてみませんか?