PHP cURL Proxy Scraping: CURLOPT_PROXY, Auth и Guzzle
Большинство PHP-примеров с прокси в интернете до сих пор содержат те же три ошибки с 2011 года. Вот чистая настройка CURLOPT_PROXY — аутентификация, SOCKS5, ротация curl_multi, Guzzle и тайм-ауты — которая работает в продакшене.
Код PHP cURL proxy имеет специфическую проблему: самые популярные примеры устарели на десятилетие, и многие содержат реальные ошибки — двойные вызовы curl_exec(), которые запускают каждый запрос дважды, отключение CURLOPT_SSL_VERIFYPEER "чтобы HTTPS работал", параметры, установленные на значения, которые уже являются значениями по умолчанию. Это руководство — актуальная, правильная версия: CURLOPT_PROXY с аутентификацией, SOCKS5, параллельная ротация с curl_multi, такая же настройка в Guzzle и тайм-ауты, которые не позволяют рабочему зависнуть на всю ночь.
Минимальный, правильный пример PHP cURL proxy
<?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, а не ресурс; вызовы параметров остаются неизменными.
Параметры, которые люди копируют бездумно (и одна реальная ошибка безопасности)
CURLOPT_HTTPPROXYTUNNEL— не нужен для целей HTTPS. cURL автоматически устанавливает туннель CONNECT всякий раз, когда цель —https://; этот параметр только принуждает к туннелированию для целей с обычным HTTP, что почти никогда не требуется для скрепинга.CURLOPT_CUSTOMREQUEST => 'GET'— GET уже является значением по умолчанию; установка его ничего не делает, кроме как ломает обработку перенаправлений в крайних случаях.- Двойной вызов
curl_exec()— классическая ошибка Stack Overflow: запрос выполняется дважды, и второе возвращаемое значение затирает первое. Один раз на дескриптор. CURLOPT_SSL_VERIFYPEER => false— действительно опасная ошибка. Она "исправляет" ошибки сертификата, позволяя атаки типа "человек в середине" на каждый запрос. Настоящее исправление, согласно руководству PHP, — это указаниеcurl.cainfoв php.ini на актуальный пакетcacert.pem.

SOCKS5 прокси в PHP
<?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 proxy для эквивалентов в оболочке всего на этой странице.
Ротация и параллельные запросы с curl_multi
PHP не имеет async/await, но это и не нужно для параллельного скрепинга — curl_multi запускает десятки передач одновременно в одном процессе. Укажите каждый дескриптор на вращающийся шлюз, и каждый запрос автоматически выходит с другого резидентного 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) для каждого дескриптора и направьте ошибки в очередь повторных попыток, а не повторно запрашивайте их в строке. Когда поток охватывает несколько запросов, которые должны разделять одну идентичность (вход в систему, корзина, пагинация с использованием куки), переключите этот дескриптор на липкую сессию, добавив суффикс к имени пользователя: USER-session-a1b2c3 удерживает тот же выходной IP на протяжении всего времени вместо ротации.

Заголовки и куки: выглядите как браузер, за который себя выдаете
Чистый выходной IP с заголовками по умолчанию cURL все еще читается как бот — PHP объявляет себя, если вы не оформите запрос. Три параметра закрывают большую часть разрыва: CURLOPT_USERAGENT с текущей строкой браузера, CURLOPT_HTTPHEADER для Accept-Language и других, и пара куки CURLOPT_COOKIEJAR / CURLOPT_COOKIEFILE, указывающая на один и тот же файл, что сохраняет сессионные куки между дескрипторами так, как это делает браузер. Сохранение куки важнее, чем люди ожидают: многие сайты устанавливают токен при первом посещении и тихо проверяют клиентов, которые его не возвращают. Сохраняйте заявленную идентичность согласованной — User-Agent Chrome без Accept-Language и с минимальным набором заголовков сам по себе является отпечатком, и проверка согласованности — это именно то, как поставщики антиботов среднего уровня ловят PHP-скреперы на их втором запросе.
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 под капотом, так что все вышеперечисленное все еще применимо — просто читается лучше. Одно предупреждение: значение timeout по умолчанию в Guzzle — 0, что означает ожидание бесконечно. Прокси-выход, который зависает в середине передачи, будет удерживать рабочий PHP-FPM бесконечно, поэтому устанавливайте оба тайм-аута для каждого клиента, которого вы создаете. Параметр proxy также принимает массив с ключами http, https и no, когда вам нужно маршрутизацию по схеме или исключения для внутренних хостов.
Когда PHP cURL перестает быть достаточным
cURL извлекает HTML; он не выполняет JavaScript. Если ответ возвращается как скелет страницы с пустыми div, данные рендерятся на стороне клиента — наше руководство по ответам пустой страницы показывает, как это обнаружить. И на защищенных целях, сам TLS-хендшейк cURL выдает вас, независимо от того, насколько чист IP (загадка браузер работает, а cURL 403). PHP не может разумно запускать безголовый браузер для каждого запроса, поэтому прагматичный выход — это Scraper API: один вызов 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 прокси?
Да. Передайте URL socks5h:// в CURLOPT_PROXY, или установите CURLOPT_PROXYTYPE на CURLPROXY_SOCKS5_HOSTNAME. Предпочитайте варианты с именем хоста вместо простого socks5, чтобы DNS разрешался на выходе прокси, а не на вашем сервере — иначе каждый целевой домен утечет через ваш локальный резолвер.
Что такое CURLOPT_HTTPPROXYTUNNEL и нужно ли это?
Он заставляет cURL туннелировать через прокси с помощью CONNECT вместо того, чтобы просить прокси получить URL. Для целей https:// cURL туннелирует автоматически, так что параметр избыточен в почти всем коде скрепинга. Установите его только тогда, когда вам нужны семантики туннеля для цели с обычным HTTP.
Как установить тайм-аут для PHP cURL через прокси?
Установите оба: CURLOPT_CONNECTTIMEOUT охватывает достижение прокси и открытие туннеля, CURLOPT_TIMEOUT ограничивает всю передачу. Варианты в миллисекундах (_MS) существуют для более жестких бюджетов. Без них зависший выход удерживает запрос — и под PHP-FPM, целый рабочий — до тех пор, пока собственный лимит веб-сервера не убьет его.
Скопируйте первый фрагмент, держите в уме контрольный список, и PHP — это совершенно надежный язык для скрепинга: curl_multi для параллелизма, вращающийся шлюз для идентичности, Guzzle для эргономики и Scraper API для страниц, которые сопротивляются.
Запустите ваш PHP скрепер на резидентных IP от QuantumProxies