PHP cURL プロキシスクレイピング: CURLOPT_PROXY、認証とGuzzle
オンラインのほとんどのPHPプロキシスニペットは、2011年からの同じ3つのバグを未だに抱えています。ここでは、実運用に耐えるクリーンなCURLOPT_PROXY設定—認証、SOCKS5、curl_multiローテーション、Guzzle、タイムアウト—を紹介します。
PHP cURL プロキシコードには特有の問題があります。上位のスニペットは10年前のもので、多くが実際のバグを含んでいます。例えば、curl_exec()が二重に呼ばれ、リクエストが二度実行される、CURLOPT_SSL_VERIFYPEERが無効化されて「HTTPSを動作させる」、既にデフォルトの値に設定されているオプションなど。このガイドは現在の正しいバージョンです: 認証付きCURLOPT_PROXY、SOCKS5、curl_multiを使った並列ローテーション、Guzzleでの同様の設定、そしてワーカーが一晩中ハングしないようにするタイムアウト。
最小限で正しいPHP cURL プロキシの例
<?php
function fetch(string $url): string|false {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_PROXY => 'http://gate.quantumproxies.io:8000',
CURLOPT_PROXYUSERPWD => 'USER:PASS',
CURLOPT_RETURNTRANSFER => true, // return the body, don't echo it
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_CONNECTTIMEOUT => 10, // seconds to reach the proxy
CURLOPT_TIMEOUT => 30, // seconds for the whole transfer
CURLOPT_ENCODING => '', // accept gzip/br, decode automatically
]);
$body = curl_exec($ch);
if ($body === false) {
error_log('cURL error ' . curl_errno($ch) . ': ' . curl_error($ch));
}
curl_close($ch);
return $body;
}
echo fetch('https://httpbin.org/ip'); // -> the proxy exit IP
認証情報はCURLOPT_PROXYUSERPWDに示されるように設定するか、プロキシURLにインラインで埋め込むことができます(http://USER:PASS@host:port)。どちらも機能します。初心者が見落としがちなCURLOPT_RETURNTRANSFERも重要です。これがないと、curl_exec()はレスポンスをそのまま出力し、trueを返しますが、これはスクレイパーが望むものではありません。PHP 8ではハンドルがCurlHandleオブジェクトであり、リソースではありませんが、オプションの呼び出しは変わりません。
人々が模倣するオプション(と1つの本当のセキュリティミス)
CURLOPT_HTTPPROXYTUNNEL— HTTPSターゲットには不要です。cURLはターゲットがhttps://の場合、自動的にCONNECTトンネルを確立します。このオプションは、通常スクレイピングではほとんど必要とされないプレーンHTTPターゲットに対してのみトンネリングを強制します。CURLOPT_CUSTOMREQUEST => 'GET'— GETは既にデフォルトです。これを設定しても何も変わりませんが、エッジケースでリダイレクト処理を壊すことがあります。curl_exec()を二度呼び出す — クラシックなStack Overflowのバグ: リクエストが二度実行され、二度目の戻り値が最初のものを上書きします。ハンドルごとに一度だけ。CURLOPT_SSL_VERIFYPEER => false— 本当に危険なものです。証明書エラーを「修正」するために、すべてのリクエストで中間者攻撃を許可します。PHPマニュアルによると、実際の修正はphp.iniのcurl.cainfoを最新のcacert.pemバンドルに指し示すことです。

PHPでのSOCKS5プロキシ
<?php
// scheme style: socks5h = DNS resolved by the proxy (use for scraping)
curl_setopt($ch, CURLOPT_PROXY, 'socks5h://USER:PASS@gate.quantumproxies.io:1080');
// split style: same result via CURLOPT_PROXYTYPE
curl_setopt($ch, CURLOPT_PROXY, 'gate.quantumproxies.io:1080');
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'USER:PASS');
スキームがDNSの発生場所を決定します: socks5://(およびCURLPROXY_SOCKS5)はホスト名をサーバー上で解決し、すべてのターゲットドメインをローカルリゾルバに漏らします。socks5h://(CURLPROXY_SOCKS5_HOSTNAME)はプロキシ出口で解決します。後者を推奨します。QuantumProxiesはすべてのプランでHTTPとSOCKS5を提供しているため、プロトコルの切り替えは一行の変更で済み、新しいサブスクリプションは不要です。同じDNSルールがコマンドラインにも適用されます—このページのすべてのシェル相当のものについてはcurlプロキシレシピをご覧ください。
curl_multiを使ったローテーションと並列リクエスト
PHPにはasync/awaitはありませんが、並列スクレイピングには必要ありません—curl_multiは1つのプロセスで数十の転送を同時に実行します。すべてのハンドルを回転ゲートウェイに向け、各リクエストは自動的に異なる住宅IPから退出します。コード内でのプロキシリストの管理は不要です。
<?php
$urls = ['https://example.com/p/1', 'https://example.com/p/2', 'https://example.com/p/3'];
$mh = curl_multi_init();
$handles = [];
foreach ($urls as $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
// one gateway, a fresh residential exit per request
CURLOPT_PROXY => 'http://USER:PASS@rotating.quantumproxies.io:8000',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_ENCODING => '',
]);
curl_multi_add_handle($mh, $ch);
$handles[$url] = $ch;
}
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh); // wait for activity instead of spinning
} while ($running > 0);
$results = [];
foreach ($handles as $url => $ch) {
$results[$url] = curl_multi_getcontent($ch);
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
バッチは控えめに保ちましょう—8から16の並行ハンドルでほとんどのターゲットには十分です。ペースが生の速度よりも重要です: バッチ間に数百ミリ秒のusleep()を入れることで、ほとんどコストをかけずにリクエストレートを429を引き起こす閾値以下に保つことができます。各ハンドルごとにcurl_getinfo($ch, CURLINFO_HTTP_CODE)を確認し、失敗を再取得するのではなくリトライキューにルートしましょう。フローが複数のリクエストにまたがり、1つのアイデンティティを共有する必要がある場合(ログイン、カート、クッキーを使ったページネーション)、そのハンドルをスティッキーセッションに切り替え、ユーザー名にサフィックスを付けてください: USER-session-a1b2c3はローテーションせずにその期間中同じ出口IPを保持します。

ヘッダーとクッキー: 主張するブラウザのように見せる
デフォルトのcURLヘッダーを持つクリーンな出口IPでも、PHPが自らを発表しない限り、ボットと見なされます。3つのオプションでギャップの大部分を埋めます: 現在のブラウザ文字列を持つCURLOPT_USERAGENT、Accept-LanguageなどのためのCURLOPT_HTTPHEADER、そして同じファイルを指すクッキーペアCURLOPT_COOKIEJAR / CURLOPT_COOKIEFILEです。これにより、ブラウザが行うようにセッションクッキーがハンドル間で持続します。クッキーの持続性は人々が予想する以上に重要です: 多くのサイトは最初のヒットでトークンを設定し、それを返さないクライアントを静かに挑戦します。主張するアイデンティティを一貫させましょう—ChromeのUser-AgentにAccept-Languageがなく、基本的なヘッダーセットだけでは、それ自体がフィンガープリントであり、一貫性チェックは中級のアンチボットベンダーがPHPスクレイパーを2回目のリクエストで捕まえる方法です。
Guzzle: 同じプロキシ、より良い使い勝手
<?php
use GuzzleHttp\Client;
$client = new Client([
'proxy' => 'http://USER:PASS@gate.quantumproxies.io:8000',
'timeout' => 30,
'connect_timeout' => 10,
]);
$res = $client->get('https://httpbin.org/ip');
echo $res->getBody();
// per-request override, e.g. a sticky session for a login flow
$res = $client->get('https://example.com/account', [
'proxy' => 'http://USER-session-a1b2c3:PASS@gate.quantumproxies.io:8000',
]);
Guzzleは内部でcURLを駆動するため、上記のすべてが適用されます—ただし、読みやすくなります。1つの警告: Guzzleのデフォルトのtimeoutは0で、永遠に待機します。転送中に停止するプロキシ出口はPHP-FPMワーカーを無期限に固定するため、構築するすべてのクライアントにタイムアウトを設定してください。proxyオプションは、スキームごとのルーティングや内部ホストの除外が必要な場合にhttp、https、noキーを持つ配列も受け入れます。
PHP cURLが十分でなくなるとき
cURLはHTMLを取得しますが、JavaScriptを実行しません。レスポンスが空のdivを持つスケルトンページとして戻ってきた場合、データはクライアント側でレンダリングされます—空ページレスポンスの検出方法を示すガイドをご覧ください。また、強化されたターゲットでは、cURLのTLSハンドシェイク自体がIPがどれだけクリーンであってもあなたを露呈します(ブラウザが動作するがcURLは403の謎)。PHPはリクエストごとにヘッドレスブラウザを実行することは現実的ではないため、実用的な逃げ道はScraper APIです: 単一のエンドポイントへの1回のcURL呼び出しで、レンダリング、ブラウザフィンガープリント、ローテーション、解析がクリーンなMarkdown、JSON、またはHTMLとして戻ってきます。
よくある質問
PHP cURLでプロキシを使うにはどうすればいいですか?
CURLOPT_PROXYにプロキシURL全体を設定し、認証情報が必要な場合はCURLOPT_PROXYUSERPWDを追加し、常にCURLOPT_RETURNTRANSFERと明示的なタイムアウトを有効にしてください。それが必要なセット全体です—HTTPSターゲットは自動的にトンネルを通過し、その他はすべてオプションの調整です。
PHPでプロキシのユーザー名とパスワードを渡すにはどうすればいいですか?
curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'user:pass')を使うか、URLに埋め込む: http://user:pass@host:port。HTTP 407を受け取った場合、プロキシがその認証情報を拒否したということです—タイプミスを確認し、特殊文字をURLエンコードしてください。IPホワイトリスト認証を使用するプロバイダーは、サーバーのIPが認証されると認証情報は不要です。
PHP cURLはSOCKS5プロキシを使用できますか?
はい。socks5h:// URLをCURLOPT_PROXYに渡すか、CURLOPT_PROXYTYPEをCURLPROXY_SOCKS5_HOSTNAMEに設定します。ホスト名のバリアントをプレーンなsocks5よりも優先してください。そうしないと、すべてのターゲットドメインがローカルリゾルバを通じて漏れます。
CURLOPT_HTTPPROXYTUNNELとは何ですか?それは必要ですか?
これはcURLがプロキシを通じてCONNECTでトンネルを通過するように強制します。https://ターゲットの場合、cURLは自動的にトンネルを通過するため、このオプションはほとんどのスクレイピングコードで冗長です。プレーンHTTPターゲットにトンネルセマンティクスが必要な場合にのみ設定してください。
PHP cURLをプロキシ経由でタイムアウトを設定するにはどうすればいいですか?
両方を設定してください: CURLOPT_CONNECTTIMEOUTはプロキシへの接続とトンネルのオープンをカバーし、CURLOPT_TIMEOUTは転送全体を制限します。ミリ秒単位のバリアント(_MS)は、より厳しい予算に対応します。それらがないと、停止した出口がリクエストを保持し、PHP-FPMの下では、Webサーバーの制限がそれを終了するまでワーカー全体を保持します。
最初のスニペットをコピーし、チェックリストを心に留めておけば、PHPは完全に堅実なスクレイピング言語です: 並列処理にはcurl_multi、アイデンティティには回転ゲートウェイ、使いやすさにはGuzzle、そして対抗するページにはScraper API。