Socks5Proxy

Scrapyプロキシ設定、meta proxyでリクエストごとに異なるIPを使用

このガイドではScrapyにSocks5ProxyのHTTPプロキシをmeta proxyで接続する方法、APIで取得したリストをリクエストごとにローテーションするダウンローダーミドルウェア、対象サイトに適した速度設定を説明します。

Scrapyはプロキシをどのように処理しますか?

ScrapyにはHttpProxyMiddlewareがデフォルトで有効です。リクエストのmeta辞書にproxyキーでアドレスを指定すると、そのリクエストが該当プロキシ経由で送出され、アドレス内のユーザー名とパスワードはミドルウェアがProxy-Authorizationヘッダーに変換して送信します。HTTPSアドレスはCONNECTトンネル経由です。

ScrapyのデフォルトダウンロードハンドラーはHTTPプロキシをサポートします。SOCKS5が必須の場合は別途ハンドラーが必要で、Scrapyでは同じIPのHTTPポートを使用することを推奨します。対象サイトから見えるIPは同じです。

例ではIP 198.51.100.23、HTTPポート8000、ユーザー名spx_user、パスワードspx_passを使用します。

pip install scrapy
scrapy startproject shop
cd shop

リクエストにプロキシを設定するにはどうすればいいですか?

start_requestsでRequestを作成する際にmetaにproxyを入れます。コールバックで次のページをリクエストする際も同じ方法で入れてください。同じセッションを続ける必要がある場合は、前の応答のmetaからproxy値をそのまま渡すと、同じIPが維持されます。

import scrapy

PROXY = "http://spx_user:spx_pass@198.51.100.23:8000"

class IpCheckSpider(scrapy.Spider):
    name = "ip_check"

    def start_requests(self):
        yield scrapy.Request("https://api.ipify.org?format=json", meta={"proxy": PROXY})

    def parse(self, response):
        self.logger.info("seen as %s", response.json()["ip"])

リクエストごとにIPをローテーションするにはどうすればいいですか?

ダウンローダーミドルウェアを作成し、すべてのリクエストに次のプロキシを設定すれば、スパイダーコードを変更せずにローテーションできます。以下のミドルウェアは開始時にAPIでリストを取得し、metaにproxyが既にある場合はそのままにして、セッション固定とローテーションを併用できるようにしています。

APIキーはsettings.pyに置くか、環境変数から読み込むようにしてください。順序は基本のHttpProxyMiddleware(750)より前の数字で登録する必要があります。

import itertools
import requests

class RotatingProxyMiddleware:
    def __init__(self, key):
        text = requests.get(
            "https://socks5proxy.io/api/v1/proxies/download",
            params={"format": "user:pass@ip:port", "protocol": "http"},
            headers={"Authorization": "Bearer " + key},
            timeout=30,
        ).text
        self.pool = itertools.cycle(["http://" + l.strip() for l in text.splitlines() if l.strip()])

    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler.settings.get("SPX_API_KEY"))

    def process_request(self, request, spider):
        if "proxy" not in request.meta:  # keep a proxy that was set on purpose (sticky sessions)
            request.meta["proxy"] = next(self.pool)

速度と再試行はどのように調整しますか?

CONCURRENT_REQUESTS_PER_DOMAINは同じドメインに同時に送信するリクエスト数です。プロキシIPが多いほどこの値を上げても、IP1つにかかる負担は変わりません。例えばIP500個で同時リクエスト50個を送信すると、IP1つは平均10回のリクエストに1回しか使用されません。

AUTOTHROTTLEを有効にすると、レスポンス速度に合わせてリクエスト間隔を自動的に調整します。429レスポンスが増えるとRETRY_HTTP_CODESで再試行しますが、再試行時にミドルウェアが次のプロキシを挿入するようにmetaのproxyを削除する方法もあります。

