ほぼすべてのスクレイピングフレームワークでプロキシを設定できる。しかしプロキシを管理してくれるものとなると、話は変わる。ローテーションさせたり、ブロックされたものを引退させたり、1つのIPにセッションを紐付けたりすることだ。レジデンシャルプロキシを使う場合、フレームワークが担う部分とゲートウェイが担う部分を把握しておくと、つなぎ合わせのコードを大幅に減らせる。
このガイドでは、よく使われる6つのツールを3つの観点から比較する。プロキシの設定方法、ツール自体がプロキシをローテーションするか、そしてセッションを1つのプロキシに紐付けるかどうかだ。そのうえで、ローテーティングゲートウェイの背後でそれぞれに必要となる、ごくわずかなコードを示す。
結論を先に
| ツール | 言語 | プロキシの設定方法 | 自動でローテーションするか | セッションをプロキシに紐付けるか |
|---|---|---|---|---|
| Crawlee | Node.js | ProxyConfiguration | する: ラウンドロビン、カスタム関数、ティア | する、セッションプールを通じて |
| Scrapy | Python | request.meta["proxy"] または環境変数 | しない、独自のミドルウェアが必要 | しない |
| Colly | Go | SetProxy または SetProxyFunc | する: ラウンドロビン式スイッチャー | しない |
| Playwright | Node.js、Python、Java、.NET | 起動時またはブラウザコンテキストごと | しない | コンテキストごと |
| Puppeteer | Node.js | 起動フラグまたはブラウザコンテキストごと | しない | コンテキストごと |
| Selenium | 多数 | ブラウザ全体のオプション | しない | しない |
Crawleeは、プロキシを単なる設定項目ではなく管理対象として扱う唯一のツールだ。ブラウザ系のツールはコンテキストごとの分離を提供しており、これはセッションを構成するための基礎になるが、ローテーションのロジック自体は自分で用意する必要がある。
Crawlee: プロキシを管理対象のリソースとして扱う
CrawleeのProxyConfigurationはプロキシURLのリストを受け取り、「ラウンドロビン方式で」順に切り替えていく。リストの代わりに、リクエストごとにプロキシを選ぶ関数を渡すこともできる。3つ目のモードであるティア付きプロキシは、最も安価なティア(多くの場合プロキシなし)から開始し、サイトがクローラーをブロックするとティアを上げ、下位のティアで再び機能するようになれば元に戻す。
最も重要なのはセッションの扱いだ。クローラーがセッションプールを使って動作する場合、各セッションは同じプロキシを保持し続ける。ドキュメントの言葉を借りれば、以後の呼び出しは「同じsessionIdであれば常に同じproxyUrlを返す」。Crawleeがセッションをブロックされたと判断すると、そのセッションを引退させ、次のセッションには別のプロキシが割り当てられる。クッキーはセッションに追従するため、IPとクッキーは一体のまま保たれる。
Scrapy: リクエストごとに1つのプロキシ、ローテーションは自分で行う
ScrapyのHttpProxyMiddlewareはrequest.meta["proxy"]からプロキシを読み取り、設定がなければhttp_proxyとhttps_proxyの環境変数にフォールバックする。認証情報はhttp://username:password@host:portのようにURLに含めることができる。これがすべてで、リクエストごとに異なるプロキシを選んだり、BANに気づいたり、死んだプロキシを休ませたりする処理は、自分自身で行うか、scrapy-rotating-proxiesのような拡張機能に任せる必要がある。この拡張機能はプロキシのリストを読み込み、デフォルトでは非200応答や空の応答をBANとして扱う。
Scrapy統合ガイドでは基本的なセットアップを扱っており、Scrapyでのレジデンシャルプロキシのローテーションの記事ではミドルウェアについてさらに踏み込んで解説している。
Colly: シンプルなラウンドロビン方式のスイッチャー
Go言語のフレームワークであるCollyには、1つのプロキシを設定するSetProxyと、リクエストごとに選択する関数を渡すSetProxyFuncがある。そのproxyパッケージにはRoundRobinProxySwitcherが含まれており、「リクエストごとにProxyURLをローテーションする」もので、http、https、socks5のURLに対応している。セッションという概念は存在せず、2つのリクエストで同じIPを共有させたい場合は、自分の関数の中でそれを決める必要がある。
PlaywrightとPuppeteer: コンテキストごとの分離
ブラウザ自動化ツールは何もローテーションしないが、ブラウザコンテキストごとに独自のプロキシを使わせることができ、これが1つのブラウザの中で多数の独立したセッションを実行する方法となる。
- Playwrightは
server、username、passwordを持つproxyオプションを受け取り、ブラウザ全体には起動時に、1つのコンテキストにはnewContext()で設定できる。完全なセットアップについてはPlaywright統合ガイドを参照してほしい。 - Puppeteerは
--proxy-server起動フラグでプロキシを設定するか、createBrowserContext({ proxyServer })でコンテキストごとに設定する。どちらも認証情報は受け取れないため、プロキシのログイン要求にはpage.authenticate()で応答する。
各コンテキストに専用のセッションを持たせれば、コンテキストごとに1つのIPと1つのクッキージャーが得られ、これはログイン状態を要するフローに必要なものだ。
Selenium: ブラウザ全体への設定で、パスワード周りがやや煩雑
Seleniumはブラウザのオプションを通じてプロキシを設定するため、ブラウザ全体に適用される。Chromeの場合、通常は--proxy-serverスイッチを使うが、ここにはユーザー名とパスワードを入れる場所がない。よくある回避策は、ログイン要求に応答する小さなブラウザ拡張機能を使うか、パスワードなしのローカル転送用プロキシを立て、そこから認証情報付きでプロバイダーへトラフィックを渡す方法だ。かつて人気のあった解決策であるSelenium Wireは2024年1月にアーカイブされ、現在はメンテナンスされていない。
ローテーティングゲートウェイがもたらす変化
フレームワークのローテーション機能は、プロキシのリスト、たとえば100組のIP:ポアのペアを順に使い、テストし、引退させるという前提で設計されている。ローテーティングレジデンシャルゲートウェイの仕組みはこれとは異なる。1つのホストとポートに接続し、出口IPはゲートウェイ側が選ぶ。これにより、作業の大半がコードの外に移る。
- ローテーションはゲートウェイ側で行われるため、1つのプロキシURLで十分になる。1件だけのラウンドロビンリストは、単なる設定にすぎない。
- セッションはユーザー名の中でリクエストする。Shifterでは、出口IPを維持するために
sid-<id>を、保持時間を指定するためにttl-<seconds>を追加する。詳しくはスティッキープロキシとローテーティングプロキシの違いを参照してほしい。 - ロケーションもユーザー名の一部として指定できるため、クローラーは2本目のプロキシリストを用意しなくても、リクエストごとに国や都市を指定できる。
したがって、フレームワーク側の仕事は、フレームワーク独自のセッションの概念をプロキシのユーザー名内のセッションIDに対応づける、という1点に絞られる。
実装してみる
Crawlee。 Crawleeのセッションidからユーザー名を組み立てる関数を渡す。セッションIDにはアンダースコアなどの文字が含まれうるため、英数字だけを残すようにする。
import { CheerioCrawler, ProxyConfiguration } from 'crawlee';
const proxyConfiguration = new ProxyConfiguration({
newUrlFunction: (sessionId) => {
const sid = sessionId
? `-sid-${String(sessionId).replace(/[^a-zA-Z0-9]/g, '')}-ttl-600`
: '';
return `http://customer-USERNAME${sid}:PASSWORD@p.shifter.io:443`;
},
});
const crawler = new CheerioCrawler({
proxyConfiguration,
useSessionPool: true,
persistCookiesPerSession: true,
async requestHandler({ request, $ }) {
console.log(request.url, $('title').text());
},
});
await crawler.run(['https://example.com/']);
Crawleeがブロックされたセッションを引退させると、次のセッションIDは自動的に新しいスティッキーIPを得る。
Scrapy。 小さなダウンローダーミドルウェアを使い、Scrapy自身のプロキシミドルウェアが動く前にプロキシを設定する。metaにsessionの値を持つリクエストは1つのIPを共有し、それ以外はローテーションする。
# middlewares.py
class GatewayProxyMiddleware:
def process_request(self, request, spider):
sid = request.meta.get("session")
user = "customer-USERNAME"
if sid:
user += f"-sid-{sid}-ttl-600"
request.meta["proxy"] = f"http://{user}:PASSWORD@p.shifter.io:443"
# settings.py
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.GatewayProxyMiddleware": 350,
}
Playwright。 セッションごとに1つのコンテキストを使う。
const context = await browser.newContext({
proxy: {
server: 'http://p.shifter.io:443',
username: 'customer-USERNAME-sid-cart1-ttl-600',
password: 'PASSWORD',
},
});
どれを選ぶか
フレームワークはプロキシ機能ではなく、対象のサイトとチームに合わせて選ぶべきだ。Crawleeは、ブロック対応、セッション、プロキシのティアをNode.js上ですべて任せたい場合に最も強力な選択肢となる。Scrapyは、Python上で大量のシンプルなHTMLページをクロールする最速の手段であり続けており、プロキシ周りの不足分は10行程度のミドルウェアで補える。PlaywrightまたはPuppeteerは、ページが実際のブラウザを必要とする場合に適したツールであり、コンテキストがクリーンなセッションを提供してくれる。Seleniumも機能するが、認証情報まわりの手間が増える。
ローテーティングゲートウェイの背後では、この6つすべてが同じパターンに帰着する。1つのプロキシURLと、リクエストがIPを共有すべき箇所でのユーザー名内のセッションIDだ。これを本番のクローラーに組み込む方法については、Webスクレイピングのユースケースも参照してほしい。
出典および参考資料
- Crawlee、プロキシ管理ガイド: crawlee.dev
- Scrapy、ダウンローダーミドルウェアのリファレンス(HttpProxyMiddleware): docs.scrapy.org
- scrapy-rotating-proxies: github.com
- Colly、proxyパッケージ: pkg.go.dev
- Playwright、ネットワークとHTTPプロキシ: playwright.dev
- Puppeteer、BrowserContextOptions: pptr.dev
- Selenium Wireリポジトリ(2024年1月3日にアーカイブ): github.com