Написали парсер, запустили, работает. Через час бан по IP. Знакомо? Сайты научились определять и блокировать автоматические запросы, а простой скрипт на CURL без подготовки живёт недолго. При этом задачи парсинга никуда не делись: мониторинг цен, сбор данных, анализ конкурентов.

Для стабильного парсинга на PHP понадобится библиотека CURL для запросов, пул прокси-серверов (подобрать подходящий сервис можно на toproxylab.com где собраны рейтинги провайдеров под разные задачи), и немного хитростей с заголовками и задержками. Разберём как всё это собрать в рабочий код.
Базовый запрос через CURL с прокси
Начнём с простого. Вот минимальный код для запроса через прокси:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.com/page');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_PROXY, '192.168.1.1:8080');
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Работает? Работает. Но недолго. Проблема в том, что запрос выглядит как запрос бота. Нет заголовков, нет cookies, подозрительно чистый.
Добавляем реалистичные заголовки:
$ch = curl_init();
$headers = [
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
'Accept-Encoding: gzip, deflate, br',
'Connection: keep-alive',
];
curl_setopt($ch, CURLOPT_URL, 'https://example.com/page');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_PROXY, '192.168.1.1:8080');
curl_setopt($ch, CURLOPT_ENCODING, ''); // автоматическая распаковка gzip
$response = curl_exec($ch);
curl_close($ch);
Уже лучше. Теперь запрос похож на запрос браузера.
Авторизация и типы прокси
Бесплатные прокси обычно без авторизации. Платные почти всегда требуют логин и пароль. В CURL это делается так:
curl_setopt($ch, CURLOPT_PROXY, '192.168.1.1:8080');
curl_setopt($ch, CURLOPT_PROXYUSERPWD, 'username:password');
Типы прокси тоже важны. HTTP прокси работает по умолчанию. Для SOCKS нужно указать тип явно:
// SOCKS5 прокси
curl_setopt($ch, CURLOPT_PROXY, '192.168.1.1:1080');
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
// SOCKS5 с DNS через прокси (рекомендуется)
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
Разница между SOCKS5 и SOCKS5_HOSTNAME в том, где резолвится DNS. Во втором случае DNS запросы тоже идут через прокси, что скрывает вашу активность от провайдера.
Подробнее о параметрах CURL можно почитать в официальной документации PHP https://www.php.net/manual/ru/function.curl-setopt.php.
Ротация прокси и User-Agent
Один прокси, один User-Agent, это путь к бану. Нужна ротация.
Вот класс для работы с пулом прокси:
class ProxyRotator
{
private $proxies = [];
private $userAgents = [];
private $currentIndex = 0;
public function __construct(array $proxies, array $userAgents)
{
$this->proxies = $proxies;
$this->userAgents = $userAgents;
}
public function getProxy()
{
$proxy = $this->proxies[$this->currentIndex];
$this->currentIndex = ($this->currentIndex + 1) % count($this->proxies);
return $proxy;
}
public function getRandomUserAgent()
{
return $this->userAgents[array_rand($this->userAgents)];
}
}
// Использование
$proxies = [
'192.168.1.1:8080',
'192.168.1.2:8080',
'192.168.1.3:8080',
];
$userAgents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
];
$rotator = new ProxyRotator($proxies, $userAgents);
Теперь каждый запрос будет с новым прокси и случайным User-Agent:
function fetchPage($url, $rotator)
{
$ch = curl_init();
$headers = [
'User-Agent: ' . $rotator->getRandomUserAgent(),
'Accept: text/html,application/xhtml+xml',
'Accept-Language: ru-RU,ru;q=0.9',
];
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_PROXY, $rotator->getProxy());
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ['code' => $httpCode, 'body' => $response];
}
Обработка ошибок и повторные попытки
Прокси падают. Сайты отвечают ошибками. Соединения обрываются. Нужна обработка:
function fetchWithRetry($url, $rotator, $maxRetries = 3)
{
$attempt = 0;
while ($attempt < $maxRetries) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_PROXY, $rotator->getProxy());
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
// Успешный ответ
if ($httpCode >= 200 && $httpCode < 300) {
return $response;
}
// Ошибка прокси или таймаут
if ($error || $httpCode == 407 || $httpCode == 0) {
$attempt++;
sleep(1);
continue;
}
// Бан или блокировка
if ($httpCode == 403 || $httpCode == 429) {
$attempt++;
sleep(rand(5, 15)); // Увеличенная пауза
continue;
}
break;
}
return false;
}
Код 429 (Too Many Requests) означает что сайт заметил подозрительную активность. Нужно замедлиться.
Задержки между запросами
Машинный трафик легко отличить по скорости. Человек не делает 100 запросов в секунду. Добавляем случайные паузы:
function humanDelay($min = 1, $max = 5)
{
$delay = rand($min * 1000, $max * 1000);
usleep($delay * 1000); // микросекунды
}
// Между запросами
foreach ($urls as $url) {
$result = fetchWithRetry($url, $rotator);
processResult($result);
humanDelay(2, 7); // пауза 2-7 секунд
}
Для агрессивного парсинга паузы можно уменьшить, но тогда нужно больше прокси в ротации. Баланс между скоростью и выживаемостью каждый находит сам.
Работа с cookies и сессиями

Некоторые сайты требуют cookies. Без них даже не покажут контент. CURL умеет сохранять и отправлять cookies:
$cookieFile = '/tmp/cookies_' . md5($proxyAddress) . '.txt';
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFile); // сохранять
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFile); // отправлять
Важно: для каждого прокси лучше использовать отдельный файл cookies. Иначе сессия одного прокси уйдёт на другой, что выглядит подозрительно.
Обход Cloudflare и других защит
Cloudflare, DataDome, PerimeterX, эти системы анализируют поведение и блокируют ботов. Простой CURL их не пройдёт.
Варианты решения:
- Первый: использовать браузерные решения типа https://pptr.dev или Selenium. Они рендерят JavaScript и проходят проверки. Но это уже не чистый PHP.
- Второй: специализированные прокси-сервисы с встроенным обходом защит. Они сами решают капчи и проходят проверки.
- Третий: для простых случаев иногда помогает правильный набор заголовков и cookies. Но это работает не всегда и не со всеми защитами.
Парсинг на PHP через CURL это рабочий инструмент когда всё настроено правильно. Прокси скрывают IP, ротация распределяет нагрузку, заголовки маскируют бота под браузер, задержки имитируют человека.
Главные правила: не жадничать со скоростью, иметь запас прокси, обрабатывать ошибки и следить за кодами ответов. 403 и 429 это сигналы замедлиться, а не повод долбить сервер ещё сильнее.
Для серьёзных проектов одного CURL мало. Но для мониторинга цен, сбора каталогов и несложного скрапинга описанных методов достаточно.
