この記事の登場人物
🧑💻 …Scraplingを見つけてきた先輩エンジニア(前回に引き続き)
🔰 …前回Scraplingを教わった後輩(今度はScrapyが気になっている)
🔰「前回Scrapling教えてもらいましたけど、ページ巡回は手動ループなんですよね?」
🧑💻「そう。whileで未訪問ページを管理して、1ページずつFetcher.get()する感じ」
🔰「Scrapyだとそのへんフレームワークが全部やってくれるじゃないですか」
🧑💻「うん。巡回・リトライ・スロットリング、全部入り。じゃあ両方組み合わせたらどうなると思う?」
🔰「…合体?」
🧑💻「合体」
🔰「でも、Scraplingだけで十分じゃないんですか?前回けっこう便利だったし」
🧑💻「数ページならね。でもページ巡回で困らなかった?重複排除とかリトライとか、自分で書くの面倒だったでしょ」
🔰「…たしかに、visitedセットとか自分で管理してました」
🧑💻「そこがScrapyの守備範囲。今回はBS4じゃなくてScrapyと比べて、最後に合体技を見せるよ」
この記事でわかること
- ScrapyとScraplingの性能差(速度・メモリ・コード量)
- 構造変更への対応力の違い(Adaptive vs 固定セレクタ)
- Anti-Bot耐性の比較
- ハイブリッド構成(Scrapy巡回 + Scraplingパース)の実装と威力
この記事の解説版もあります
対話なしのストレートな技術記事として、同じ内容をまとめています。
→ 解説版はこちら
検証環境
🔰「今回もダミーサイトで試すんですか?」
🧑💻「うん。前回と同じFlask製だけど、今回はページネーション付きで30商品×5ページにしてある」
🔰「前回は1ページ6商品でしたよね。今回はだいぶ規模が大きくなったんですね」
🧑💻「そういうこと」
🔰「前回と同じで、v1→v2でセレクタが全滅するやつですね」
| 要素 | v1 セレクタ | v2 セレクタ |
|---|---|---|
| 商品カード | div.product-card |
article.item-tile |
| 商品名 | h2.product-name a |
h3.title a |
| 価格 | span.product-price |
div.cost |
| 評価 | div.product-rating |
div.stars |
| カテゴリ | span.product-category |
span.tag |
| レビュー | div.product-reviews |
span.review-count |
| 説明 | p.product-desc |
p.desc |
| ページネーション | nav.pagination a.page-link |
nav.page-nav a.page-btn |
🧑💻「ページネーションのセレクタも変えてあるのがポイント。巡回ロジックまで壊れる」
🔰「えぐい…」
セットアップ手順(クリックで展開)
# リポジトリをクローン
git clone https://github.com/matsubara457/scrapling-vs-scrapy.git
cd scrapling-vs-scrapy
# 仮想環境を作成して有効化
python3 -m venv .venv
source .venv/bin/activate
# 依存パッケージをインストール
pip install -r requirements.txt
# Flaskダミーサイト起動(port 5002)
python3 demo_site/app.py &
比較1: 速度ベンチマーク
🔰「まず気になるのは速度です。Scrapyって速いイメージありますけど」
🧑💻「同じ30商品(5ページ)を取得して比べてみた」
python3 -m scraper.benchmark --runs 2 --version v1
| 指標 | Scrapy | Scrapling | 差 |
|---|---|---|---|
| 平均時間 | 0.712s | 0.161s | Scrapling 4.4倍 速い |
| メモリピーク | 10.61MB | 1.73MB | Scrapling 6.1倍 少ない |
🔰「え、4倍以上速いんですか?」
🧑💻「内部のパーサーが違う。ScraplingはlxmlベースでC言語レベルで速い」
🔰「Scrapyって非同期で速いイメージでしたけど」
🧑💻「大量のリクエストを並列に飛ばすなら速い。でも今回みたいにlocalhostの5ページだと、Twisted——PythonでいうNode.jsのイベントループみたいなもの——の初期化コストが支配的になる」
🔰「Twistedって何ですか?」
🧑💻「Scrapyの内部エンジン。非同期I/Oの仕組みで、reactor——イベントを監視して処理を振り分けるやつ——の起動やミドルウェアの読み込みに時間がかかる。数千ページ巡回するならこのコストは無視できるけど、5ページだと目立つ」
🔰「なるほど。ページ数が少ないとオーバーヘッドの割合が大きくなるんですね」
補足: メモリ差が大きいのも同じ理由です。Scrapyはリクエストキュー、スケジューラー、ダウンローダーミドルウェアなど多くのコンポーネントをメモリに常駐させます。Scraplingは Fetcher.get() → Selector() の2ステップなので、メモリフットプリントが小さくなります。
比較2: コード量
🔰「書きやすさはどうなんですか?コード量に差あります?」
| 指標 | Scrapy | Scrapling |
|---|---|---|
| 実効行数(空行・コメント除く) | 213行 | 198行 |
🔰「あれ、ほぼ同じですね」
🧑💻「量はね。でも性格が全然違う。まずScrapyの方を見てみて」
import scrapy
from scrapy.crawler import CrawlerProcess
class TechShopSpider(scrapy.Spider):
name = "techshop"
# 最初にアクセスするURL
start_urls = ["http://localhost:5002/page/1?v=v1"]
def parse(self, response):
# 商品カードを取得
for card in response.css(".product-card"):
yield {
"name": card.css("h2.product-name a::text").get("").strip(),
"price": card.css("span.product-price::text").get(""),
}
# 次のページへのリンクを辿る(フレームワークが管理)
for href in response.css("a.page-link::attr(href)").getall():
yield response.follow(href, self.parse)
🧑💻「ポイントは
yield response.follow()。次のページを辿る処理を3行で書ける。重複排除もリトライもフレームワークがやってくれる」
🔰「便利ですね。Scraplingだとどうなるんですか?」
from scrapling.fetchers import Fetcher
def scrape_all_pages(version="v1"):
all_products = []
visited = set() # 訪問済みページを自分で管理
pages_to_visit = [1] # 未訪問ページのキュー
while pages_to_visit:
page_num = pages_to_visit.pop(0)
if page_num in visited:
continue
visited.add(page_num)
# 1ページずつHTTP取得
url = f"http://localhost:5002/page/{page_num}?v={version}"
page = Fetcher.get(url)
# 商品データを抽出
for card in page.css(".product-card"):
all_products.append({
"name": card.css("h2.product-name a").first.text.strip(),
"price": card.css("span.product-price").first.text.strip(),
})
return all_products
🧑💻「
visitedセットとかpages_to_visitとか、全部自分で管理してるでしょ」
🔰「たしかに…。巡回の仕組みを自分で書かないといけないんですね」
🧑💻「弱点というか、そもそもScraplingはパーサーであってクローラーじゃない。得意分野が違うだけ」
全文はリポジトリにあります: 上のコードは核心部分だけ抜粋しています。v1/v2両対応やエラーハンドリング込みの完全版はリポジトリを参照してください。
比較3: 構造変更対応(Adaptive)
🔰「前回教えてもらったAdaptiveですね。Scrapyだとどうなるんですか?」
🧑💻「Scrapyには構造変更に自動対応する機能はない。v1のセレクタでv2をスクレイピングしたら、当然全滅する」
python3 -m scraper.adaptive_compare full
| 要素 | Scrapy(v1セレクタ→v2) | Scrapling Adaptive |
|---|---|---|
| 商品名 | ❌ 0件 | ✅ 復元 |
| 価格 | ❌ 0件 | ⚠️ レビュー数と誤対応 |
| カテゴリ | ❌ 0件 | ✅ 復元 |
| 評価 | ❌ 0件 | ✅ 復元 |
| レビュー | ❌ 0件 | ⚠️ 価格と誤対応 |
| 説明 | ❌ 0件 | ✅ 復元 |
🔰「Scrapy 0/6全滅、Scrapling 4/6正常復元 + 2件は取れてるけど中身が入れ替わってる」
🧑💻「そう。価格とレビューがクロスワイヤリングしてる」
🔰「クロスワイヤリング?」
🧑💻「配線が交差してる状態。span.product-price(¥12,800)の指紋で探したらspan.review-count(128件のレビュー)を引っ張ってきた。タグもspanで、数値テキストを持ってて、構造的な位置も近いから間違えちゃう」
🔰「でも取れてることは取れてるんですよね」
🧑💻「そう。だからバリデーションで弾ける。こんな感じ」
# 価格なら ¥ で始まるかチェックする
def validate_price(text: str) -> bool:
return text.startswith("¥") or text.startswith("¥")
# Adaptiveで取得した値を検証
found = selector.css(".product-price", adaptive=True)
text = found.first.text.strip()
if validate_price(text):
price = text # OK: 正しい値
else:
print(f"⚠️ 価格として不正な値: {text}")
# find_by_text 等でフォールバック...
🧑💻「ここのポイントは、Scrapyだとそもそもセレクタが見つからないから、バリデーション以前の問題ってこと」
🔰「0件 vs 復元できてるけど要検証なら、後者の方が圧倒的にマシですね」
比較4: Anti-Bot耐性
🔰「最近のサイトってBot弾きますよね。そのへんはどっちが強いんですか?」
🧑💻「ダミーサイトにAnti-Botシミュレーション付けて5パターン試した」
python3 -m scraper.anti_bot_compare
| シナリオ | ツール | User-Agent | 結果 |
|---|---|---|---|
| requests デフォルト | requests | python-requests/2.32.5 |
✅ 200 |
Bot偽装 (MyBot/1.0) |
requests | MyBot/1.0 |
❌ 403 |
| Scrapling Fetcher | Scrapling | リアルなブラウザUA(自動生成) | ✅ 200 |
| Scrapy デフォルト | Scrapy | Scrapy/2.14 (+https://scrapy.org) |
✅ 200 |
| Scrapy + ミドルウェア | Scrapy | Mozilla/5.0 (Windows...) |
✅ 200 |
🔰「あれ、ScrapyデフォルトUAでも通ってますね?」
🧑💻「今回のダミーサイトはUA内のscrapy文字列をホワイトリストに入れてるからね。本番のBot検知はもっと厳しいよ」
🔰「本番だとどんな検知があるんですか?」
🧑💻「たとえばTLS fingerprint——HTTPSの接続方法でブラウザかBotか判別する技術——とか、JavaScriptを実行してブラウザの挙動パターンを見たりする。何層にもチェックが重なってる」
🔰「それ全部突破するの大変そう…」
🧑💻「ScraplingのStealthyFetcherはその辺を自動でやってくれる。内部でChromiumを起動して、ブラウザのように振る舞いながらアクセスする」
from scrapling.fetchers import StealthyFetcher
# Bot検知を自動回避してページ取得
page = StealthyFetcher.fetch("https://bot-protected-site.com")
🔰「Scrapyだと?」
🧑💻「scrapy-playwrightでPlaywrightを組み込むか、scrapy-fake-useragentとかのミドルウェアを自分で設定する。動くけどセットアップが面倒」
注意: 今回のAnti-BotテストはダミーサイトのUAチェックのみです。実サイトのBot検知(Cloudflare、PerimeterX等)はもっと多層的なので、この結果がそのまま当てはまるわけではありません。
比較5: ハイブリッド構成(この記事の目玉)
🔰「ここまで見ると、巡回はScrapyが強くて、パースはScraplingが強いですよね。でもどっちかを選ばないといけないんですか?」
🧑💻「選ばなくていい。巡回はScrapyに任せて、パースはScraplingでやる構成が組めるんだよ」
🔰「おお、最初に言ってた合体ですか!」
アーキテクチャ
🧑💻「ポイントはScrapyにパースさせないこと。巡回・リトライ・ページネーション追跡だけやらせて、生HTMLを集める」
🔰「で、集まったHTMLをScraplingに渡す。なるほど」
Step 1: Scrapy で HTML を収集するだけの Spider
🧑💻「まずStep 1のコードを見て」
import scrapy
class HtmlCollectorSpider(scrapy.Spider):
"""HTMLだけ集めてパースしない Spider"""
name = "html_collector"
start_urls = ["http://localhost:5002/page/1?v=v1"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.collected_pages = [] # 生HTMLを溜めるリスト
def parse(self, response):
# HTMLだけ保存(パースはしない!)
self.collected_pages.append({
"url": response.url,
"html": response.text, # 生HTMLをそのまま保持
})
# ページネーションリンクを辿る(Scrapyの得意分野)
for href in response.css("a.page-link::attr(href)").getall():
yield response.follow(href, self.parse)
🧑💻「本当にHTMLを集めてるだけ。
yieldでアイテムを返してないのがポイント」
🔰「巡回はScrapyの得意分野だから任せると」
Step 2: Scrapling で高速パース
🧑💻「次にStep 2。集めたHTMLをScraplingでパースする」
from scrapling.parser import Selector
def parse_with_scrapling(pages: list[dict]) -> list[dict]:
"""収集したHTMLをScraplingでパース"""
all_products = []
for page_data in pages:
# 生HTMLからSelectorを生成(lxmlベースで高速)
selector = Selector(page_data["html"])
for card in selector.css(".product-card"):
product = {
"name": card.css("h2.product-name a").first.text.strip(),
"price": card.css("span.product-price").first.text.strip(),
"category": card.css("span.product-category").first.text.strip(),
}
all_products.append(product)
return all_products
🔰「ここにAdaptive入れることもできるんですか?」
🧑💻「もちろん。Step 2 のSelectorを呼び出すときにadaptive=Trueを足すだけだよ。こんな感じ」
from scrapling.core.storage import SQLiteStorageSystem
selector = Selector(
html,
url=url,
adaptive=True, # Adaptive機能を有効化
storage=SQLiteStorageSystem, # 指紋の保存先
storage_args={"storage_file": "storage.db", "url": url},
)
# v1の指紋があれば、v2のHTMLでも自動復元を試みる
cards = selector.css(".product-card", adaptive=True)
実行結果
python3 -m scraper.hybrid --version v2
| ステップ | 処理内容 | 時間 |
|---|---|---|
| Step 1 | Scrapy で5ページ巡回 | 0.80s |
| Step 2 | Scrapling で30商品パース | 0.007s |
| 合計 | 30商品 x 5ページ | 0.81s |
🔰「Step 2 が 0.007秒!?」
🧑💻「もうHTMLがメモリにあるからね。lxmlで5ページ分のHTMLをパースするだけだから一瞬で終わる」
🔰「待ってください、これ--version v2で実行してますよね? v2のHTMLなのにデータがちゃんと取れてる…しかも5ページ全部!」
🧑💻「そう、そこが本題。Scrapyの巡回力で5ページを安定して回りつつ、ScraplingのAdaptiveで壊れたHTMLからデータを引っこ抜く。さっきの比較3でScrapy単体だと0/6全滅だったのに、ハイブリッドなら30商品ちゃんと取れてる」
🔰「巡回に0.8秒かかってるのはScrapyのオーバーヘッドですか?」
🧑💻「Twisted——さっき話したイベントループ——のreactor起動 + 5ページのHTTP取得。localhostだからネットワーク遅延はほぼゼロだけど、フレームワークの初期化コストがある」
🔰「…でもこの構成って、Scrapy単体で全部やるのと速度変わらなくないですか?」
🧑💻「変わらない。このスケールだとね。ハイブリッドの真価は速度じゃない。構造が壊れたサイトでも巡回を止めずにデータを取りきれること。Scrapy単体なら0件で終わってた場面で、30商品持って帰れる」
🔰「それが"激つよ"の意味か…」
まとめ
🔰「全部見てきましたけど、結局どっちが強いんですか?」
🧑💻「"どっちが強いか"じゃなくて、"何が得意か"で考えるべき」
総合比較
| 比較軸 | Scrapy | Scrapling | ハイブリッド |
|---|---|---|---|
| パース速度 | 0.71s | 0.16s | 0.81s(巡回込み) |
| メモリ使用量 | 10.6MB | 1.7MB | — |
| ページ巡回 | ◎(フレームワーク管理) | △(手動ループ) | ◎(Scrapy担当) |
| 構造変更対応 | ✗(固定セレクタ) | ◎(Adaptive自動復元) | ◎(Scrapling担当) |
| Anti-Bot耐性 | △(ミドルウェア追加が必要) | ◎(StealthyFetcher内蔵) | ◎(Scraplingで補完) |
🧑💻「フローチャートにするとこんな感じ」
🔰「おお、これわかりやすい」
どれを使えばいい?
| やりたいこと | おすすめ |
|---|---|
| 数ページの定型スクレイピング | Scrapling 単体 — 軽量・高速・シンプル |
| 数百〜数千ページの大規模巡回 | Scrapy 単体 — 非同期巡回・リトライ・ジョブ管理 |
| 巡回 + 構造変更に自動対応したい | ハイブリッド — Scrapy巡回 + Scrapling Adaptive |
| Bot対策が厳しいサイト | Scrapling(StealthyFetcher) or Scrapy + scrapy-playwright |
| 安定して動いてるBS4スクレイパー | そのままでOK |
🔰「ScrapyとScraplingって競合じゃなくて補完関係なんですね」
🧑💻「そう。得意なことが違うから、組み合わせると最強」
🔰「じゃあ今BS4で安定して動いてるやつは、わざわざ変えなくていいんですか?」
🧑💻「BS4で安定してるなら無理に変える必要はないよ。動いてるものを壊すメリットはない」
🔰「ですよね」
🧑💻「でも新規プロジェクトなら選択肢として知っておくべき。Scraplingはパーサーとしても速いし、Adaptiveは他にない強み。Scrapyユーザーでもパース部分だけ差し替える選択肢が増える」
🔰「まずはScraplingのドキュメント読んでみます!」
🧑💻「いいね。リポジトリにデモ一式入ってるから、30分あれば一通り試せるよ。得意なことが違うなら、組み合わせればいい。それだけの話」
リポジトリ: https://github.com/matsubara457/scrapling-vs-scrapy
※環境構築手順は記事上部の「セットアップ手順」を参照
python3 demo_site/app.py & # Flask起動(port 5002)
python3 -m scraper.benchmark --runs 2 # 速度ベンチマーク
python3 -m scraper.adaptive_compare full # 構造変更対応比較
python3 -m scraper.hybrid --version v1 # ハイブリッド実行
Scrapling公式: https://github.com/D4Vinci/Scrapling
この記事の解説版もあります
対話なしのストレートな技術記事として、同じ内容をまとめています。
→ 解説版はこちら
前回の記事(BS4 vs Scrapling)
BeautifulSoup4からの乗り換えを検討している方はこちらもどうぞ。
→ 【Python】「サイト変わってデータ取れません」「自動で直るやつあるよ」〜次世代スクレイピングライブラリScrapling〜


