Scrapy は 2026 年になっても本番クロールの主力であり続けています — そして相変わらず、プロキシの設定で最も人を混乱させるフレームワークでもあります。なぜなら、プロキシを差し込める場所が 4 つあり、そのうち 3 つはほとんどのプロジェクトにとって誤りだからです。本ガイドが示すのは正しい 1 つ:リクエスト単位のルーティング、sticky セッション、ban 検出、そして妥当なリトライ挙動を備えた、小さなカスタム middleware です。
素の requests/httpx/aiohttp を使っているなら、Python でプロキシをローテーションする方法を参照してください。本稿は Scrapy 特化です。
ローテーティング住宅ゲートウェイの場合、最小限の構成はリクエストごとに 1 行 — middleware は不要です:
def start_requests(self):
for url in self.urls:
yield scrapy.Request(
url,
meta={"proxy": "http://USERNAME:[email protected]:913"},
)
Scrapy 組み込みの HttpProxyMiddleware は request.meta["proxy"] を読み取り、URL からの認証を処理します。ゲートウェイがあなたのために出口 IP をローテーションします。それだけで十分ならここで終わりです。本ガイドの残りは、制御が必要な場合のためのものです:sticky セッション、国別ルーティング、ban を意識したローテーション、そして並行数のチューニング。
これを middlewares.py に置いてください。ドメインごとに sticky セッションを割り当て、ban 時にローテーションし、どのセッションが何を取得したかをデバッグできるよう全リクエストにタグを付けます:
import random
import string
GATEWAY = "us.jibaoproxy.com:913"
USERNAME = "USERNAME" # move to settings.py / env in real projects
PASSWORD = "PASSWORD"
def _new_session(n=8):
return "".join(random.choices(string.ascii_lowercase + string.digits, k=n))
class JibaoProxyMiddleware:
"""Sticky session per domain; rotate session on ban."""
def __init__(self):
self.sessions = {} # domain -> session id
def _proxy_url(self, session_id):
user = f"{USERNAME}-session-{session_id}"
return f"http://{user}:{PASSWORD}@{GATEWAY}"
def process_request(self, request, spider):
domain = request.url.split("/")[2]
session = self.sessions.setdefault(domain, _new_session())
request.meta["proxy"] = self._proxy_url(session)
request.meta["proxy_session"] = session
def rotate(self, domain):
"""Call when a session is burned."""
self.sessions[domain] = _new_session()
そして、ban を検出して新しいセッションでリトライする、対になる downloader middleware:
from scrapy.downloadermiddlewares.retry import RetryMiddleware
from scrapy.utils.response import response_status_message
BAN_CODES = {403, 429}
BAN_MARKERS = (b"captcha", b"access denied", b"unusual traffic")
class BanAwareRetryMiddleware(RetryMiddleware):
def process_response(self, request, response, spider):
banned = (
response.status in BAN_CODES
or any(m in response.body[:2048].lower() for m in BAN_MARKERS)
)
if banned:
domain = request.url.split("/")[2]
proxy_mw = spider.crawler.engine.downloader.middleware.middlewares
for mw in proxy_mw:
if hasattr(mw, "rotate"):
mw.rotate(domain) # burn the session
reason = response_status_message(response.status)
return self._retry(request, reason, spider) or response
return super().process_response(request, response, spider)
両方を settings.py で配線します:
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.JibaoProxyMiddleware": 350,
"scrapy.downloadermiddlewares.retry.RetryMiddleware": None, # replace stock retry
"myproject.middlewares.BanAwareRetryMiddleware": 550,
}
RETRY_TIMES = 2
優先順位が重要です:プロキシ middleware は Scrapy の HttpProxyMiddleware(750)より前に実行されなければなりません。したがって 750 未満なら何でも機能します。350 にしておけば早期かつ予測可能な位置に保てます。
| クロールの種類 | モード | 実装 |
|---|---|---|
| ステートレスなページ収集 | Rotating | 素の username、ゲートウェイがリクエストごとにローテーション |
| ログイン+認証下のクロール | アカウントごとに sticky | -session-{account_id}、ログイン中は決してローテーションしない |
| ページネーションの多いリスト | ドメインごとに sticky、ban 時にローテーション | 上記の middleware |
| 地域別の価格設定 | Rotating +国の固定 | USERNAME-country-de 形式のパラメータ |
このトレードオフのより深い扱い:Sticky vs Rotating プロキシセッション。
Scrapy のデフォルトは、礼儀正しい単一 IP クロール向けに調整されています。ローテーティングプールの背後では、もっと強く攻められます — しかしドメインごとの上限は依然として重要です。なぜならターゲットが見るのは集約された挙動だからです:
# settings.py - sane starting point behind a residential pool
CONCURRENT_REQUESTS = 64
CONCURRENT_REQUESTS_PER_DOMAIN = 8 # what the target experiences
DOWNLOAD_DELAY = 0.25 # jitter applied per slot
RANDOMIZE_DOWNLOAD_DELAY = True # 0.5x-1.5x the delay
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 6.0
DOWNLOAD_TIMEOUT = 30
ROBOTSTXT_OBEY = True
CONCURRENT_REQUESTS_PER_DOMAIN を引き上げるのは、現在のレベルで数千リクエスト分の 403 率を観察してからにしてください。「どうせプロキシがローテーションするから」と 8 → 32 にするのは、人々がリトライで GB を焼き尽くす典型的なやり方です。
IMAGES_STORE パイプラインをデータセンタープロキシ経由または直接接続で取得するよう設定してください — メディアはあなたの GB の大半を占め、かつ保護されていることはまれです。HTTPCACHE_ENABLED = True にしておくと、プール経由で再取得する代わりにディスクからレスポンスをリプレイします。この設定 1 つで、プロジェクトの帯域コストはたいてい半減します。COMPRESSION_ENABLED = True(デフォルト)を維持してください — gzip された HTML は転送路上で 5–10 倍小さくなります。407 Proxy Authentication Required認証情報がプロキシに届いていません。meta["proxy"] の URL(http://user:pass@host:port)に入れてください — Scrapy がパースして Proxy-Authorization を設定してくれます。ヘッダーを手動で設定し、かつ URL 認証情報も使うと二重認証の奇妙な挙動が起きます。どちらか一方を選んでください。
TunnelError: Could not open CONNECT tunnelほぼ常に、ホスト/ポートのタイプミスか、そのポートで CONNECT を許可しないエンドポイント経由の HTTPS ターゲットです。まず Scrapy の外で curl -x を使って検証してください。
あなたの sticky セッションが歓迎される期間を超えたか、ドメインごとのレートが熱すぎます。上記の ban を意識した middleware は前者を処理します。後者には CONCURRENT_REQUESTS_PER_DOMAIN を下げてください。JA4 をチェックするターゲットなら、Scrapy の TLS スタックそのものが正体を暴いているかもしれません — なぜプロキシではそれが直らないのかは JA3/JA4 の解説を参照してください。
meta["proxy"] が構成のすべてです。新規ユーザーは登録時に500MBを獲得、さらに初回チャージにボーナスが付きます。期間限定オファーです。