はじめに:Scrapyとは?🤔
Scrapy(スクレイピー)は、Pythonで書かれた強力で高速なWebクローリング・スクレイピングフレームワークです。Webサイトから構造化されたデータを効率的に抽出するために設計されており、データマイニング、情報処理、履歴のアーカイブなど、幅広い用途で利用されています。
従来のスクレイピングでは、RequestsでHTMLを取得し、BeautifulSoupやlxmlでパースするといった複数のライブラリを組み合わせる必要がありましたが、Scrapyはこれらの機能を統合し、さらに多くの便利な機能を提供することで、開発者がスクレイピングの本質的な部分(データの抽出ロジック)に集中できるようにします。非同期処理をベースにしているため、高速な動作が可能です。
Scrapyはオープンソース(BSDライセンス)であり、活発なコミュニティによって開発・メンテナンスされています。Zyte社(旧Scrapinghub)が主要なメンテナーを務めています。
なぜScrapyを使うのか?
- 高速性: 非同期ネットワークライブラリであるTwistedを基盤としており、複数のリクエストを並行して処理できます。
- 拡張性: ミドルウェアやパイプラインといったコンポーネントを通じて、機能の追加やカスタマイズが容易です。
- 堅牢性: エラーハンドリング、リトライ機構、robots.txtの尊重など、安定したクローリングのための機能が組み込まれています。
- ポータビリティ: Pythonで書かれており、Linux, Windows, macOS, BSDなど、様々なOSで動作します。
- 組み込み機能: データの抽出(CSSセレクタ, XPath)、データの整形・保存(Item Pipeline)、エクスポート(JSON, CSV, XMLなど)、クローリングの制御(深さ制限、ドメイン制限など)といった多くの機能が標準で提供されています。
インストールと環境構築 💻
Scrapyを使用するには、まずPython環境が必要です。ScrapyはPython 3.9以上をサポートしています(2025年4月時点)。
パッケージの依存関係の衝突を避けるため、仮想環境(venvなど)内にScrapyをインストールすることが強く推奨されます。
pipを使用したインストール:
最も一般的なインストール方法はpipを使用することです。
# 仮想環境を作成 (例: venv)
python -m venv scrapy_env
# 仮想環境を有効化
# Windowsの場合: scrapy_env\Scripts\activate
# macOS/Linuxの場合: source scrapy_env/bin/activate
# Scrapyをインストール
pip install Scrapy
依存関係(lxml, twisted, cryptographyなど)のインストールで問題が発生する場合は、プラットフォーム固有のインストール手順を確認してください。特にWindowsでは、Cコンパイラが必要になることがあります。
condaを使用したインストール:
AnacondaやMinicondaを使用している場合は、conda-forgeチャネルからインストールするのが簡単です。
conda install -c conda-forge scrapy
インストール後、バージョンを確認できます。
scrapy version
Scrapyの基本的な使い方 🚶♀️➡️🏃♀️
Scrapyを使ったスクレイピングの基本的な流れは以下のようになります。
- プロジェクトの作成: `scrapy startproject`コマンドでプロジェクトの雛形を作成します。
- Itemの定義: 抽出したいデータの構造を`items.py`で定義します。
- Spiderの作成: クロールの開始URL、ページの辿り方、データの抽出ロジックをSpiderクラスに記述します。`scrapy genspider`コマンドでSpiderの雛形を作成できます。
- Spiderの実行: `scrapy crawl`コマンドでSpiderを実行し、スクレイピングを開始します。
- (オプション) Item Pipelineの設定: 抽出したデータの加工、検証、保存処理を`pipelines.py`に記述し、`settings.py`で有効化します。
1. プロジェクトの作成
まず、ターミナルで任意のディレクトリに移動し、以下のコマンドを実行します。ここでは`myproject`という名前のプロジェクトを作成します。
scrapy startproject myproject
cd myproject
これにより、以下のようなディレクトリ構造が生成されます。
myproject/
scrapy.cfg # プロジェクトの設定ファイル
myproject/ # プロジェクトのPythonモジュール
__init__.py
items.py # Item定義ファイル
middlewares.py # Middleware定義ファイル
pipelines.py # Pipeline定義ファイル
settings.py # プロジェクト設定ファイル
spiders/ # Spider格納ディレクトリ
__init__.py
2. Itemの定義 (`items.py`)
スクレイピングしたいデータの構造を定義します。`scrapy.Item`を継承したクラスを作成し、抽出したいフィールドを`scrapy.Field()`で定義します。
import scrapy
class QuoteItem(scrapy.Item):
# 引用文
text = scrapy.Field()
# 著者
author = scrapy.Field()
# タグリスト
tags = scrapy.Field()
3. Spiderの作成 (`spiders/`)
Spiderは、特定のWebサイト(群)をクロールし、ページからItemを抽出する方法を定義するクラスです。`scrapy genspider`コマンドを使うと雛形を簡単に作成できます。
# scrapy genspider <spider_name> <allowed_domain>
scrapy genspider quotes quotes.toscrape.com
これにより、`spiders/quotes.py`が生成されます。中身を編集して、実際の抽出ロジックを記述します。
import scrapy
from myproject.items import QuoteItem # items.py で定義したクラスをインポート
class QuotesSpider(scrapy.Spider):
name = 'quotes' # Spiderを識別するための名前(ユニークである必要あり)
allowed_domains = ['quotes.toscrape.com'] # このドメイン外のURLには追従しない
start_urls = ['http://quotes.toscrape.com/'] # クロールを開始するURLのリスト
def parse(self, response):
# responseオブジェクトからデータを抽出
for quote_div in response.css('div.quote'):
item = QuoteItem()
item['text'] = quote_div.css('span.text::text').get()
item['author'] = quote_div.css('small.author::text').get()
item['tags'] = quote_div.css('div.tags a.tag::text').getall()
yield item # 抽出したItemをyieldで返す
# 次のページへのリンクを取得して、再帰的にparseメソッドを呼び出す
next_page = response.css('li.next a::attr(href)').get()
if next_page is not None:
# response.urljoin()で絶対URLに変換
next_page_url = response.urljoin(next_page)
# 次のページのリクエストを生成し、コールバック関数として自身(parse)を指定
yield scrapy.Request(next_page_url, callback=self.parse)
データ抽出 (Selectors): Scrapyは強力なセレクタ機能(`response.css()` と `response.xpath()`)を提供しており、CSSセレクタやXPathを使ってHTML/XML文書から簡単にデータを抽出できます。
- `.get()`: 最初にマッチした要素の内容を取得します。
- `.getall()`: マッチした全ての要素の内容をリストで取得します。
- `::text`: 要素内のテキストコンテンツを取得します。
- `::attr(attribute_name)`: 指定した属性の値を取得します。
Scrapy Shell: 開発中にセレクタの動作を確認したい場合、`scrapy shell
scrapy shell "http://quotes.toscrape.com/"
Shell内で以下のように試せます:
# CSSセレクタでタイトルを取得
response.css('title::text').get()
# XPathで最初の引用を取得
response.xpath('//div[@class="quote"]/span[@class="text"]/text()').get()
# fetch()で別のページに移動 (Shell内のみ)
fetch("http://quotes.toscrape.com/page/2/")
response.css('title::text').get() # 新しいページのタイトルを取得
4. Spiderの実行 (`scrapy crawl`)
プロジェクトのルートディレクトリ(`scrapy.cfg`があるディレクトリ)で、以下のコマンドを実行します。
# scrapy crawl <spider_name>
scrapy crawl quotes
これにより、`quotes`という名前のSpiderが実行され、ログとともに抽出されたItemが標準出力に表示されます。
Feed Exports: 抽出したデータをファイルに保存したい場合は、`-o` (または `–output`) オプションを使います。Scrapyはファイル拡張子から自動的にフォーマット(JSON, JSON Lines, CSV, XMLなど)を判断します。
# JSON Lines形式で保存
scrapy crawl quotes -o quotes.jl
# CSV形式で保存
scrapy crawl quotes -o quotes.csv
# JSON形式で保存 (インデント付き)
scrapy crawl quotes -o quotes.json -O quotes.json:indent=4 # -O は上書きオプション
より詳細なエクスポート設定は`settings.py`の`FEEDS`設定で行うことも可能です。
Scrapyアーキテクチャ 🏗️
Scrapyは、いくつかの主要コンポーネントが連携して動作するモジュール式のアーキテクチャを採用しています。これにより、高い拡張性と柔軟性を実現しています。
コンポーネント | 役割 |
---|---|
Scrapy Engine (エンジン) | システム全体のデータフローを制御する中心的な役割。各コンポーネント間の通信を司り、イベントをトリガーする。 |
Scheduler (スケジューラ) | Engineから受け取ったリクエスト(次にクロールするURL)をキューで管理し、Engineからの要求に応じてDownloaderに渡す順序を決定する。重複リクエストのフィルタリングも行う。 |
Downloader (ダウンローダー) | Schedulerから受け取ったリクエストに基づき、Webページをダウンロードし、そのレスポンス(HTTP応答)をEngine経由でSpiderに渡す。非同期に複数のダウンロードを並行して行う。 |
Spiders (スパイダー) | 開発者が主に記述する部分。Downloaderから受け取ったレスポンスを解析し、Item(抽出データ)や次のリクエスト(フォローするリンク)を生成してEngineに返す。 |
Item Pipeline (アイテムパイプライン) | Spiderが生成したItemを受け取り、一連の処理(データのクリーニング、検証、重複チェック、データベースへの保存など)を順番に行う。複数のパイプラインを定義できる。 |
Downloader Middlewares (ダウンローダーミドルウェア) | EngineとDownloaderの間に位置し、EngineからDownloaderへ渡されるリクエストと、DownloaderからEngineへ返されるレスポンスを加工・処理するためのフック。User-Agentの変更、プロキシ設定、リトライ処理などに使われる。 |
Spider Middlewares (スパイダーミドルウェア) | EngineとSpiderの間に位置し、Spiderへの入力(レスポンス)とSpiderからの出力(Itemやリクエスト)を加工・処理するためのフック。クロール深度の制限、オフサイトリクエストのフィルタリングなどに使われる。 |
データフローの概要:
- EngineがSpiderから最初のクロールリクエストを取得する。
- EngineがリクエストをSchedulerに送り、キューに入れるよう指示する。
- EngineがSchedulerに次のリクエストを要求する。
- Schedulerが次のリクエストをEngineに返し、EngineはそれをDownloader Middlewaresを経由してDownloaderに送る。
- Downloaderがページをダウンロードし、レスポンスを生成したら、Downloader Middlewaresを経由してEngineに送る。
- EngineがDownloaderからレスポンスを受け取り、Spider Middlewaresを経由してSpiderに処理を依頼する。
- Spiderがレスポンスを処理し、抽出したItemや新しいリクエストをSpider Middlewaresを経由してEngineに返す。
- Engineが処理済みのItemをItem Pipelineに送り、新しいリクエストをSchedulerに送る(ステップ2へ)。
- キューにリクエストがなくなるまで、ステップ2から8のプロセスが繰り返される。
このイベント駆動型の非同期アーキテクチャにより、Scrapyはダウンロードの待ち時間中にも他の処理を進めることができ、効率的なクローリングを実現します。
高度な機能とテクニック ✨
Item Pipelines (`pipelines.py`)
Item Pipelineは、Spiderによって抽出されたItemを処理するためのコンポーネントです。複数のパイプラインを定義し、`settings.py`の`ITEM_PIPELINES`設定で有効化と実行順序(数値が小さいほど先に実行)を指定します。
主な用途:
- データクリーニング: HTMLタグの除去、不要な空白の削除、価格から通貨記号を除くなど。
- データ検証: 必須フィールドが存在するか、データ型が正しいかなどをチェック。不正なItemは`DropItem`例外を発生させて破棄できる。
- 重複チェック: すでに処理済みのItemかどうかを判定し、重複していれば破棄する。
- データ永続化: データベース(MySQL, PostgreSQL, MongoDBなど)、ファイル、外部APIなどへItemを保存する。
パイプラインクラスは通常、以下のメソッドを実装します:
- `process_item(self, item, spider)`: 各Itemに対して呼び出される主要な処理メソッド。Itemを返すか、`DropItem`例外を送出する。非同期処理(`async def`)も可能。
- `open_spider(self, spider)`: Spiderが開かれたときに一度だけ呼び出される。リソースの初期化(DB接続など)に使用。
- `close_spider(self, spider)`: Spiderが閉じられたときに一度だけ呼び出される。リソースのクリーンアップ(DB接続切断など)に使用。
- `from_crawler(cls, crawler)`: (クラスメソッド) Pipelineインスタンスを作成するために呼び出される。Crawlerオブジェクトを介して設定やシグナルにアクセスできる。
# pipelines.py の例
from itemadapter import ItemAdapter
from scrapy.exceptions import DropItem
import pymongo # MongoDBの例
class PriceToFloatPipeline:
def process_item(self, item, spider):
adapter = ItemAdapter(item)
if adapter.get('price'):
try:
adapter['price'] = float(adapter['price'].replace('£', ''))
return item
except ValueError:
raise DropItem(f"Invalid price found in {item}")
else:
raise DropItem(f"Missing price in {item}")
class DuplicatesPipeline:
def __init__(self):
self.ids_seen = set()
def process_item(self, item, spider):
adapter = ItemAdapter(item)
# ここでは例としてtextフィールドで重複チェック
if adapter['text'] in self.ids_seen:
raise DropItem(f"Duplicate item found: {item!r}")
else:
self.ids_seen.add(adapter['text'])
return item
class MongoPipeline:
collection_name = 'scrapy_items'
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
# settings.pyからMongoDB接続情報を取得
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
# Itemを辞書に変換してMongoDBに挿入
self.db[self.collection_name].insert_one(ItemAdapter(item).asdict())
return item
# settings.py で有効化と順番を指定
# ITEM_PIPELINES = {
# 'myproject.pipelines.PriceToFloatPipeline': 100,
# 'myproject.pipelines.DuplicatesPipeline': 200,
# 'myproject.pipelines.MongoPipeline': 300,
# }
# MONGO_URI = 'mongodb://localhost:27017/'
# MONGO_DATABASE = 'my_database'
Middlewares (`middlewares.py`)
ミドルウェアは、Scrapyのリクエスト/レスポンス処理フローに割り込んでカスタムロジックを挿入するための仕組みです。Downloader MiddlewareとSpider Middlewareの2種類があります。
- Downloader Middleware: EngineとDownloaderの間に位置します。
- `process_request(self, request, spider)`: Downloaderに送られる前のリクエストを処理。リクエストの変更(ヘッダ追加、プロキシ設定など)、破棄、またはレスポンスを直接返すことができる。
- `process_response(self, request, response, spider)`: Downloaderから返されたレスポンスを処理。レスポンスの変更、破棄、または新しいリクエストを返すことができる。
- `process_exception(self, request, exception, spider)`: リクエスト処理中に例外が発生した場合に呼び出される。リトライ処理などに使用。
- Spider Middleware: EngineとSpiderの間に位置します。
- `process_spider_input(self, response, spider)`: Spiderに渡される前のレスポンスを処理。
- `process_spider_output(self, response, result, spider)`: Spiderから返された結果(Itemやリクエスト)を処理。
- `process_spider_exception(self, response, exception, spider)`: Spider処理中に例外が発生した場合に呼び出される。
- `process_start_requests(self, start_requests, spider)`: Spiderの`start_requests`メソッドから返されたリクエストを処理。
カスタムミドルウェアを作成し、`settings.py`の`DOWNLOADER_MIDDLEWARES`または`SPIDER_MIDDLEWARES`設定で有効化と実行順序を指定します。
# middlewares.py の例 (カスタムUser-Agentを設定するDownloader Middleware)
from scrapy import signals
from random import choice
class RandomUserAgentMiddleware:
# settings.py から USER_AGENTS リストを読み込む想定
user_agent_list = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
# ... 他のUser-Agent
]
@classmethod
def from_crawler(cls, crawler):
o = cls()
crawler.signals.connect(o.spider_opened, signal=signals.spider_opened)
# settings.pyからUSER_AGENTSを取得することも可能
# cls.user_agent_list = crawler.settings.getlist('USER_AGENTS', cls.user_agent_list)
return o
def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)
def process_request(self, request, spider):
# リクエストヘッダーにランダムなUser-Agentを設定
random_user_agent = choice(self.user_agent_list)
request.headers.setdefault('User-Agent', random_user_agent)
spider.logger.debug(f'Using User-Agent: {random_user_agent}')
# settings.py で有効化
# DOWNLOADER_MIDDLEWARES = {
# 'myproject.middlewares.RandomUserAgentMiddleware': 543,
# # ScrapyデフォルトのUserAgentMiddlewareより先に実行されるように設定 (デフォルトは500)
# 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
# }
設定 (`settings.py`)
`settings.py`ファイルは、Scrapyプロジェクトの動作をカスタマイズするための中心的な場所です。多くの設定項目があり、主なものには以下のようなものがあります。
- `BOT_NAME`: プロジェクト名(User-Agentなどで使用)。
- `SPIDER_MODULES`, `NEWSPIDER_MODULE`: Spiderを探すモジュールのリスト。
- `ROBOTSTXT_OBEY`: `robots.txt`のルールに従うか (`True`/`False`)。デフォルトは`True`。
- `DOWNLOAD_DELAY`: リクエスト間の待機時間(秒)。サーバー負荷軽減のため設定推奨。
- `CONCURRENT_REQUESTS`: 同時に処理する最大リクエスト数。
- `CONCURRENT_REQUESTS_PER_DOMAIN`, `CONCURRENT_REQUESTS_PER_IP`: ドメインごと、IPごとの最大同時リクエスト数。
- `USER_AGENT`: デフォルトのUser-Agent文字列。
- `ITEM_PIPELINES`: 有効にするItem Pipelineとその実行順序。
- `DOWNLOADER_MIDDLEWARES`, `SPIDER_MIDDLEWARES`: 有効にするミドルウェアとその実行順序。
- `FEEDS`: Feed Exportsの詳細設定。
- `LOG_LEVEL`, `LOG_FILE`: ログレベルとログファイルのパス。
- `REQUEST_FINGERPRINTER_IMPLEMENTATION`: リクエストの重複判定方法の変更(例: ‘2.7’)。
- カスタム設定: MongoDBの接続情報など、プロジェクト固有の設定を追加可能。
JavaScriptレンダリング対応
近年、多くのWebサイトがJavaScriptを使って動的にコンテンツを生成しています。Scrapy本体はJavaScriptを実行しないため、そのままではこれらのサイトからデータを取得できないことがあります。
対応策としては、JavaScriptレンダリングサービスやヘッドレスブラウザと連携するミドルウェアを利用します。
- Scrapy Splash: Scrapy開発元がメンテナンスするJavaScriptレンダリングサービスSplashと連携するためのミドルウェア。比較的軽量。
- Scrapy Playwright: Microsoft製のPlaywright(ヘッドレスブラウザ自動化ライブラリ)と連携するミドルウェア。Chromium, Firefox, WebKitに対応。高機能だがややリソースを消費。2024年頃から注目されている。
- Scrapy Selenium: 定番のブラウザ自動化ツールSeleniumと連携するミドルウェア。古くからあるが、Playwrightに比べるとパフォーマンス面で劣る場合がある。
これらのミドルウェアを導入し、`settings.py`で有効化することで、SpiderはJavaScriptがレンダリングされた後のHTMLを取得できるようになります。例えば、Scrapy Playwrightを使う場合、`settings.py`に以下のような設定を追加し、リクエスト時に`meta={‘playwright’: True}`を指定します。
# settings.py の設定例 (Scrapy Playwright)
# DOWNLOAD_HANDLERS = {
# "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
# "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
# }
# TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
# PLAYWRIGHT_BROWSER_TYPE = 'chromium' # または 'firefox', 'webkit'
# PLAYWRIGHT_LAUNCH_OPTIONS = {
# 'headless': True, # ヘッドレスモードで起動
# }
# Spider内でのリクエスト例
# yield scrapy.Request(url, meta={'playwright': True}, callback=self.parse_js_page)
デプロイと実行管理
- Scrapyd: Scrapyプロジェクトをサーバー上で実行・管理するためのデーモンサービス。HTTP API経由でSpiderのデプロイや実行制御が可能。
- Scrapy Cloud: Scrapy開発元であるZyte社が提供するクラウドプラットフォーム。Spiderのホスティング、スケジューリング、モニタリング、データストレージなどを提供。
活用事例 💡
Scrapyはその強力な機能から、様々な分野で活用されています。
- データマイニング・市場調査: ECサイトの商品価格、レビュー、在庫情報収集。不動産サイトの物件情報収集。競合他社の情報収集。
- ニュース・コンテンツ集約: ニュースサイト、ブログ、SNSからの情報収集と分析。
- モニタリング: Webサイトの変更検知、価格変動監視、求人情報の監視。
- テスト自動化: Webサイトのリンク切れチェック、フォーム入力テスト。
- 学術研究: 大規模なWebデータセットの構築。
- SEO分析: 検索結果ページの順位や内容の収集。
ベストプラクティスと注意点 ⚠️
Webスクレイピングを行う際には、技術的な側面だけでなく、倫理的・法的な側面にも注意が必要です。
- 礼儀正しく:
- `DOWNLOAD_DELAY`を設定し、サーバーに過度な負荷をかけないようにする。
- `CONCURRENT_REQUESTS_PER_DOMAIN`/`CONCURRENT_REQUESTS_PER_IP`を適切に設定する。
- 適切な`User-Agent`を設定し、ボットであることを正直に(または一般的なブラウザとして)伝える。
- Webサイトの利用規約を確認し、スクレイピングが禁止されていないか確認する。
- `robots.txt`の尊重: デフォルトでScrapyは`robots.txt`のルールに従います (`ROBOTSTXT_OBEY = True`)。特定のクローラに対するアクセス制限が記述されている場合、それに従うべきです。無視することも可能ですが、推奨されません。
- エラーハンドリング: Spider内で`try…except`ブロックを使用したり、Downloader Middlewareの`process_exception`を利用したりして、ネットワークエラーやパースエラーに適切に対処する。
- データ品質: 抽出したデータの検証(Item Pipelineなど)を行い、不正確なデータや欠損データに対応する。Webサイトの構造変更に備え、定期的にSpiderの動作を確認する。
- ログ活用: Scrapyは詳細なログを出力します。`LOG_LEVEL`を設定し、デバッグや問題解決に役立てる。
- 法的・倫理的側面: 著作権、個人情報保護、不正アクセス禁止法などに抵触しないよう注意する。特に個人情報やログインが必要なページのスクレイピングは慎重に行う。
まとめ 🎉
Scrapyは、Pythonで効率的かつ堅牢なWebクローラ・スクレイパーを開発するための非常に強力なフレームワークです。非同期処理による高速性、モジュール化されたアーキテクチャによる高い拡張性、そして豊富な組み込み機能により、単純なデータ収集から複雑なクローリングタスクまで幅広く対応できます。
基本的な使い方から、Item Pipeline、Middleware、JavaScriptレンダリング対応といった高度な機能まで理解することで、Web上の膨大な情報源から価値あるデータを引き出すことが可能になります。
ただし、スクレイピングを行う際は、対象サイトへの負荷や利用規約、法的側面に十分配慮し、責任ある行動を心がけましょう。
ぜひ公式ドキュメント (https://docs.scrapy.org/en/latest/) も参照しながら、Scrapyを使ったWebスクレイピングの世界を探求してみてください!🚀✨
コメント