ScrapyとAWSを用いたサーバーレススクレイピング

このチュートリアルで、Scrapyスパイダーの作成方法を学び、AWS Lambdaにデプロイし、スクレイピングしたデータをS3に保存しましょう。
2 分読
Serverless Scraping with Scrapy and AWS blog image

本日のガイドでは、Scrapy Spiderを作成し AWS Lambdaにデプロイします。コード自体は非常にシンプルです。Lambdaのようなクラウドサービスでは多くの可変要素が存在します。これらの要素を管理する方法と、問題発生時の対処法を解説します。

前提条件

このタスクを実行するには、以下のものが必要です:

  • here
  • Scrapy を使ったスクレイピングの基本知識

サーバーレスとは?

サーバーレスアーキテクチャはコンピューティングの未来として称賛されています。サーバーレスアプリの実際の稼働時間は時間当たりでより高額になる可能性がありますが、既にサーバーの稼働費用を支払っていない場合、Lambdaは理にかなっています。

仮にスクレイパーの実行に1分かかり、1日1回実行するとします。従来のサーバーでは、24時間稼働の1か月分の料金を支払うことになりますが、実際の使用時間はわずか30分です。Lambdaのようなサービスでは、実際に使用した分だけ支払うことになります。

メリット

  • 課金: 実際に使用した分だけ支払う。
  • スケーラビリティ:Lambdaは自動でスケールするため、管理不要。
  • サーバー管理:サーバー管理に時間を費やす必要はありません。すべてが自動化されています。

デメリット

  • レイテンシー: 関数がアイドル状態の場合、起動と実行に時間がかかります。
  • 実行時間: Lambda関数のデフォルトタイムアウトは3秒、最大実行時間は15分です。従来のサーバーの方がはるかに柔軟です。
  • 移植性: OS互換性に依存するだけでなく、ベンダーの裁量に委ねられます。Lambda関数を単純にコピーしてAzureやGoogle Cloudで実行することはできません。

Bright Dataには、こうした制限のないソリューションがあります。次にそれを探ってみましょう。

サーバーレス関数:最良の代替手段

The flow of Bright Data's Serverless Functions

AWS LambdaやScrapyもサーバーレスウェブスクレイピングを提供しますが、Bright Dataのサーバーレス関数は、より高速で信頼性の高いウェブスクレイピングに特化したソリューションです。70以上の事前構築済みJavaScriptテンプレート、統合型クラウドベースIDE、AI搭載のブロック解除ソリューションにより、CAPTCHAの回避、容易なスケーリングが可能となり、インフラ管理の手間なくデータ抽出に集中できます。

AWSやScrapyのアプローチとは異なり、Bright Dataのソリューションにはプロキシ管理、自動スケーリング、S3やGoogle Cloudなどのストレージプラットフォームとの直接連携が含まれます。1,000ページロードあたりわずか2.7ドルから利用可能なServerless Functionsは、高度なウェブスクレイピングをよりシンプルに、より高速に、よりコスト効率良く実現します。

それでは、ScrapyとAWSのガイドを続行しましょう。

はじめに

サービスの設定

AWSアカウントを取得したら、S3バケットが必要です。「すべてのサービス」ページに移動し、下にスクロールします。

A list of all AWS services

やがて「ストレージ」セクションが表示されます。このセクションの最初のオプションが「S3」です。クリックしてください。

The S3 storage service

次に、「バケットの作成」ボタンをクリックします。

Creating a new S3 bucket

ここでバケット名と設定を選択します。ここではデフォルト設定のまま進めます。

Naming the new S3 bucket and choosing your settings

設定が完了したら、ページ右下の「バケットの作成」ボタンをクリックします。

Creating the new S3 bucket

作成が完了すると、バケットはAmazon S3の「バケット」タブに表示されます。

Clicking the create bucket button when the configuration is done

プロジェクトの設定

新しいプロジェクトフォルダを作成します。

mkdir scrapy_aws

新しいフォルダに移動し、仮想環境を作成します。

cd scrapy_aws
python3 -m venv venv  

環境を有効化します。

source venv/bin/activate

Scrapyをインストールします。

pip install scrapy

スクレイピング対象

動的ウェブサイト、ボット対策、大規模スクレイピングには、Bright Dataのスクレイピングブラウザをご利用ください。タスクの自動化、CAPTCHAの回避、シームレスな拡張が可能です。

対象サイトとしてbooks.toscrapeを使用します。これはウェブスクレイピングに特化した教育サイトです。下の画像を見ると、各書籍はclass名product_podを持つ記事です。ページからこれらの要素をすべて抽出します。

Inspecting one of the book in an article tag

各書籍のタイトルは、h3要素内にネストされたa要素内に埋め込まれています。

Inspecting the book title

各価格はdiv内にネストされたp内に埋め込まれています。クラス名はprice_colorです

Inspecting the book price

コードの記述

次に、スクレイパーを記述しローカルでテストします。新しいPythonファイルを開き、以下のコードを貼り付けます。当方のファイル名はaws_spider.pyとします

import scrapy


class BookSpider(scrapy.Spider):
    name = "books"
    allowed_domains = ["books.toscrape.com"]
    start_urls = ["https://books.toscrape.com"]

    def parse(self, response):
        for card in response.css("article"):
            yield {
                "title": card.css("h3 > a::text").get(),
                "price": card.css("div > p::text").get(),
            }
        next_page = response.css("li.next > a::attr(href)").get()

        if next_page:
            yield scrapy.Request(response.urljoin(next_page))

