Парсинг в PHP: как получить и разобрать данные со страниц

Парсинг в PHP: как получить и разобрать данные со страниц Полезное

Парсинг в PHP — это автоматическое получение веб-страницы по сети и извлечение из ее HTML нужных данных скриптом на PHP. Ниже — рабочая цепочка «получить страницу -> разобрать разметку -> вытащить нужные узлы», примеры на cURL и DOMDocument, честная граница регулярных выражений и правила, без которых парсер быстро упрется в блокировку.

Дальше разберем каждый шаг с кодом, который можно скопировать и запустить, и отдельно — функцию parse_url(), которую часто путают с парсингом страниц.

Три разных смысла слова «парсинг»

Слово перегружено, и новички смешивают три разные вещи. Разведем их сразу, дальше в статье они не путаются.

Термин Что делает Инструмент в PHP
Разбор (parsing) строки превращает текст в структуру данных json_decode, DOMDocument
parse_url() режет строку-адрес на части (схема, хост, путь) функция ядра
Веб-скрапинг скачивает страницу и достает из нее данные cURL + DOMDocument

Когда говорят «парсер сайта на PHP», обычно имеют в виду именно веб-скрапинг: сеть плюс разбор HTML. Именно он в фокусе статьи.

Как устроен парсер: три шага

Любой скрапер укладывается в три этапа:

  1. Получить HTML — отправить HTTP-запрос и принять тело ответа.
  2. Разобрать разметку — построить из строки дерево элементов (DOM).
  3. Извлечь и сохранить — выбрать нужные узлы через XPath или CSS-селекторы, записать в файл или базу.

Разберем их по порядку на минимальных запускаемых примерах.

Шаг 1. Получаем страницу: cURL

cURL (обертка над библиотекой libcurl) — основной способ ходить по сети из PHP: поддерживает HTTPS, редиректы, заголовки, cookie и POST.

<?php
$ch = curl_init('https://example.com/');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // вернуть тело строкой, а не печатать
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // идти за редиректами 301/302
curl_setopt($ch, CURLOPT_TIMEOUT, 20);          // таймаут ответа, секунды
curl_setopt($ch, CURLOPT_USERAGENT, 'MyParser/1.0 (+https://example.com/bot)');

$html = curl_exec($ch);

if ($html === false) {
    echo 'Ошибка cURL: ' . curl_error($ch);
} else {
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    echo "HTTP-код: {$code}, получено байт: " . strlen($html);
    // Пример вывода: HTTP-код: 200, получено байт: 1256 (длина зависит от страницы)
}
curl_close($ch);

Ключевой параметр — CURLOPT_RETURNTRANSFER: без него curl_exec() печатает ответ в вывод и возвращает true, а переменная $html останется пустой. Всегда проверяйте результат на === false (ошибка сети) и отдельно смотрите HTTP-код: страница может ответить 404 или 403, а $html при этом будет непустым.

Более простой вариант без cURL — file_get_contents(), но он требует включенной опции allow_url_fopen в php.ini и хуже управляет заголовками и ошибками:

<?php
$context = stream_context_create([
    'http' => [
        'header'  => "User-Agent: MyParser/1.0\r\n",
        'timeout' => 20,
    ],
]);
$html = file_get_contents('https://example.com/', false, $context);
// при ошибке вернет false и выдаст предупреждение

Для новичка правило простое: разовое чтение — file_get_contents, любой реальный парсер (редиректы, cookie, POST, обработка ошибок) — cURL.

Шаг 2. Разбираем HTML: DOMDocument и XPath

Получив строку HTML, ее нужно превратить в дерево, по которому можно ходить. В стандартной поставке PHP для этого есть класс DOMDocument, а выбирать узлы удобно через DOMXPath.

<?php
$html = '<html><body><h1>Заголовок</h1>'
      . '<a href="/page1">Ссылка 1</a>'
      . '<a href="/page2">Ссылка 2</a></body></html>';

$doc = new DOMDocument();
libxml_use_internal_errors(true); // не падать на невалидной верстке реальных сайтов
// без подсказки о кодировке DOMDocument читает вход как Latin-1 и портит кириллицу;
// префикс с XML-декларацией - распространенный способ заставить его читать UTF-8
$doc->loadHTML('<?xml encoding="UTF-8">' . $html, LIBXML_NOERROR);
libxml_clear_errors();

$xpath = new DOMXPath($doc);

$h1 = $xpath->query('//h1')->item(0);
echo $h1->textContent . PHP_EOL;

foreach ($xpath->query('//a') as $a) {
    echo $a->getAttribute('href') . ' -> ' . $a->textContent . PHP_EOL;
}
// Вывод:
// Заголовок
// /page1 -> Ссылка 1
// /page2 -> Ссылка 2

Здесь важны две привычки. Первая — libxml_use_internal_errors(true): реальная верстка почти всегда невалидна, и без этого DOMDocument засыпет вывод предупреждениями. Вторая — явная кодировка: без нее кириллица в выводе превратится в мусор. Прием с префиксом <?xml encoding="UTF-8"> работает на большинстве сборок libxml, но это именно обходной трюк, а не документированный параметр, и его поведение зависит от версии libxml.

XPath — язык адресации узлов: //a — все ссылки, //div[@class="price"] — все div с нужным классом, //h1/text() — текст заголовка. Если привычнее CSS-селекторы (как в jQuery), поставьте пакеты symfony/dom-crawler и symfony/css-selector — тогда доступен метод filter('div.price a'). Раньше для этого брали библиотеку phpQuery, но она давно заброшена и на новых проектах ее использовать не стоит; Symfony DomCrawler — актуальная замена.

Регулярные выражения: когда можно, когда нельзя

