スキップしてメイン コンテンツに移動

Scrapy非同期クローラー

Scrapy非同期クローラー

はじめに

近年、Web上に溢れる情報を効率的に取得し、ビジネスや研究に活用するニーズが急増しています。Python自動化の一環として、Webクローラーを構築する際に欠かせないのがScrapyです。Scrapyは、クローリング、データ抽出、パイプライン処理を統合したフレームワークであり、非同期I/Oを活用して高速に大量のページを取得できます。この記事では、Scrapyを使った大規模スクレイピングの設計から実装、運用までを実践的に解説します。

基礎知識・概念

まずはScrapyの主要コンポーネントを整理します。
スパイダー(Spider)はURLを巡回し、レスポンスを受け取るクラスです。
アイテム(Item)は抽出したデータを格納する構造体で、辞書型に似ています。
パイプライン(Pipeline)はアイテムを順次処理し、データベースへの保存やファイル出力を行います。
ScrapyはTwistedという非同期フレームワーク上に構築されており、CONCURRENT_REQUESTSDOWNLOAD_DELAYなどの設定で並列度を調整できます。これにより、サーバーへの負荷を抑えつつ高速にデータ収集が可能です。

実装・設定の詳細

以下では、Scrapyプロジェクトを作成し、基本的なクローラーを構築する手順を示します。

1. プロジェクト作成

pip install scrapy
scrapy startproject example_scraper
cd example_scraper

2. スパイダーの作成

scrapy genspider example example.com

生成されたspiders/example.pyを編集し、以下のようにします。

import scrapy

class ExampleSpider(scrapy.Spider):
    name = 'example'
    allowed_domains = ['example.com']
    start_urls = ['https://example.com']

    def parse(self, response):
        # ページ内のリンクを取得して再帰的に巡回
        for href in response.css('a::attr(href)').getall():
            yield response.follow(href, callback=self.parse)

        # データ抽出例
        item = {
            'title': response.css('h1::text').get(),
            'url': response.url,
        }
        yield item

3. パイプライン設定

settings.pyでパイプラインを有効化し、JSONファイルに保存する例です。

ITEM_PIPELINES = {
    'example_scraper.pipelines.JsonWriterPipeline': 300,
}

# pipelines.py
import json

class JsonWriterPipeline:
    def open_spider(self, spider):
        self.file = open('items.json', 'w', encoding='utf-8')

    def close_spider(self, spider):
        self.file.close()

    def process_item(self, item, spider):
        line = json.dumps(dict(item), ensure_ascii=False) + '\\n'
        self.file.write(line)
        return item

4. 大規模スクレイピングのための設定

大量のページを高速に取得するには、以下の設定を調整します。

# settings.py
CONCURRENT_REQUESTS = 200
DOWNLOAD_DELAY = 0.1
COOKIES_ENABLED = False
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 0.5
AUTOTHROTTLE_MAX_DELAY = 60

また、FEED_EXPORT_ENCODING'utf-8'に設定すると、文字化けを防げます。

FEED_EXPORT_ENCODING = 'utf-8'

5. 実行

scrapy crawl example -o output.json

上記コマンドで、スパイダーが起動し、抽出したデータがoutput.jsonに保存されます。

応用テクニック

基本的なクローラーを超えて、実務で役立つテクニックを紹介します。

1. API連携と認証

多くのサイトはAPIキーやOAuth認証を要求します。ScrapyではDEFAULT_REQUEST_HEADERSCOOKIESを設定し、ヘッダーにトークンを付与できます。

# settings.py
DEFAULT_REQUEST_HEADERS = {
    'Authorization': 'Bearer YOUR_TOKEN',
    'User-Agent': 'Mozilla/5.0 (compatible; Scrapy/2.6.0)'
}

2. JavaScriptレンダリング

非同期で生成されるコンテンツを取得するには、Scrapy-PlaywrightやSplashを併用します。以下はPlaywrightを使った例です。

# settings.py
TWISTED_REACTOR = 'twisted.internet.asyncioreactor.AsyncioSelectorReactor'
PLAYWRIGHT_BROWSER_TYPE = 'chromium'
PLAYWRIGHT_LAUNCH_OPTIONS = {'headless': True}
# spider.py
from scrapy_playwright.page import PageCoroutine

class JsSpider(scrapy.Spider):
    name = 'js'
    start_urls = ['https://example.com']

    async def parse(self, response):
        # JavaScriptで生成された要素を取得
        await response.page.wait_for_selector('div.dynamic')
        dynamic_text = await response.page.inner_text('div.dynamic')
        yield {'dynamic': dynamic_text}

3. スケジューリングと分散実行

大規模スクレイピングでは、複数ノードで分散実行することが有効です。Scrapy ClusterやCeleryを組み合わせ、キューにタスクを投入し、複数のワーカーで並列処理できます。

# scrapy.cfg
[deploy]
project = example_scraper
url = http://scrapy-cluster:6800

これにより、数千ページを数時間で収集可能です。

Check: 大規模スクレイピングでは、対象サイトのrobots.txtを必ず確認し、過剰なリクエストを避けるようにしてください。

トラブルシューティング

実際に運用する際に直面しやすいエラーと対処法をまとめます。

1. 403 Forbidden

サーバーがアクセスを拒否する場合、User-Agentを変更するか、DOWNLOAD_DELAYを増やしてリクエスト頻度を下げます。

2. 502 Bad Gateway / タイムアウト

ネットワーク不安定時は、RETRY_TIMESを増やし、DOWNLOAD_TIMEOUTを長めに設定します。

3. データが取得できない

CSSセレクタが変更された可能性があります。ブラウザの開発者ツールで再確認し、response.css()を修正します。

4. メモリリーク

大量データをメモリに保持している場合、パイプラインでファイルに書き出すか、データベースに直接保存します。

まとめ

ScrapyはPython自動化の強力なフレームワークであり、非同期I/Oとパイプライン機能を活用すれば、大規模スクレイピングを効率的に実装できます。今回紹介した設定や応用テクニックをベースに、実際のプロジェクトに合わせてカスタマイズしてみてください。次のステップとしては、Scrapy-PlaywrightやScrapy Clusterを試し、分散環境でのスケーラビリティを検証することをおすすめします。

この記事はAI技術を活用して作成されましたが、内容は慎重に確認されています。

コメント