法的注意: 本記事は公開データの収集のみを想定しています。スクレイピング前に robots.txt と各サイトの利用規約(ToS)を確認し、米国 Computer Fraud and Abuse Act(CFAA) や欧州 GDPR の適用範囲を理解した上で実装してください。可能であれば公式 API の利用を優先してください。
Crawlee for Python でのプロキシローテーションは、本番クローラーを構築する Python 開発者にとって避けて通れないテーマです。Cloudflare や DataDome で保護されたサイトをスクレイピングする場合、単一のデータセンタ IP では数リクエストでブロックされます。本記事では、Crawlee の ProxyConfiguration クラスと SessionPool を活用し、residential proxy を idiomatic に統合する方法を、実行可能なコード例とともに解説します。
Crawlee for Python でのプロキシローテーション: アーキテクチャ概要
Crawlee for Python は、複数のクローラー型が統一されたインフラストラクチャの上に構築されるアーキテクチャを採用しています。BeautifulSoupCrawler は軽量な HTTP リクエスト + HTML パースに最適で、PlaywrightCrawler は JavaScript レンダリングが必要なサイト向けです。両者とも以下の共通基盤を利用します。
- RequestQueue — URL の重複排除と再訪問管理を行う統一キュー。
- AutoscaledPool — CPU とメモリ使用率に基づいて同時実行数を自動調整するスケジューラ。
- SessionPool — Cookie、ブラウザフィンガープリント、プロキシ IP を 1 つのセッションに紐付ける管理機構。
- ProxyConfiguration — プロキシ URL の生成とローテーション戦略をカプセル化するクラス。
プロキシローテーションの鍵は SessionPool と ProxyConfiguration の連携にあります。Crawlee は新しいセッションを作成する際、proxy_configuration.new_url(session_id=session.id) を呼び出してプロキシ URL を取得します。この URL がそのセッションに固定されるため、Cookie と IP アドレスの整合性が保たれ、アンチボットシステムが「同じユーザーが異なる IP からアクセスしている」と検知するのを防ぎます。
SessionPool がプロキシに与える影響
SessionPool は各セッションにプロキシ URL を紐付けるだけでなく、セッションの寿命管理も行います。セッションが retire() されると、Crawlee は新しいセッションを作成し、ProxyConfiguration から新しいプロキシ URL を取得します。これがプロキシローテーションの基本的なメカニズムです。
重要なのは、プロキシローテーションは「リクエストごとに IP を変える」ことだけではないという点です。むしろ、セッション単位で IP を固定し、ブロックされた時だけ新しい IP に切り替えるアプローチが、アンチボット対策においてはるかに効果的です。これにより、ログイン後のセッション維持や、段階的なページ遷移が必要なスクレイピングが可能になります。
ProxyConfiguration の idiomatic な使い方
Crawlee の ProxyConfiguration は、プロキシ URL のリストを受け取り、new_url(session_id=...) メソッドでセッションごとに URL を割り当てます。ProxyHat の場合、ユーザー名に session フラグを埋め込むことで、セッションごとに異なる住宅 IP を固定できます。
from crawlee.proxy_configuration import ProxyConfiguration
PROXYHAT_GATEWAY = 'gate.proxyhat.com'
PROXYHAT_PORT = 8080
PROXYHAT_USER = 'YOUR_USERNAME'
PROXYHAT_PASS = 'YOUR_PASSWORD'
def build_proxy_url(session_id: str, country: str = 'US') -> str:
"""ProxyHat のユーザー名に geo と session フラグを埋め込む"""
username = f'{PROXYHAT_USER}-country-{country}-session-{session_id}'
return f'http://{username}:{PROXYHAT_PASS}@{PROXYHAT_GATEWAY}:{PROXYHAT_PORT}'
# セッション固定のプロキシ URL を 20 個生成
proxy_urls = [build_proxy_url(f'sess-{i:03d}') for i in range(20)]
proxy_configuration = ProxyConfiguration(proxy_urls=proxy_urls)
この例では、sess-000 から sess-019 までの 20 個のセッション固定 URL を生成しています。Crawlee の SessionPool が各セッションにこれらの URL を割り当て、同じセッション ID には常に同じプロキシ URL が返されます。これが proxy_configuration.new_url(session_id=...) の役割です。
ラウンドロビン vs セッション固定
ProxyConfiguration は 2 つのローテーションモードをサポートします。
- ラウンドロビン —
session_idを指定せずnew_url()を呼ぶと、リスト内の URL が順番に返されます。各リクエストで IP が変わるため、単純なデータ収集に向いています。 - セッション固定 —
new_url(session_id='abc123')のようにsession_idを渡すと、同じ ID には常に同じ URL が返されます。ログイン状態の維持や段階的なクロールに必須です。
実務では、セッション固定モードをデフォルトとし、ブロック時に session.retire() で新しいセッション(=新しい IP)に切り替えるパターンが最も効果的です。ProxyHat の 公式ドキュメントでサポートされている session フラグと組み合わせることで、Crawlee のセッション管理と ProxyHat の IP 固定がシームレスに連携します。
Residential vs Datacenter: なぜ住宅プロキシが勝つのか
Cloudflare や DataDome などのアンチボットシステムは、IP レピュテーションデータベースを参照してリクエストを評価します。データセンタ IP(AWS、GCP、DigitalOcean などの ASN)は即座にフラグ付けされ、CAPTCHA やブロックページが返されます。一方、residential proxy は実際の ISP に割り当てられた IP アドレスを使用するため、通常のユーザートラフィックと区別が困難です。
| プロキシタイプ | 検出耐性 | 平均レイテンシ | 価格帯 | 適用シナリオ |
|---|---|---|---|---|
| Residential | 高 | 200〜800ms | 中〜高 | Cloudflare / DataDome 対策、SERP スクレイピング |
| Datacenter | 低 | 50〜100ms | 低 | API スクレイピング、制限の緩いサイト |
| Mobile | 最高 | 500〜2000ms | 高 | 厳格なアンチボット対策、ソーシャルメディア |
データセンタプロキシはレイテンシが 50ms 程度と高速ですが、Cloudflare が管理する IP レピュテーションデータベースに登録されている場合、最初のリクエストでブロックされる確率が 50% を超えることもあります。Residential プロキシはレイテンシが 200〜800ms と遅いものの、ブロック率を大幅に下げ、結果的にスループットが向上します。
ティアードフォールバック戦略
本番環境では、単一のプロキシタイプに依存するのではなく、ティアード(階層型)フォールバック戦略を推奨します。Residential プロキシをプライマリとし、ブロックが頻発する場合は Mobile プロキシにフォールバックします。Crawlee では、ProxyConfiguration の URL リストに両方のタイプを混在させることで、SessionPool が自動的に利用可能な URL を割り当てます。
# Residential プロキシ(プライマリ)
residential_proxies = [
f'http://{PROXYHAT_USER}-country-US-session-res-{i:03d}:{PROXYHAT_PASS}'
f'@gate.proxyhat.com:8080'
for i in range(50)
]
# Mobile プロキシ(フォールバック)
mobile_proxies = [
f'http://{PROXYHAT_USER}-country-US-session-mob-{i:03d}:{PROXYHAT_PASS}'
f'@gate.proxyhat.com:8080'
for i in range(10)
]
proxy_configuration = ProxyConfiguration(
proxy_urls=residential_proxies + mobile_proxies
)
この構成では、60 個のプロキシ URL が SessionPool に渡されます。Crawlee はセッション作成時にラウンドロビンで URL を割り当てるため、大部分のセッションが residential プロキシを使用し、一部が mobile プロキシを使用します。ブロックされたセッションが retire() されると、次のセッションが別の URL を取得し、自動的にフェイルオーバーが行われます。
実践例: BeautifulSoupCrawler と ProxyHat の統合
以下は、ProxyHat の residential proxy を統合した BeautifulSoupCrawler の完全な実行例です。セッションごとに固有の住宅 IP を割り当て、ブロック検出時にセッションを破棄する仕組みを実装しています。
import asyncio
from crawlee.beautifulsoup_crawler import (
BeautifulSoupCrawler,
BeautifulSoupCrawlingContext,
)
from crawlee.proxy_configuration import ProxyConfiguration
PROXYHAT_GATEWAY = 'gate.proxyhat.com'
PROXYHAT_PORT = 8080
PROXYHAT_USER = 'YOUR_USERNAME'
PROXYHAT_PASS = 'YOUR_PASSWORD'
def build_proxy_url(session_id: str, country: str = 'US') -> str:
"""ProxyHat のユーザー名に geo + session フラグを埋め込む"""
username = f'{PROXYHAT_USER}-country-{country}-session-{session_id}'
return f'http://{username}:{PROXYHAT_PASS}@{PROXYHAT_GATEWAY}:{PROXYHAT_PORT}'
# 50 セッション分の residential proxy URL を生成
proxy_urls = [build_proxy_url(f'sess-{i:04d}') for i in range(50)]
proxy_configuration = ProxyConfiguration(proxy_urls=proxy_urls)
async def main():
async with BeautifulSoupCrawler(
proxy_configuration=proxy_configuration,
max_request_retries=3,
max_concurrency=10,
max_requests_per_crawl=5000,
) as crawler:
@crawler.router.default_handler
async def handler(context: BeautifulSoupCrawlingContext) -> None:
# --- ブロック検出 ---
status = context.http_response.status_code
if status in (403, 429):
context.log.warning(
f'Blocked ({status}) on {context.request.url}, '
f'retiring session {context.session.id}'
)
context.session.retire()
raise RuntimeError(f'Blocked with status {status}')
# --- データ抽出 ---
title = context.soup.find('title')
if title:
await context.push_data({
'url': context.request.url,
'title': title.text.strip(),
'session_id': context.session.id,
'proxy_used': str(context.proxy_info.url).split('@')[-1]
if context.proxy_info else 'none',
})
# --- リンクのエンキュー ---
await context.enqueue_links(selector='a[href]')
await crawler.run(['https://example.com'])
if __name__ == '__main__':
asyncio.run(main())
このコードの重要ポイントを解説します。
build_proxy_url— ProxyHat のユーザー名にcountry-US-session-sess-0000形式でフラグを埋め込みます。各セッション ID に対して ProxyHat は異なる住宅 IP を割り当て、同じセッション ID には同じ IP を返し続けます。proxy_configuration— 50 個のプロキシ URL をProxyConfigurationに渡します。Crawlee のSessionPoolがセッション作成時にnew_url(session_id=...)を呼び出し、URL をセッションに紐付けます。- ブロック検出 — HTTP ステータス 403(Cloudflare/DataDome のブロック)または 429(レートリミット)を検出した場合、
session.retire()を呼び出してセッションを破棄します。Crawlee はmax_request_retries=3の範囲内で新しいセッションを使ってリトライします。 max_concurrency=10— 同時実行数を 10 に制限します。AutoscaledPool が CPU とメモリ使用率を監視し、必要に応じてこの値を下回るように自動調整します。
ProxyHat SDK で動的にユーザー名を生成する
セッション数が実行時に決まる場合、ProxyHat の接続パラメータを動的に生成するヘルパー関数を実装できます。ProxyHat はユーザー名ベースのフラグシステムを採用しているため、追加の SDK や API 呼び出しなしでセッションごとの IP 固定が可能です。
import uuid
class ProxyHatManager:
"""Crawlee のセッション ID を ProxyHat のユーザー名フラグに変換"""
def __init__(
self,
username: str,
password: str,
gateway: str = 'gate.proxyhat.com',
port: int = 8080,
default_country: str = 'US',
):
self.username = username
self.password = password
self.gateway = gateway
self.port = port
self.default_country = default_country
def url_for_session(
self,
session_id: str | None = None,
country: str | None = None,
city: str | None = None,
) -> str:
"""セッション ID に基づいてプロキシ URL を生成"""
sid = session_id or uuid.uuid4().hex[:12]
flags = [self.username, f'country-{country or self.default_country}']
if city:
flags.append(f'city-{city.lower()}')
flags.append(f'session-{sid}')
username = '-'.join(flags)
return f'http://{username}:{self.password}@{self.gateway}:{self.port}'
def url_list(self, count: int, country: str | None = None) -> list[str]:
"""指定数のプロキシ URL リストを生成"""
return [
self.url_for_session(session_id=f'sess-{i:04d}', country=country)
for i in range(count)
]
# 使用例
manager = ProxyHatManager(
username='YOUR_USERNAME',
password='YOUR_PASSWORD',
)
# US の住宅 IP を 50 セッション分生成
proxy_urls = manager.url_list(count=50, country='US')
# 都市レベルのターゲティングも可能
berlin_proxy = manager.url_for_session(
session_id='de-session-001',
country='DE',
city='berlin',
)
# => http://YOUR_USERNAME-country-DE-city-berlin-session-de-session-001:YOUR_PASSWORD@gate.proxyhat.com:8080
このマネージャクラスを Crawlee の ProxyConfiguration と組み合わせることで、実行時にセッション数やジオターゲティングを柔軟に制御できます。ProxyHat は 100 以上のロケーションで国・都市レベルのターゲティングをサポートしており、SERP スクレイピングなどで地域ごとの検索結果を取得する際に有用です。
本番運用パターン: session.retire() とエラーハンドリング
ブロック時のセッション破棄
本番環境で最も重要なパターンは、ブロックを検出したら即座にセッションを破棄し、新しい IP でリトライすることです。Crawlee では session.retire() を呼び出すだけで、SessionPool が自動的に新しいセッションを作成し、ProxyConfiguration から新しいプロキシ URL を取得します。
@crawler.router.default_handler
async def handler(context: BeautifulSoupCrawlingContext) -> None:
status = context.http_response.status_code
# --- アンチボットブロックの検出 ---
if status == 403:
# Cloudflare のチャレンジページか確認
if 'cf-challenge' in context.http_response.headers.get('server', ''):
context.log.warning(
f'Cloudflare block on {context.request.url}, '
f'retiring session {context.session.id}'
)
context.session.retire()
raise RuntimeError('Cloudflare challenge detected')
if status == 429:
context.log.warning(
f'Rate limited on {context.request.url}, '
f'cooling down session {context.session.id}'
)
context.session.retire()
raise RuntimeError('Rate limited')
# --- DataDome の検出 ---
if 'datadome' in context.http_response.headers.get('set-cookie', '').lower():
context.log.warning(
f'DataDome challenge on {context.request.url}, '
f'retiring session {context.session.id}'
)
context.session.retire()
raise RuntimeError('DataDome challenge detected')
# --- 正常時の処理 ---
title = context.soup.find('title')
if title:
await context.push_data({
'url': context.request.url,
'title': title.text.strip(),
})
await context.enqueue_links(selector='a[href]')
max_request_retries=3 を設定している場合、Crawlee は最大 3 回までリトライします。各リトライで新しいセッション(=新しい住宅 IP)が割り当てられるため、3 回のリトライで 3 つの異なる IP からアクセスを試行することになります。99.9% の成功率を目指す場合、3 回のリトライで十分なケースが多いですが、より厳格なターゲットでは max_request_retries=5 に引き上げることも検討してください。
同時実行数と AutoscaledPool
Crawlee の AutoscaledPool は、CPU とメモリ使用率を監視し、システムリソースに応じて同時実行数を自動調整します。max_concurrency で上限を設定し、min_concurrency で下限を設定できます。
from crawlee.beautifulsoup_crawler import BeautifulSoupCrawler
crawler = BeautifulSoupCrawler(
proxy_configuration=proxy_configuration,
max_request_retries=3,
max_concurrency=20, # 最大 20 並列リクエスト
max_requests_per_crawl=10000,
request_handler_timeout=60, # 1 リクエスト 60 秒でタイムアウト
)
50 セッションのプロキシプールに対して max_concurrency=20 を設定すると、最大 20 のセッションが同時にアクティブになります。残りの 30 セッションは待機状態となり、アクティブなセッションがブロックされて retire() されると、待機中のセッションが即座に利用可能になります。この設計により、100 並列リクエストを送信してもプロキシプールが枯渇しません。
実践のヒント:
max_concurrencyはプロキシプールサイズの 30〜50% に設定するのが目安です。同時実行数がプロキシ数を超えると、セッションの再利用が頻発し、同じ IP から短時間に多数のリクエストが送信されてブロックリスクが高まります。
コンテナ化とスケーリング
本番環境では、Crawlee クローラーを Docker コンテナで実行し、Kubernetes などのオーケストレーターで水平スケーリングするのが一般的です。各コンテナに独立した ProxyConfiguration と SessionPool を持たせることで、コンテナ間でプロキシ IP が重複することを防げます。セッション ID にコンテナのホスト名を含めると、デバッグ時のトレーサビリティも向上します。
import socket
container_id = socket.gethostname()[:8]
proxy_urls = [
build_proxy_url(f'{container_id}-sess-{i:04d}')
for i in range(50)
]
ブラウザクロールを使うべきでないケース
PlaywrightCrawler は JavaScript レンダリングが必要なサイトに強力ですが、常に最適な選択とは限りません。以下の基準で判断してください。
- BeautifulSoupCrawler を使うべき: 対象ページの HTML がサーバーサイドでレンダリングされており、JavaScript 実行が不要な場合。HTTP リクエストのコストが 1/10 以下になり、同時実行数を大幅に増やせます。
- PlaywrightCrawler を使うべき: ページのコンテンツが JavaScript で動的に生成される場合、またはログイン後の SPA ナビゲーションが必要な場合。ただし、リソース消費が大きいため、
max_concurrencyは 5〜10 程度に抑えるべきです。
多くの eコマースサイトや SERP は、サーバーサイドレンダリングを採用しているため BeautifulSoupCrawler で十分です。ブラウザを起動するオーバーヘッド(メモリ 200〜500MB / ページ)を回避でき、同じサーバーで 10 倍以上の並列処理が可能になります。
倫理と法的注意事項
スクレイピングは強力なツールですが、適切に使用しなければ法的リスクや倫理的問題を引き起こします。Robots Exclusion Standard に従い、対象サイトの robots.txt を尊重してください。
- 公開データのみを収集: ログイン背後のデータや、有料コンテンツのスクレイピングは避けてください。
- レートリミットの尊重: 対象サイトに過度な負荷をかけないよう、適切な遅延と同時実行数の制限を設定してください。
- 公式 API を優先: 多くのプラットフォーム(Google、Amazon、Twitter など)は公式 API を提供しており、利用規約の範囲内でデータアクセスが可能です。
- CFAA と GDPR: 米国の CFAA は「認可を超えるアクセス」を犯罪とする可能性があり、GDPR は EU 識別子の収集・処理に厳格な要件を課します。個人情報を含むデータの収集は特に注意が必要です。
ProxyHat は 柔軟な価格プランで residential、mobile、datacenter プロキシを提供しており、Web スクレイピングやSERP トラッキングのユースケースに対応します。詳細な接続パラメータについてはProxyHat ドキュメントを参照してください。
Key Takeaways
- セッション固定ローテーションをデフォルトに:
ProxyConfigurationとSessionPoolを組み合わせ、セッションごとに住宅 IP を固定する。ブロック時のみsession.retire()で新しい IP に切り替える。 - Residential プロキシがアンチボット対策の鍵: Cloudflare や DataDome はデータセンタ IP を即座にブロックする。Residential プロキシは 200〜800ms のレイテンシ代价でブロック率を大幅に下げる。
- ティアードフォールバックで堅牢性を向上: Residential をプライマリとし、Mobile プロキシをフォールバックに設定。1 つの
ProxyConfigurationに混在させることで Crawlee が自動的にフェイルオーバーする。 - 同時実行数はプロキシプールの 30〜50%:
max_concurrencyをプロキシ URL 数の半分以下に設定し、セッション枯渇を防ぐ。 - BeautifulSoupCrawler を優先: JavaScript レンダリングが不要な場合は HTTP ベースのクローラーを使い、10 倍以上の並列処理能力を確保する。
- 倫理と法令を遵守: 公開データのみを収集し、
robots.txtと利用規約を尊重する。公式 API が利用可能な場合はそちらを優先する。
FAQ
Crawlee for Python でのプロキシローテーションとは何ですか?
Crawlee for Python でのプロキシローテーションとは、ProxyConfiguration クラスと SessionPool を連携させ、クローラーのセッションごとに異なるプロキシ IP を割り当てる仕組みです。proxy_configuration.new_url(session_id=...) を通じて、同じセッションには同じプロキシ URL が返され、ブロック時に session.retire() を呼ぶことで新しい IP に自動的に切り替わります。
なぜプロキシユーザーにとって Crawlee for Python のプロキシローテーションが重要なのですか?
Cloudflare や DataDome などのアンチボットシステムは、単一 IP からの大量リクエストを検出すると即座にブロックします。プロキシローテーションを適切に実装することで、各セッションが独立した住宅 IP を持ち、通常ユーザーと同様のトラフィックパターンを作れるため、ブロック率を大幅に下げつつデータ収集の継続性を確保できます。
Crawlee for Python でのプロキシローテーションに最適なプロキシタイプはどれですか?
Residential プロキシが最適です。実際の ISP に割り当てられた IP アドレスを使用するため、Cloudflare や DataDome の IP レピュテーションチェックを通過しやすくなります。データセンタプロキシは高速ですが検出されやすく、Mobile プロキシは検出耐性が最も高いもののレイテンシが 500〜2000ms と遅いため、Residential をプライマリとし Mobile をフォールバックとする構成が推奨されます。
Crawlee for Python でプロキシローテーションを実装する際、ブロックを回避するにはどうすればよいですか?
HTTP ステータス 403 や 429 を検出したら即座に session.retire() を呼び出してセッションを破棄し、Crawlee に新しいセッションとプロキシ IP でリトライさせてください。また、DataDome の Cookie ヘッダーをチェックするロジックも追加し、max_request_retries=3 以上を設定することで、複数の異なる IP からリトライできます。同時実行数をプロキシプールサイズの 30〜50% に抑えることも重要です。