Регулярное выражение (preg_match, preg_match_all) — шаблон для поиска подстрок. Соблазн вытаскивать данные прямо из HTML регуляркой велик, но HTML — не регулярный язык, и наивный шаблон ломается на первом же отклонении верстки:

<?php
$html = '<a href="/a">A</a> <a href=\'/b\'>B</a>';
preg_match_all('/<a href="([^"]+)"/', $html, $m);
print_r($m[1]);
// Array ( [0] => /a )
// вторая ссылка с одинарными кавычками потеряна - шаблон ждал только двойные

Правильно тут — разбирать DOM-парсером, который понимает и одинарные, и двойные кавычки, и лишние пробелы, и вложенность:

<?php
$doc = new DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML('<a href="/a">A</a> <a href=\'/b\'>B</a>', LIBXML_NOERROR);
foreach ((new DOMXPath($doc))->query('//a') as $a) {
    echo $a->getAttribute('href') . PHP_EOL;
}
// Вывод:
// /a
// /b

Регулярки уместны для простого текста внутри уже извлеченного узла — вытащить число из строки «Цена: 1990 руб», проверить формат телефона. Для разбора самой разметки берите DOMDocument.

parse_url: разбор адреса, а не страницы

parse_url() не ходит в сеть и не читает HTML — она режет одну строку-адрес на компоненты. Ее путают с парсингом страниц из-за слова «parse», поэтому держим отдельно.

<?php
$parts = parse_url('https://user:pass@otus.ru:443/journal/?q=php#top');
print_r($parts);
/* Вывод:
Array
(
    [scheme] => https
    [host] => otus.ru
    [port] => 443
    [user] => user
    [pass] => pass
    [path] => /journal/
    [query] => q=php
    [fragment] => top
)
*/

Что стоит помнить:

  • функция возвращает ассоциативный массив; отсутствующие в адресе части просто не попадают в него (ключа нет);
  • на серьезно испорченном URL возвращает false — проверяйте результат перед обращением к ключам;
  • со вторым аргументом (например PHP_URL_HOST) вернет одну часть строкой, а не массив; для отсутствующей части — null;
  • корректность адреса она не проверяет — это разбор строки, а не валидация.

Разобрать строку запроса (q=php&page=2) в массив помогает парная функция parse_str().

Этика и robots.txt: что можно парсить

Парсер обращается к чужому серверу, поэтому у скрапинга есть правила — и технические, и юридические. Игнорировать их — быстрый путь к бану по IP и к претензиям.

  • robots.txt. По адресу https://сайт/robots.txt владелец указывает, какие разделы не предназначены для автоматического обхода (Disallow). Это не техническая защита, но общепринятая договоренность; уважайте ее. Нестандартную директиву Crawl-delay (пауза между запросами) поддерживают не все, но если она есть — соблюдайте.
  • Нагрузка. Не бейте запросами в цикле без пауз: добавляйте задержку (sleep() между обращениями) и лимит частоты, иначе создадите серверу проблемы и получите блокировку.
  • Идентификация. Ставьте осмысленный User-Agent с контактом — это честнее анонимного обхода и упрощает диалог, если что-то пойдет не так.
  • Право. Условия использования сайта, авторские права на контент и законодательство о персональных данных (в РФ — 152-ФЗ) распространяются и на собранные данные. Публичная доступность страницы не означает, что данные можно переопубликовывать или собирать персональные сведения. Спорные случаи — к юристу, а не «по общим соображениям».

Выводы

  • Парсинг в PHP — это связка «получить страницу по сети + разобрать ее HTML»; разделяйте эти два шага в коде.
  • Для запроса берите cURL (CURLOPT_RETURNTRANSFER, проверка === false и HTTP-кода); file_get_contents — для разовых чтений при включенном allow_url_fopen.
  • HTML разбирайте через DOMDocument и DOMXPath (или Symfony DomCrawler для CSS-селекторов), а не регулярками: наивный regex ломается на кавычках и вложенности.
  • parse_url() — это разбор строки-адреса, а не страницы; не путайте с веб-скрапингом.
  • robots.txt, паузы между запросами, честный User-Agent и правовые ограничения — обязательная часть парсера, а не опция.

Где применяется / связь с практикой

Парсинг — рабочий инструмент backend-разработчика: наполнение каталогов, мониторинг цен, сбор данных для аналитики, интеграции между сервисами без готового API. Все это упирается в те же навыки — HTTP-запросы, разбор HTML и аккуратная обработка ошибок и кодировок.

Освойте тему на практике

Систематически эти темы (cURL, работа с DOM, взаимодействие с внешними сервисами, устройство HTTP) разбирают на курсе Разработчик на PHP. Посмотреть формат и уровень подачи, прежде чем принимать решение, удобно на открытых уроках — это бесплатные занятия по отдельным темам.

FAQ

Как парсить сайт, который подгружает данные через JavaScript?
cURL и DOMDocument видят только исходный HTML и не выполняют JS. Если контент дорисовывается скриптами, ищите запрос к API в панели разработчика браузера (вкладка Network) и обращайтесь к нему напрямую, либо используйте headless-браузер (например, через Chrome DevTools Protocol).

Что делать, если сервер отдает 403 или блокирует парсер?
Проверьте User-Agent и заголовки, снизьте частоту запросов, посмотрите robots.txt. Если доступ закрыт намеренно — это сигнал, что автоматический сбор здесь не приветствуется; ищите официальный API или разрешение владельца.

Ответ приходит «кракозябрами» — как чинить кодировку?
Смотрите заголовок Content-Type и meta-тег страницы. Если кодировка не UTF-8, приводите строку через mb_convert_encoding($html, 'UTF-8', 'windows-1251') до передачи в DOMDocument, либо задавайте кодировку при загрузке.

OTUS Журнал
Скидка 5% 14-20 сентября на курсы (popup)