以下のコマンドでスパイダーをテストできます。書籍と価格が記載されたJSONファイルが出力されるはずです。

 python -m scrapy runspider aws_spider.py -o books.json

次に、ハンドラーが必要です。ハンドラーの役割は単純で、スパイダーを実行することです。ここでは基本的に同じ機能を持つ2つのハンドラーを作成します。主な違いは、1つをローカルで実行し、もう1つをLambda上で実行する点です。

ローカルハンドラーはlambda_function_local.py と名付けました。

import subprocess

def handler(event, context):
    # ローカルテスト用出力ファイルパス
    output_file = "books.json"

    # Scrapyスパイダーを-oフラグ付きで実行し、出力をbooks.jsonに保存
    subprocess.run(["python", "-m", "scrapy", "runspider", "aws_spider.py", "-o", output_file])

    # 成功メッセージを返す
    return {
        'statusCode': '200',
        'body': f"スクレイピング完了!出力は {output_file} に保存されました",
    }

# ローカルテスト用にこのブロックを追加
if __name__ == "__main__":
    # AWS Lambda呼び出しイベントとコンテキストをシミュレート
    fake_event = {}
    fake_context = {}

    # ハンドラーを呼び出し結果を出力
    result = handler(fake_event, fake_context)
    print(result)

books.jsonを削除してください。以下のコマンドでローカルハンドラーをテストできます。正常に動作している場合、プロジェクトフォルダ内に新しいbooks.jsonが生成されます。bucket_nameを自身のバケット名に変更することを忘れないでください。

python lambda_function_local.py

次に、Lambdaで使用するハンドラーです。ほぼ同様ですが、データをS3バケットに保存するための微調整が加えられています。

import subprocess
import boto3

def handler(event, context):
    # ローカル出力ファイルとS3出力ファイルのパスを定義
    local_output_file = "/tmp/books.json"  # Lambdaでは/tmp必須
    bucket_name = "aws-scrapy-bucket"
    s3_key = "scrapy-output/books.json"  # S3バケット内のパス

    # Scrapyスパイダーを実行し、出力をローカルに保存
    subprocess.run(["python3", "-m", "scrapy", "runspider", "aws_spider.py", "-o", local_output_file])

    # ファイルをS3にアップロード
    s3 = boto3.client("s3")
    s3.upload_file(local_output_file, bucket_name, s3_key)

    return {
        'statusCode': 200,
        'body': f"スクレイピング完了!出力は s3://{bucket_name}/{s3_key} にアップロードされました"
    }
  • まずデータを一時ファイルに保存します:local_output_file = "/tmp/books.json"。これによりデータが失われるのを防ぎます。
  • s3.upload_file(local_output_file, bucket_name, s3_key) でバケットにアップロードします。

AWS Lambdaへのデプロイ

次に、AWS Lambdaにデプロイする必要があります。

パッケージフォルダを作成します。

mkdir package

依存関係をパッケージフォルダにコピーします。

cp -r venv/lib/python3.*/site-packages/* package/

ファイルをコピーします。以前にテストしたローカルハンドラーではなく、Lambda用に作成したハンドラーを必ずコピーしてください。

cp lambda_function.py aws_spider.py package/

packageフォルダをzipファイルに圧縮します。

zip -r lambda_function.zip package/

ZIPファイルを作成したら、AWS Lambdaに移動し「関数を作成」を選択しますプロンプトが表示されたら、ランタイム(Python)やアーキテクチャなどの基本情報を入力します

S3バケットへのアクセス権限を付与することを必ず確認してください。

Creating a new function

関数作成後、[ソース]タブ右上のドロップダウンから[アップロード]を選択します。

Lambda upload from

.zipファイルを選択し、作成したZIPファイルをアップロードします。

Uploading the created ZIP file

テストボタンをクリックし、関数の実行を待ちます。実行後、S3バケットを確認すると、新しいファイル「books.json」が作成されているはずです。

The new books.json file in your S3 bucket

トラブルシューティングのヒント

Scrapyが見つからない

Scrapyが見つからないというエラーが発生する場合があります。その場合は、subprocess.run()内のコマンド配列に以下を追加する必要があります。

Adding a piece of code in the subprocess.run() function

一般的な依存関係の問題

Pythonのバージョンが一致していることを確認してください。ローカルにインストールされているPythonを確認します。

python --version

このコマンドの出力が Lambda 関数と異なる場合は、Lambda 設定を変更して一致させてください。

ハンドラーの問題

The handler should match the function you wrote

ハンドラーはlambda_function.py で記述した関数と一致させる必要があります。上記のようにlambda_function.handler という形式です。lambda_function は Python ファイル名を、handler は関数名をそれぞれ表します。

S3への書き込み不可

出力の保存時に権限の問題が発生する可能性があります。その場合、Lambdaインスタンスに以下の権限を追加する必要があります。

IAMコンソールに移動し、対象のLambda関数を検索します。該当関数をクリックし、「権限の追加」ドロップダウンを選択します。

[ポリシーの添付]をクリックします。

Clicking on 'attach policies' in the Lambda function

AmazonS3FullAccessを選択します。

Selecting AmazonS3FullAccess

まとめ

完了です!これで、AWSコンソールというUIの悪夢の中でも、自力で対処できるはずです。Scrapyを使ったクローラーの作成方法、Amazon Linuxとのバイナリ互換性を確保するためのLinuxまたはWSL環境のパッケージ化方法も理解できました。

ウェブスクレイピングに興味がない場合は、当社のウェブスクレイパー APIと既製のデータセットをご覧ください。今すぐ登録して無料トライアルを開始しましょう!