Написали парсер, запустили, работает. Через час бан по 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 мало. Но для мониторинга цен, сбора каталогов и несложного скрапинга описанных методов достаточно.
Сбор неструктурированных данных из открытых источников превратился из вспомогательной задачи в ключевой элемент конкурентной аналитики. Методология парсинга варьируется от простого HTTP-запроса до сложных распределенных систем, эмулирующих поведение реального пользователя.
PHP: потоковая обработка и XML-навигация
Экосистема и профиль нагрузки
PHP традиционно воспринимается как язык для веба, но его консольные возможности позволяют строить эффективные парсеры. Основное преимущество – богатый опыт работы с HTML/XML через DOMDocument и расширение ext-xmlreader. Для больших объемов критично использовать потоковый парсинг, избегая загрузки всего документа в память.
Работа с тяжелыми XML через XmlReader
При парсинге выписок брокеров или дампов баз данных объемом гигабайты стандартный simplexml_load_file() вызовет переполнение памяти. Используйте XMLReader в сочетании с DOMDocument для выборочной выгрузки узлов.
$reader = new XMLReader();
$reader->open('broker_report.xml');
while ($reader->read()) {
if ($reader->nodeType === XMLReader::ELEMENT && $reader->localName === 'Trade') {
$node = new DOMDocument();
$node->appendChild($node->importNode($reader->expand(), true));
$domx = new DOMXPath($node);
$amount = $domx->query('//Amount')->item(0)->nodeValue;
// Немедленная обработка узла, затем освобождение памяти
processTrade($amount);
unset($node);
}
}
$reader->close();
Пакетный подход для сложных структур
На практике полезны обертки над нативными средствами. Утилита rodenastyle/stream-parser демонстрирует удобный интерфейс, где каждый элемент документа оборачивается в коллекцию, а обработчик применяется к нему через callback. Это снижает порог входа без потери производительности.
use Illuminate\Support\Collection;
StreamParser::xml("https://source.com/books.xml")->each(function(Collection $book) {
// Каждый тег становится отдельной коллекцией
$price = $book->get('price');
$comments = $book->get('comments')->toArray();
saveToDatabase($book->get('ISBN'), $price, $comments);
});
Решение специфических проблем кодировок
Финансовые отчеты часто приходят в Windows-1251. При работе с DOMDocument необходимо преобразовать кодировку до загрузки, иначе кириллица превратится в кракозябры. Технически грамотный парсер всегда нормализует даты (из 02.10.24 в 02.10.2024) и числа с плавающей точкой, так как атрибуты XML приходят как строки.
function loadEncodedXml($path) {
$content = file_get_contents($path);
$encoding = 'windows-1251';
$utf8 = mb_convert_encoding($content, 'UTF-8', $encoding);
$doc = new DOMDocument();
$doc->loadXML($utf8, LIBXML_NOERROR);
$xpath = new DOMXPath($doc);
$xpath->registerNamespace('bis', 'http://broker.schema/BIS');
return $xpath;
}
Python: асинхронный скрейпинг и тяжелая аналитика
Фреймворки: Scrapy против BeautifulSoup
Python предлагает два полюса: низкоуровневые библиотеки для малых задач и тяжелые фреймворки для промышленного сбора. BeautifulSoup удобен для парсинга единичных страниц, где нужно пройти по дереву DOM, но не справляется с распределением нагрузки. Scrapy построен на асинхронном движке Twisted, что позволяет поддерживать сотни открытых соединений одновременно без создания потоков ОС.
# Scrapy Spider для массового сбора
import scrapy
class ParserSpider(scrapy.Spider):
name = "market_spider"
start_urls = ['https://guide.kaspi.kz/client/ru']
def parse(self, response):
# Используем CSS-селекторы для скорости
for product in response.css('div.product-card'):
yield {
'title': product.css('a.:text').get(),
'price': product.css('span.price::text').re_first(r'[\d\s]+'),
'link': product.css('a::attr(href)').get()
}
# Следование пагинации
next_page = response.css('a.next-page::attr(href)').get()
if next_page:
yield response.follow(next_page, self.parse)
Асинхронный режим для обхода антибот-систем
При сборе динамических сообществ (например, Pikabu или Twitter) синхронные запросы слишком медленны. Связка aiohttp + asyncio позволяет отправлять запросы пачками. Для обхода ограничений пагинации часто используется техника поиска по датам, где скрипт самостоятельно генерирует временные метки для запроса старых постов.
import aiohttp
import asyncio
from fake_useragent import UserAgent
async def fetch(session, url):
headers = {'User-Agent': UserAgent().random}
async with session.get(url, headers=headers) as response:
return await response.text()
async def bulk_parse(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
# Обработка выполняется конкурентно
return await asyncio.gather(*tasks)
Работа с API и динамическим контентом
Когда данные подгружаются через JavaScript, прямое извлечение из HTML не работает. В этом случае применяют Playwright или Selenium, но для API-шлюзов (маркетплейсы типа Wildberries) эффективнее перехватывать сетевые запросы. Python перехватывает POST-запросы, имитирующие работу приложения, и обрабатывает JSON напрямую, минуя рендеринг страницы.
A-Parser: визуальный конструктор для SEO и автоматизаторов
Концепция пресетов и конфигурации
A-Parser представляет собой среду выполнения, где парсеры пишутся на JavaScript (интерпретатор V8), но управляются через графический интерфейс. Вместо написания кода с нуля пользователь настраивает пресет – JSON-объект, описывающий маршруты запросов. Статический метод defaultConf задает, какие поля извлечь и как их форматировать.
static defaultConf = {
version: '0.0.1',
results: {
flat: [
['title', 'Заголовок'],
['price', 'Цена'],
['availability', 'Наличие']
]
},
results_format: "Товар: $title | Цена: $price\n",
timeout: 30,
useproxy: 1 // Включить ротацию прокси
};
Настройка через editableConf
Главное преимущество – декларативная настройка полей ввода без переписывания логики. Массив editableConf определяет, какие параметры увидит пользователь в интерфейсе (текстовые поля, чекбоксы, выпадающие списки). A-Parser генерирует форму автоматически, что позволяет менеджерам по SEO изменять параметры сбора (глубину, задержки) без знания программирования.
Гибридный подход: связка с нейросетями
Программный интерфейс A-Parser позволяет запускать batch-обработку текстов через нейросети (ChatGPT, Perplexity). Парсер берет на себя ротацию прокси, очереди запросов и обработку ошибок капчи. Пользователь отправляет JSON через POST на локальный сервер A-Parser, получая готовый результат resultString, который можно использовать в других системах.
ZennoPoster: эмуляция пользователя и C#
Низкоуровневый контроль браузера
ZennoPoster работает не через HTTP-клиент, а через эмуляцию полноценного браузера (Chromium). Это необходимо для обхода сложных антибот-систем, анализирующих движения мыши, свайпы и WebGL-отпечатки. Шаблоны создаются визуально (кубики) или на C#. Для высоконагруженных проектов шаблоны компилируются в код.
Встраивание C# кода для обработки массивов
В ZennoPoster можно использовать сниппеты C# внутри одного кубика. Это ускоряет постобработку данных в сотни раз по сравнению с визуальными действиями. Следующий пример загружает два списка, фильтрует строки, содержащие ключи из первого списка, и выгружает результат за секунду вместо минуты.
// Код для кубика "Выполнить C#" в ZennoPoster
string projectDirectory = project.Directory;
// Чтение и дедупликация
List<string> keywords = File.ReadAllLines(Path.Combine(projectDirectory, "List1.txt")).Distinct().ToList();
List<string> dataRows = File.ReadAllLines(Path.Combine(projectDirectory, "List2.txt")).Distinct().ToList();
List<string> result = new List<string>();
foreach (string keyword in keywords)
{
// Быстрый поиск через LINQ
var matches = dataRows.Where(item => item.Contains(keyword)).ToList();
if (matches.Any())
{
foreach (var match in matches)
{
dataRows.Remove(match);
result.Add(match);
}
}
}
// Запись результатов обратно в файл проекта
File.WriteAllLines(Path.Combine(projectDirectory, "List3.txt"), result);
Протокол взаимодействия с внешними API
Связка ZennoPoster и A-Parser реализуется через HTTP-запросы из кубика "POST". Это позволяет использовать A-Parser как вычислительный кластер: ZennoPoster эмулирует сложное поведение пользователя, получает сырые данные и отправляет их на парсинг в A-Parser, который быстро структурирует JSON.
{
"password": "",
"action": "oneRequest",
"data": {
"query": "Сгенерируй заголовок для статьи",
"parser": "FreeAI::ChatGPT",
"configPreset": "default"
}
}
C++: максимальная производительность и бинарные протоколы
Преимущества нативного парсинга
C++ выбирают для задач, где скорость критична (сбор биржевых тиков, разбор пакетов сетевых протоколов, потоковая обработка терабайт логов). Отсутствие сборщика мусора и управление памятью вручную позволяют достигать микросекундных задержек. Библиотеки типа RapidXML или simdjson используют инструкции SIMD для разбора данных быстрее, чем пишется код на интерпретируемых языках.
Чтение бинарных форматов
В отличие от веб-парсинга, на C++ часто приходит бинарные данные от железок или проприетарных протоколов. Например, файл .bin может содержать структуру: байт группы, затем 4 байта координаты X и 4 байта Y. Парсер на C++ читает файл как поток байтов, накладывая структуру через memcpy или битовые сдвиги, что занимает наносекунды.
#include <fstream>
#include <vector>
#include <cstdint>
#pragma pack(push, 1) // Выравнивание без padding
struct Point {
uint8_t group;
int32_t x;
int32_t y;
};
#pragma pack(pop)
std::vector<Point> parseBinaryFile(const std::string& path) {
std::ifstream file(path, std::ios::binary);
if (!file.is_open()) return {};
file.seekg(0, std::ios::end);
size_t fileSize = file.tellg();
file.seekg(0, std::ios::beg);
// Рассчитываем количество записей и резервируем память
size_t count = fileSize / sizeof(Point);
std::vector<Point> points;
points.reserve(count);
// Прямое чтение блоками
Point p;
while (file.read(reinterpret_cast<char*>(&p), sizeof(p))) {
points.push_back(p);
}
return points;
}
Параллелизм и шедулинг
C++ позволяет задействовать все ядра процессора через std::async или std::thread. При парсинге множества файлов (например, миллиона маленьких JSON) имеет смысл создать пул потоков, где каждый поток обрабатывает свой кусок данных. В связке с Python или Node.js через FFI критичные по скорости модули пишут на C++ и подключают как динамические библиотеки.
Сравнительный анализ производительности
Когда выбирать PHP
PHP оптимален для встраивания в существующие веб-проекты (CMS, биллинг). Если данные нужно сразу отдать в шаблон или записать в локальную MySQL, скрипт на PHP отработает без оверхедов на межпроцессное взаимодействие. Потоковый XMLReader незаменим для финансовых логов.
Когда выбирать Python
Python - король исследований и работы с "грязными" данными. Библиотеки Pandas, NumPy и Jupyter Notebook позволяют сразу визуализировать спарсенные данные, строить графики динамики продаж и находить аномалии. Scrapy обеспечивает наилучшую масштабируемость для краулеров.
Когда выбирать A-Parser
Это инструмент для тех, кому нужен результат "здесь и сейчас" без написания кода. Готовые пресеты для Google, Bing, социальные сети и парсеры нейросетей экономят недели разработки. A-Parser часто используют как прокси-сервер: мелкие парсеры на PHP или Python отправляют в A-Parser сложные запросы, а он возвращает уже разобранный JSON.
Когда выбирать ZennoPoster
Тяжелая артиллерия для сайтов с Behavioral Analysis. Если сайт требует капчу, эмуляцию скролла, хранение кук и подмену WebGL – ZennoPoster справляется там, где обычный requests падает. Шаблон на C# внутри ZennoPoster работает быстрее визуального в 10 раз.
Когда выбирать C++
Только при жестких требованиях к ресурсам: 100 000 запросов в секунду, обработка бинарных протоколов в реальном времени (HFT-трейдинг), или когда нужно запустить парсер на микроконтроллере (ESP32, ARM).
| Инструмент | Скорость работы | Сложность обучения | Обход защиты | Тип данных |
|---|---|---|---|---|
| PHP | Средняя (потоковая) | Низкая | Базовая | XML, HTML |
| Python | Высокая (асинхронная) | Средняя | Средняя | JSON, HTML, API |
| A-Parser | Высокая (нативная) | Низкая | Высокая (нейросети) | Текст, JSON |
| ZennoPoster | Средняя (браузерная) | Высокая | Максимальная | Динамический контент |
| C++ | Экстремальная | Экстремальная | Низкая | Бинарные протоколы |
Архитектурные паттерны для отказоустойчивости
Циркулярные буферы для очередей
При разборе данных критически важна очередь. Независимо от языка, реализуйте паттерн "Producer-Consumer". Один поток (или процесс) качает байты из сети, складывает в блокирующую очередь. Другой поток разбирает эти байты. При падении потребителя, продюсер продолжает накопление, исключая потерю данных. В PHP для этого используют iterable генераторы, в Python - queue.Queue, в C++ - concurrent_queue из TBB.
Ротация прокси и User-Agent
Профессиональный парсинг невозможен без пула прокси. Практический совет: храните прокси в кольцевом буфере с меткой времени бана (proxybannedcleanup в терминах A-Parser). Если запрос вернул 429 или 403, прокси помечается как "забаненное" и исключается из ротации на 10-15 минут. Генерация User-Agent должна быть логически связана с ОС: Windows-агенты идут с Chrome, а Mac-агенты с Safari.
Выбор инструмента парсинга диктуется форматом источника, требуемой скоростью и доступными ресурсами. Python хорош для прототипов, ZennoPoster для обхода защиты, PHP для интеграции, C++ для скорости, а A-Parser для автоматизации без кода. Критически важным остается нормализация данных, обработка кодировок и грамотная работа с памятью.
