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, а не ресурс; вызовы параметров остаются неизменными.

Параметры, которые люди копируют бездумно (и одна реальная ошибка безопасности)

Контрольный список параметров PHP cURL proxy, которые следует установить, и параметров, которых следует избегать, включая ошибку SSL_VERIFYPEER
Шесть параметров выполняют всю работу; правая колонка — это долг копирования-вставки из учебников 2011 года.

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 на протяжении всего времени вместо ротации.

Диаграмма конвейера скрепинга PHP, отправляющего партии curl_multi через вращающийся шлюз резидентных прокси
Рабочий остается простым, шлюз остается умным: ротация, липкость и гео-таргетинг все находятся в URL прокси.

Заголовки и куки: выглядите как браузер, за который себя выдаете

Чистый выходной 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.

Попробуйте Scraper API из PHP

Часто задаваемые вопросы

Как использовать прокси с 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