Scrapy非同期クローラー
はじめに
近年、Web上に溢れる情報を効率的に取得し、ビジネスや研究に活用するニーズが急増しています。Python自動化の一環として、Webクローラーを構築する際に欠かせないのがScrapyです。Scrapyは、クローリング、データ抽出、パイプライン処理を統合したフレームワークであり、非同期I/Oを活用して高速に大量のページを取得できます。この記事では、Scrapyを使った大規模スクレイピングの設計から実装、運用までを実践的に解説します。
基礎知識・概念
まずはScrapyの主要コンポーネントを整理します。
スパイダー(Spider)はURLを巡回し、レスポンスを受け取るクラスです。
アイテム(Item)は抽出したデータを格納する構造体で、辞書型に似ています。
パイプライン(Pipeline)はアイテムを順次処理し、データベースへの保存やファイル出力を行います。
ScrapyはTwistedという非同期フレームワーク上に構築されており、CONCURRENT_REQUESTSやDOWNLOAD_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_HEADERSやCOOKIESを設定し、ヘッダーにトークンを付与できます。
# 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
これにより、数千ページを数時間で収集可能です。
トラブルシューティング
実際に運用する際に直面しやすいエラーと対処法をまとめます。
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を試し、分散環境でのスケーラビリティを検証することをおすすめします。
コメント
コメントを投稿