Scrapyの使い方とScrapy Cloudへのデプロイ手順【2.17対応】
Scrapyの入門記事はネット上に多数ありますが、その大半はスパイダーの起点を start_requests() で書いています。このメソッドは2026年5月の2.16.0で削除され、呼ばれなくなりました。しかも例外は出ません。start_requests() だけを定義したスパイダーは、エラーも警告も出さずに0リクエストで終了します。本記事は2026年7月7日公開の2.17.0を基準に、インストールからスパイダー実行、Scrapy Cloudへのデプロイまでを実際の設定値とコマンドで確認しながら説明します。
まとめ
- Scrapy 2.17.0(2026年7月7日公開)が対応するPythonは3.10〜3.14。3.9とPyPy 3.10は2.14.0で切り捨てられました。
- スパイダーの起点は2.13で
async def start()に置き換わり、旧start_requests()は2.16.0で削除され呼ばれなくなりました。2.13より前のScrapyも同時に支える場合に限り両方を定義します。 - 手順は「インストールとプロジェクト生成」「
genspiderでの雛形作成」「parseの実装」「crawlでの出力」「shellでの検証」の5段で完結します。 startprojectが生成するsettings.pyは、フレームワークの既定値より抑制的な値(robots.txt遵守・1秒待機・同一ドメイン同時1本)を明示的に上書きしています。プロジェクト外で単体ファイルを走らせると、この抑制は効きません。- Scrapy Cloudの無料プランはジョブ実行が最大1時間・データ保持7日で、定期実行は有料プラン限定です。有料は1ユニット月9ドルから。
- 2.14.1以下には深刻度「高」の脆弱性が2件、2.14.2から2.16には1件残っています。バージョンを古いまま固定する運用は、それ自体がリスクです。
以下では、この5段の手順と2.13以降の書き換え、そしてクラウド実行への移し方を順に見ていきます。
Scrapyの担当範囲とライブラリ選択の基準
クローリングとスクレイピングの分担
Scrapyは、Python製のクローリング・スクレイピングフレームワークです。リンクをたどってページを取得する「クローリング」と、取得したHTMLから値を抜き出す「スクレイピング」の両方を1つの枠組みで扱います。リクエストのスケジューリング、同時実行数の制御、リトライ、重複URLの除外、結果のファイル出力までが最初から組み込まれている点が、HTMLパーサ単体との最大の違いです。この2つの用語の区分やAPI利用との線引き、法務面の判断基準はスクレイピングとは?クローリング・APIとの違いと実装・法務の判断を解説で個別に整理しています。
ライブラリ選択の判断基準
| 選択肢 | 向く規模 | JS描画 | 並行制御 | 導入の重さ |
|---|---|---|---|---|
| Scrapy | 数百ページ以上 | 不可(別途連携) | 組み込み | プロジェクト構成が必要 |
| requests + BeautifulSoup | 数十ページ | 不可 | 自作 | スクリプト1本 |
| Selenium / Playwright | 数十〜数百ページ | 可 | 自作 | ブラウザ実体が必要 |
判断は「ページ数」ではなく「制御を自分で書きたいか」で決めるほうが早いです。待機間隔やリトライを自分で書きたくないならScrapy、単発の取得ならHTMLパーサ単体で十分です。パーサ側の書き方はBeautifulSoupとは?インストールから基本的な使い方・スクレイピングまで解説、JavaScriptで描画されるページの扱いはPlaywright MCPとは?設定・使い方からDify連携まで実務ガイド【2026年版】が参考になります。Scrapy自体はブラウザを内蔵しないため、JS描画が必須のサイトでは別の手段と組み合わせる前提で設計してください。
動作環境とバージョンの境界線
対応Pythonバージョンと切り捨ての履歴
| 版 | 公開日 | Python対応 | 主な変更 |
|---|---|---|---|
| 2.12.0 | 2024-11-18 | 3.8を切り捨て/3.13追加 | — |
| 2.13.0 | 2025-05-08 | — | start()導入/asyncioリアクタ既定化 |
| 2.14.0 | 2026-01-05 | 3.9とPyPy 3.10を切り捨て | 既定優先度キュー変更 |
| 2.15.0 | 2026-04-09 | — | httpx系ハンドラ(実験的) |
| 2.16.0 | 2026-05-19 | 3.14を公式対応 | start_requests()を削除 |
| 2.17.0 | 2026-07-07 | — | s3://を既定でHTTPS化 |
2.17.0のパッケージ定義は requires_python >=3.10 で、公式に分類子が付いているのは3.10から3.14までです。Python 3.9以下の環境では pip install scrapy が2.13系までしか解決しません。Python 3系向けの古い解説記事どおりに進めてもインストールが通らない場合は、まず手元のPythonのマイナーバージョンを確認してください。
2.17未満に残る既知の脆弱性
2.14.2(2026年3月12日)で修正された GHSA-cwxj-rr6w-m6w7 は、深刻度「高」で影響範囲が1.4.0から2.14.1までと広いものです。RefererMiddleware がレスポンスの Referrer-Policy ヘッダの値をPythonの呼び出し可能オブジェクトとして解釈してしまう、つまりクロール先のサーバがヘッダ経由で任意のモジュール読み込みを誘発できる不具合でした。クロール対象は自分で管理できないサーバなので、この経路は実質的に外部からの入力です。
もう1件の GHSA-76g3-c3x4-crvx は2.17で修正された同じく「高」で、2.16以下が影響します。S3DownloadHandler が署名済みのS3リクエストを平文HTTPで送っており、リクエストパス、AWSの Authorization ヘッダ、一時credentials利用時の X-Amz-Security-Token ヘッダ、そしてレスポンス内容が経路上で読める状態でした。2.17ではHTTPSが既定になり、旧挙動に戻すには request.meta["is_secure"] を False にする必要があります。S3を触っていない構成なら実害はありませんが、2件を踏まえると「動いているから古い版のままでよい」という判断は取りにくくなります。固定するなら2.17.0以上で固定してください。
インストールからスパイダー実行までの5ステップ
インストールとプロジェクト生成
pip install scrapy
scrapy startproject quotes_crawler
cd quotes_crawler
startproject は scrapy.cfg、設定ファイル、items.py、pipelines.py、spiders ディレクトリを生成します。この雛形が置く settings.py の値は後述のとおりフレームワーク既定より抑制的なので、練習であってもプロジェクトを作ってから始めるほうが安全です。
genspiderでのスパイダー雛形作成
scrapy genspider quotes quotes.toscrape.com
生成されるのは import scrapy と name、allowed_domains、start_urls、空の parse だけの最小構成です。start_urls を書いておけば Spider.start の既定実装がそこからリクエストを生成するため、2.13以降でも起点のコードを自分で書く必要はありません。テンプレートは scrapy genspider -l で一覧でき、リンク追跡用のルールを使う場合は -t crawl を指定します。練習対象には公式チュートリアルでも使われる toscrape.com を使うと、相手先に負荷をかけずに試せます。
parseメソッドの実装とページ送り
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
allowed_domains = ["quotes.toscrape.com"]
start_urls = ["https://quotes.toscrape.com/page/1/"]
def parse(self, response):
for card in response.css("div.quote"):
yield {
"text": card.css("span.text::text").get(),
"author": card.css("small.author::text").get(),
"tags": card.css("div.tags a.tag::text").getall(),
}
next_page = response.css("li.next a::attr(href)").get()
if next_page:
yield response.follow(next_page, callback=self.parse)
parse は辞書を yield するだけでアイテムになり、items.py の定義は必須ではありません。response.follow は相対URLを解決してリクエストを組み立てるので、urljoin を自分で呼ぶ必要はありません。値が取れないときは .get() が None を返して静かに通り過ぎるため、セレクタの検証は後述のシェルで先に済ませるのが確実です。
crawlの実行とファイル出力
scrapy crawl quotes -O quotes.json
scrapy crawl quotes -o quotes.jsonl
公式のコマンド仕様では -O(大文字)が既存ファイルの上書き、-o(小文字)がファイル末尾への追記と定義されています。この挙動の帰結として、JSON形式で -o を使い回すと配列の閉じ括弧の後に次の配列が続く形になり、単一のJSONとしては読み込めないファイルができます。繰り返し実行して結果を貯めたいなら1行1レコードの jsonl、毎回作り直すなら -O と使い分けてください。
scrapy shellでのセレクタ検証
scrapy shell "https://quotes.toscrape.com/page/1/"
>>> response.css("span.text::text").get()
>>> response.css("li.next a::attr(href)").get()
シェルは実際のレスポンスを保持したまま対話的にセレクタを試せるので、スパイダーを書き換えて再実行する往復がなくなります。ブラウザの開発者ツールで見えるDOMはJavaScript実行後の姿である一方、Scrapyが受け取るのはHTMLそのものです。開発者ツールでコピーしたセレクタが効かない場合は、まずシェルで response.text を確認して、目的の要素がHTMLに含まれているかを切り分けます。
start()への移行と2.16での削除
Spider.start は2.13で追加された非同期ジェネレータです。scrapy.Spider.start のリファレンスによれば、既定の実装は start_urls を順に読み、dont_filter を有効にしたリクエストを生成します。つまり start_urls だけを書く単純なスパイダーなら、2.13以降も何も変更しなくても動きます。書き換えが必要になるのは、起点でPOSTを投げる、ヘッダやクッキーを付ける、DBから対象URLを読むなど、start_requests() を自分で定義していた場合です。
# 2.16.0で削除された旧来の書き方
def start_requests(self):
yield scrapy.Request("https://quotes.toscrape.com/page/1/")
# 2.13以降の書き方
async def start(self):
yield scrapy.Request("https://quotes.toscrape.com/page/1/")
移行の実作業は def を async def に変え、メソッド名を差し替えるだけです。注意すべきは2.16.0の挙動で、リリースノートは start_requests() を「削除され、もう呼ばれない」と明記しています。例外は投げられないため、旧来の書き方のまま2.16以降へ上げたスパイダーは、start_urls が空なら1リクエストも送らずに正常終了します。2.13より前の版でも動かす必要がある場合は、start() と同期の start_requests() を両方定義しておけば、どちらの版でも起点が解決されます(2.16以降では後者が無視されるだけです)。
あわせて、2.13が変えたのは TWISTED_REACTOR の既定値です。None から AsyncioSelectorReactor になったため、asyncio前提のライブラリを設定なしで await できます。一方で、非同期に対応しないサードパーティのスパイダーミドルウェアは2.13で非推奨となり、その同期版 process_spider_output() のサポートは2.16.0で削除されました。外部プラグインを使っている環境では、先にその対応状況を確認してから上げてください。
settings.pyの既定値と雛形の差
| 設定キー | フレームワークの値 | startproject雛形の値 |
|---|---|---|
| ROBOTSTXT_OBEY | False | True |
| DOWNLOAD_DELAY | 0 | 1 |
| CONCURRENT_REQUESTS_PER_DOMAIN | 8 | 1 |
| CONCURRENT_REQUESTS | 16 | 16(コメント化) |
| FEED_EXPORT_ENCODING | None | utf-8 |
| AUTOTHROTTLE_ENABLED | False | コメント化 |
この差はScrapyのドキュメントを読むときの落とし穴になります。公式は「startproject が設定する値をDefaultとして記載し、scrapy.settings.default_settings の値をfallbackとして記載する」という規約を採っているため、ドキュメントの「Default」が手元の実行環境の既定値と一致しない場合があるのです。runspider や shell はプロジェクト外でも動くコマンドで、公式も「プロジェクト内から実行した場合はプロジェクト側の上書き設定を使うため挙動が少し変わる」と明記しています。裏を返せば、プロジェクトの外に置いた1ファイルのスパイダーには ROBOTSTXT_OBEY=False と DOWNLOAD_DELAY=0 が適用され、robots.txtを読まず待機なしで連続リクエストを送ります。
FEED_EXPORT_ENCODING の差も日本語サイトを扱うと必ず踏みます。値が None のとき、JSON出力だけは歴史的な理由で \uXXXX 形式の数値エンコードになり、日本語が読めない文字列として保存されます。雛形は utf-8 を明示しているのでプロジェクト内では問題になりませんが、設定を引き継がない環境では自分で utf-8 を指定してください。待機時間についても、RANDOMIZE_DOWNLOAD_DELAY が既定で True のため、実際の間隔は DOWNLOAD_DELAY の0.5倍から1.5倍に揺らぎます。所要時間の見積もりはこの前提で行ってください。なお RETRY_TIMES の2はソースのコメントどおり「初回リクエスト+2回の再試行=合計3リクエスト」で、合計2リクエストという意味ではありません。
Scrapy Cloudへのデプロイ手順と料金の境界
shubでのログインとデプロイ
pip install shub
shub login
shub deploy
shub はScrapy Cloudのコマンドラインクライアントで、2026年7月8日公開の2.18.1が最新です。Python 3.10以降が必要で、3.9の環境では2.16.0以下に固定する必要があります。shub login はAPIキーをホームディレクトリの ~/.scrapinghub.yml に保存します。CIから実行する場合は対話入力を避けて SHUB_APIKEY 環境変数を使ってください。shub deploy の初回実行ではウィザードが立ち上がり、プロジェクトIDを scrapy.cfg と同じ階層の scrapinghub.yml に保存します。2回目以降はプロジェクト配下のどこからでも shub deploy だけで反映されます。
ジョブの実行とログ・アイテムの取得
shub schedule quotes
shub log -f 2/34
shub items -f 2/34
デプロイ後は shub schedule にスパイダー名を渡してクラウド上で実行します。ジョブIDは「プロジェクトID/スパイダーID/ジョブID」をスラッシュで連結した形式(例 12345/2/15)ですが、scrapinghub.yml に既定ターゲットがあればプロジェクトIDを省略して 2/34 のように書けます。-f を付けると実行中のログとアイテムを追尾できます。ローカルで scrapy crawl して結果ファイルを見る流れが、そのままクラウド側の shub schedule と shub items に置き換わる構造です。定期実行(Periodic Jobs)はWeb管理画面から設定しますが、2017年10月10日以降は有料プラン限定の機能で、上限は1プロジェクトあたり1,000件、1組織あたり10,000件です。
無料プランと有料プランの分かれ目
| 項目 | 無料 | 有料(1ユニット月9ドルから) |
|---|---|---|
| ジョブ実行時間 | 最大1時間 | 無制限 |
| データ保持 | 最大7日 | 最大120日 |
| ユニットのリソース | 通常の半分 | RAM 1GB/ディスク2.5GB/CPU 1x |
| 同時実行 | 1本 | 保有ユニット数と同数 |
| 定期実行・Dockerデプロイ | 不可 | 可 |
境界を分けるのはリクエスト数ではなく、1回のジョブに使える時間と定期実行の可否です。無料枠で付与されるのは通常ユニットの半分のリソースを持つ low-resource unit で、有料ユニットは1つでRAM 1GB、ディスク2.5GB、CPU 1x、ジョブスロット1に相当します。同時実行本数は保有ユニット数に連動し、1つのジョブに割り当てられるのは最大6ユニットまでです。無料枠は1ジョブ1時間で打ち切られるため、対象ページ数と DOWNLOAD_DELAY から所要時間を先に見積もってください。1ページ1秒待機なら、揺らぎを含めても3,600ページ前後が上限の目安になります。
つまずきやすいエラーの切り分け
scrapy.logを参照するコードは動きません。このモジュールは1.7.0で削除されており(1.6.0までは存在)、import scrapy経由でscrapy.log.ERRORを読めばAttributeError、from scrapy import logと書けばModuleNotFoundErrorになります。ログ出力は標準のloggingモジュールかself.loggerを使います。- アイテムが1件も取れないときは、ログの
Forbidden by robots.txt:行と終了時統計のrobotstxt/forbiddenを見ます。件数が立っていれば雛形のROBOTSTXT_OBEY=Trueによるスキップで、リクエスト自体が送られていません。 - 取得できるはずの要素が
Noneになる場合は、JavaScript描画が原因かセレクタの誤りかをscrapy shellのresponse.textで切り分けます。 - 2.14.2以降はHTTP標準に合わせてPOSTに対する301リダイレクトがGETへ変換されます。認証やフォーム送信の途中でボディが消える挙動は、この変更が原因になり得ます。フォーム送信自体も2.16.0で
scrapy.FormRequestが非推奨となり、form2request ライブラリへの移行が案内されています。 - Basic認証をスパイダー属性
http_user・http_pass・http_auth_domainで書いている場合、2.17.0で非推奨になりました。HTTPAUTH_USER・HTTPAUTH_PASS・HTTPAUTH_DOMAINの各設定へ移してください。 - 単一ドメイン相手でクロール速度が出ない原因は、雛形の
DOWNLOAD_DELAY=1とCONCURRENT_REQUESTS_PER_DOMAIN=1です。まずこの2つを確認します。2.14で既定の優先度キューがDownloaderAwarePriorityQueueに変わっていますが、これは複数ドメインを並行して回すときのスケジューリング特性の話で、単一ドメインの速度とは別問題です。
ログレベルは既定でDEBUGです(LOG_LEVEL = "DEBUG"。雛形もこの値を上書きしません)。送信されたリクエストのURLはそのまま流れるので、どの段階で止まっているか分からないときはまずログを目視し、出力が多すぎる場合に scrapy crawl quotes -L INFO で絞ります。
よくある質問
Python 3.9以前の環境でScrapyを動かせますか
2.14.0でPython 3.9とPyPy 3.10のサポートが切れたため、3.9では pip install "scrapy<2.14" のように2.13系へ固定する以外に手がありません。shub も2.16.0以下に揃える必要があります。ただし2.13系は上で挙げた深刻度「高」の2件、GHSA-cwxj-rr6w-m6w7(2.14.1以下)と GHSA-76g3-c3x4-crvx(2.16以下)の両方に該当します。Pythonを3.10以上へ上げるのが先で、それが無理な場合はクロール対象を自社管理下のサーバに限定するなど、外部レスポンスを信頼できる範囲に絞る前提で運用してください。
Scrapy Cloudの無料プランだけで定期クロールを回せますか
管理画面からの定期実行は有料プラン限定なので、無料プランのままでは回せません。無料枠で定期化するには、外部のcronやCIからJobs APIを叩いてジョブを起動する形になります。その場合も1ジョブ最大1時間・データ保持7日の制約は残るため、日次で少数ページを巡回して都度取り出す用途に限られます。数万ページを1本のジョブで回す運用や、過去分をクラウド側に残して比較する運用は成立しません。
ScrapyとBeautifulSoupは併用できますか
parse の中で BeautifulSoup(response.text, "html.parser") を呼ぶ形で併用できます。ただしScrapyには response.css と response.xpath が備わっているため、抽出ロジックが二重になるだけで得るものは少ないです。既存のBeautifulSoup資産を再利用したい移行期を除いて、Scrapy側のセレクタに寄せるほうが保守は楽になります。
robots.txtを無視する設定にしても問題ありませんか
ROBOTSTXT_OBEY=False は設定上いつでも可能ですが、技術的に可能かどうかと許されるかどうかは別の問題です。サイトの利用規約、アクセス頻度による業務妨害の成否、取得したデータの利用目的がそれぞれ独立した論点になります。判断の枠組みはスクレイピングとは?クローリング・APIとの違いと実装・法務の判断を解説で整理しているので、業務で回す前に確認してください。
Scrapy Cloud以外にスパイダーを常時稼働させる方法はありますか
Scrapyはオープンソースで特定のホスティングに依存しないため、自前のサーバでcronから scrapy crawl を叩く構成へいつでも移せます。Scrapy Cloudを選ぶ利点は、ジョブ履歴・ログ・アイテムの保存と閲覧、そして定期実行の管理画面が最初から用意されている点です。ここを自作するコストと、1ユニット月9ドルからの費用を比べて決めるのが実際の判断になります。Python以外の選択肢を検討する場合はGolangを使用した効果的なウェブスクレイピングの方法と注意点も比較材料になります。