# 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
<?php
function fetch(string $url): string|false {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_PROXY          => 'http://residentialpremium.quantumproxies.io:8080',
        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`.

![curl_setopt_array в PHP с шестью опциями, которые нужно задать: CURLOPT_PROXY, PROXYUSERPWD, RETURNTRANSFER, CONNECTTIMEOUT 10, TIMEOUT 30 и ENCODING, а затем долг копипасты, который нужно удалить: HTTPPROXYTUNNEL, CUSTOMREQUEST GET, SSL_VERIFYPEER false и двойной curl_exec. Карточка советует вместо этого исправлять сертификаты через curl.cainfo.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335147/blog/generated/qp-b9-008-diag1.png)
*Шесть параметров выполняют всю работу; правая колонка — это долг копирования-вставки из учебников 2011 года.*

## SOCKS5 прокси в PHP

```php
<?php
// scheme style: socks5h = DNS resolved by the proxy (use for scraping)
curl_setopt($ch, CURLOPT_PROXY, 'socks5h://USER:PASS@residentialpremium.quantumproxies.io:1080');

// split style: same result via CURLOPT_PROXYTYPE
curl_setopt($ch, CURLOPT_PROXY, 'residentialpremium.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 на каждом плане](https://quantumproxies.io/socks5-proxies), так что переключение протоколов — это изменение одной строки, а не новая подписка. То же самое правило DNS применяется в командной строке — смотрите наши [рецепты curl proxy](https://quantumproxies.io/blog/curl-with-proxy-examples) для эквивалентов в оболочке всего на этой странице.

## Ротация и параллельные запросы с curl_multi

PHP не имеет async/await, но это и не нужно для параллельного скрепинга — `curl_multi` запускает десятки передач одновременно в одном процессе. Укажите каждый дескриптор на вращающийся шлюз, и каждый запрос автоматически выходит с другого [резидентного IP](https://quantumproxies.io/residential-proxies), без необходимости ведения списка прокси в вашем коде:

```php
<?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@residentialpremium.quantumproxies.io:8080',
        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-sid-a1b2c3-ttl-30m` удерживает тот же выходной IP на протяжении всего времени вместо ротации.

![PHP-воркер отправляет пакеты curl_multi от 8 до 16 дескрипторов через один URL прокси, а шлюз выбирает выход: новый ротируемый резидентный IP, фиксированную сессию с тем же выходом или гео-таргетированный выход. Карточка: ротация, фиксированное имя пользователя USER-session-a1b2c3 и гео-таргетинг задаются в URL прокси.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335147/blog/generated/qp-b9-008-diag2.png)
*Рабочий остается простым, шлюз остается умным: ротация, липкость и гео-таргетинг все находятся в URL прокси.*

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

Чистый выходной IP с заголовками по умолчанию cURL все еще читается как бот — PHP объявляет себя, если вы не оформите запрос. Три параметра закрывают большую часть разрыва: `CURLOPT_USERAGENT` с текущей строкой браузера, `CURLOPT_HTTPHEADER` для `Accept-Language` и других, и пара куки `CURLOPT_COOKIEJAR` / `CURLOPT_COOKIEFILE`, указывающая на один и тот же файл, что сохраняет сессионные куки между дескрипторами так, как это делает браузер. Сохранение куки важнее, чем люди ожидают: многие сайты устанавливают токен при первом посещении и тихо проверяют клиентов, которые его не возвращают. Сохраняйте заявленную идентичность согласованной — User-Agent Chrome без `Accept-Language` и с минимальным набором заголовков сам по себе является отпечатком, и проверка согласованности — это именно то, как поставщики антиботов среднего уровня ловят PHP-скреперы на их втором запросе.

## Guzzle: тот же прокси, более удобная эргономика

```php
<?php
use GuzzleHttp\Client;

$client = new Client([
    'proxy'           => 'http://USER:PASS@residentialpremium.quantumproxies.io:8080',
    '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-sid-a1b2c3-ttl-30m:PASS@residentialpremium.quantumproxies.io:8080',
]);
```

Guzzle управляет cURL под капотом, так что все вышеперечисленное все еще применимо — просто читается лучше. Одно предупреждение: значение `timeout` по умолчанию в Guzzle — 0, что означает ожидание бесконечно. Прокси-выход, который зависает в середине передачи, будет удерживать рабочий PHP-FPM бесконечно, поэтому устанавливайте оба тайм-аута для каждого клиента, которого вы создаете. Параметр `proxy` также принимает массив с ключами `http`, `https` и `no`, когда вам нужно маршрутизацию по схеме или исключения для внутренних хостов.

## Когда PHP cURL перестает быть достаточным

cURL извлекает HTML; он не выполняет JavaScript. Если ответ возвращается как скелет страницы с пустыми div, данные рендерятся на стороне клиента — наше руководство по [ответам пустой страницы](https://quantumproxies.io/blog/scraper-returns-empty-page-javascript) показывает, как это обнаружить. И на защищенных целях, сам TLS-хендшейк cURL выдает вас, независимо от того, насколько чист IP (загадка [браузер работает, а cURL 403](https://quantumproxies.io/blog/curl-403-but-browser-works)). PHP не может разумно запускать безголовый браузер для каждого запроса, поэтому прагматичный выход — это [Scraper API](https://quantumproxies.io/scraper-api): один вызов cURL к единой конечной точке, и рендеринг, отпечатки браузера, ротация и парсинг возвращаются в виде чистого markdown, JSON или HTML.

[Попробуйте Scraper API из PHP](https://quantumproxies.io/scraper-api)

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

### Как использовать прокси с 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](https://quantumproxies.io/residential-proxies)