収集した結果が確認画面ではなく実際のページであるかを検査するステップをパイプラインに設けると、データ品質が向上します。必要な要素がない応答は、別のIPで再度リクエストするように設定できます。

  • IP数に合わせてドメインごとの同時リクエスト数調整
  • AUTOTHROTTLEでレスポンス速度に合わせた間隔
  • 429と5xxは別のIPで再試行
  • 応答内容検査で確認画面をフィルタリング

IP認証で接続すると何が変わりますか?

ダッシュボードにScrapyが実行されるサーバーのIPv4を登録すると、meta proxyにhttp://198.51.100.23:8000のようにIDとパスワードなしで設定できます。リストもformat=ip:portで取得可能です。複数サーバーでスパイダーを実行する場合は各サーバーのIPを全て登録してください。最大10個まで登録できます。

セッションが必要なリクエストを同じIPに紐付けるにはどうすればよいですか?

ログインやカートのようにクッキーが連続するフローでは同じIPを使用する必要があります。最初のリクエストにmeta proxyを直接設定し、コールバックで次のリクエストを作成する際にresponse.metaのproxy値をそのまま引き継ぐと、ローテーションミドルウェアが変更しません。Scrapyのcookiejar metaキーを併用すれば、クッキーもセッションごとに分離されます。

1セッションに1プロキシと1クッキーバンドルをペアリングすれば、複数セッションを同時実行しても混ざりません。

import scrapy

PROXIES = [
    "http://spx_user:spx_pass@198.51.100.23:8000",
    "http://spx_user:spx_pass@198.51.100.24:8000",
]

class SessionSpider(scrapy.Spider):
    name = "sessions"

    def start_requests(self):
        for i, proxy in enumerate(PROXIES):
            yield scrapy.Request("https://example.com/", meta={"proxy": proxy, "cookiejar": i}, dont_filter=True)

    def parse(self, response):
        # follow-up request keeps the same proxy and the same cookie jar
        yield response.follow("/account", meta={"proxy": response.meta["proxy"], "cookiejar": response.meta["cookiejar"]}, callback=self.parse_account)

    def parse_account(self, response):
        self.logger.info("status %s via %s", response.status, response.meta["proxy"])

失敗したプロキシを一時除外するには?

ミドルウェアのprocess_responseで403や429応答を受けた場合、そのプロキシを数分間休ませ、同じリクエストを別のプロキシで再送できます。リクエストをコピーしてmetaのproxyを削除し、dont_filterをオンにして返すと、次のプロキシが使用されます。

休憩中のプロキシリストと再使用時刻を辞書で管理すると簡単です。同じIPをすぐに再使用しないことで、対象サイトの制限が解除される時間を短縮できます。

Scrapyで帯域を節約するには何を設定しますか?

ScrapyはデフォルトでHTMLのみ受信するためブラウザツールより帯域を大幅に節約します。さらに圧縮応答を受信するHttpCompressionMiddlewareがデフォルトで有効なため、転送量がさらに削減されます。共有プランの月間帯域内で多くのページを収集できる理由です。

同じページを再度取得しないように、開発中にHTTPCACHE_ENABLEDをオンにしておけば、テストのたびに対象サイトとプロキシを再呼び出しする必要がありません。運用時には重複アドレスフィルタリング(デフォルトでオン)を維持し、本当に再取得が必要なリクエストにのみdont_filterを使用してください。

不要な下位ページに広がらないよう、リンクをたどる範囲を狭めることも重要です。収集範囲を明確に設定すると、時間と帯域幅、対象サイトの負担がすべて減ります。

よくある問題はどう解決しますか?

407レスポンスが表示されたら、アドレス内のユーザー名とパスワードを確認してください。パスワードに特殊文字がある場合は、パーセントエンコーディングして入力する必要があります。TunnelErrorはプロキシがCONNECTリクエストを受け取れなかった場合で、ポートがHTTPポートか確認してください。

全リクエストが同じIPから送信される場合、ミドルウェア順序番号がHttpProxyMiddlewareより後か、スパイダーでmeta proxyを直接設定している可能性があります。settings.pyの順序とスパイダーコードを確認してください。

Scrapyプロキシ設定、meta proxyと循環ミドルウェア | Socks5Proxy