Парсинг данных на PHP: от простого к сложному

Раздел: Разработка на PHP

Парсинг в PHP: методы и примеры

Основной подход: DOMDocument и DOMXPath

Для разбора HTML или XML в PHP рекомендуется использовать встроенные классы DOMDocument и DOMXPath. Этот метод устойчив к нестандартной разметке и предоставляет полный контроль над деревом документа.

Пример получения всех ссылок со страницы:


<?php
$html = file_get_contents('http://example.com');
$dom = new DOMDocument();
// Загрузка с подавлением ошибок невалидного HTML
libxml_use_internal_errors(true);
$dom->loadHTML($html);
libxml_clear_errors();
$xpath = new DOMXPath($dom);
$links = $xpath->query('//a/@href');
foreach ($links as $link) {
    echo $link->nodeValue . "\n";
}
?>
  
http://example.com/page1
http://example.com/page2
...
  

Пояснение: file_get_contents получает HTML, loadHTML загружает его в DOM, libxml_use_internal_errors позволяет игнорировать синтаксические ошибки, а DOMXPath::query выполняет XPath-запрос. Такой подход подходит для статических страниц и файлов, не требующих выполнения JavaScript.

Как выполнить простой парсинг с помощью регулярных выражений?

Для извлечения простых строк из HTML можно использовать preg_match или preg_match_all. Однако этот способ ненадёжен для сложной вложенной разметки.


<?php
$html = '<a href="page1">Ссылка 1</a>';
preg_match_all('/href="([^"]+)"/', $html, $matches);
print_r($matches[1]);
?>
  
Array
(
    [0] => page1
)
  

Возможные проблемы:

  • Регулярные выражения не учитывают структуру DOM, легко сломаться при изменении атрибутов или появлении лишних пробелов.
  • Не подходят для парсинга многоуровневых элементов (таблиц, списков).
  • Сложность отладки и поддержки.

Решение: использовать только для одноразовых задач или когда HTML строго контролируется (например, внутри одного проекта).

Как парсить HTML с использованием CSS-селекторов (библиотека DiDOM)?

Библиотека DiDOM (или Simple HTML DOM) позволяет выбирать элементы как в jQuery. Установка через Composer: composer require imangazaliev/didom.


<?php
require 'vendor/autoload.php';
use DiDom\Document;
$document = new Document('http://example.com', true);
$links = $document->find('a');
foreach ($links as $link) {
    echo $link->attr('href') . "\n";
}
?>
  
http://example.com/page1
http://example.com/page2
  

Возможные ошибки:

  • Зависимость от внешнего пакета.
  • При больших документах может потреблять много памяти.
  • Не все библиотеки корректно обрабатывают невалидный HTML (DiDOM справляется хорошо).

Рекомендуется для проектов, где нужен быстрый прототип с привычным синтаксисом селекторов.

Как загружать контент с учётом кук и заголовков (cURL)?

Для парсинга сайтов, требующих авторизации или с защитой от ботов, удобно использовать cURL. Пример с передачей кук и User-Agent:


<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://example.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
$html = curl_exec($ch);
curl_close($ch);
$dom = new DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML($html);
// ... обработка
?>
  

Типичные трудности:

  • Неверный User-Agent приводит к блокировке.
  • SSL-сертификаты (можно отключить проверку, но небезопасно).
  • Необходимость обработки редиректов (curl автоматически следует по умолчанию).

Решение: настраивать заголовки через curl_setopt и использовать пул прокси при массовом парсинге.

Как обрабатывать XML с помощью SimpleXML?

Для XML-данных (RSS, API) встроенный класс SimpleXML даёт удобный объектный доступ.


<?php
$xml = '<items><item id="1"><title>Запись</title></item></items>';
$sxml = simplexml_load_string($xml);
foreach ($sxml->item as $item) {
    echo $item['id'] . ': ' . $item->title . "\n";
}
?>
  
1: Запись
  

Возможные проблемы:

  • Не поддерживает XPath в полной мере (можно использовать SimpleXML::xpath).
  • Проблемы с пространствами имён (нужно регистрировать через children).

Решение: для сложных XML с неймспейсами лучше применять DOMDocument.

Общие проблемы парсинга в PHP и их решения:

  • Невалидный HTML – используйте libxml_use_internal_errors(true) и loadHTML.
  • Кодировка – явно указывайте meta charset в HTML или используйте mb_convert_encoding.
  • Таймауты – установите curl_setopt($ch, CURLOPT_TIMEOUT, 30).
  • Блокировка сервером – имитируйте поведение браузера (Cookie, User-Agent, задержки).
  • Динамический контент (JS) – PHP не выполняет JavaScript. Используйте headless браузер (Puppeteer) и вызывайте его через exec или API.

Цели и случаи использования:

  • Сбор данных с чужих сайтов (агрегаторы, мониторинг цен).
  • Миграция контента между системами.
  • Автоматическое тестирование веб-приложений.
  • Извлечение информации из API, возвращающих HTML.

Расширенные примеры парсинга в PHP

Пример 1. Парсинг таблицы с помощью DOMDocument и сохранение в CSV

Код:

Пример

<?php
$html = '
<table>
  <tr><th>Имя</th><th>Возраст</th></tr>
  <tr><td>Иван</td><td>25</td></tr>
  <tr><td>Мария</td><td>30</td></tr>
</table>';
$dom = new DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$rows = $xpath->query('//table/tr');
$csv = fopen('output.csv', 'w');
foreach ($rows as $rowIndex => $row) {
    $cols = $row->getElementsByTagName('td');
    if ($cols->length == 0) continue; // пропускаем заголовки
    $data = [];
    foreach ($cols as $col) {
        $data[] = trim($col->nodeValue);
    }
    fputcsv($csv, $data);
}
fclose($csv);
echo "Данные сохранены в output.csv";
?>

Результат (файл output.csv):

Иван,25
Мария,30

Пояснение: скрипт проходит по строкам таблицы, игнорирует строку с заголовками (th) и записывает содержимое ячеек в CSV. Этот подход полезен для сбора табличных данных с однотипных страниц.

Пример 2. Парсинг новостной ленты RSS с использованием cURL и SimpleXML

Код:

Пример

<?php
$url = 'http://example.com/rss';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0');
$data = curl_exec($ch);
curl_close($ch);
if ($data === false) {
    die('Ошибка загрузки RSS');
}
libxml_use_internal_errors(true);
$rss = simplexml_load_string($data);
if ($rss === false) {
    echo "Не удалось разобрать XML\n";
    foreach (libxml_get_errors() as $error) {
        echo $error->message . "\n";
    }
    libxml_clear_errors();
    exit;
}
foreach ($rss->channel->item as $item) {
    echo "Заголовок: " . $item->title . "\n";
    echo "Ссылка: " . $item->link . "\n\n";
}
?>

Результат:

Заголовок: Новость 1
Ссылка: http://example.com/news/1

Заголовок: Новость 2
Ссылка: http://example.com/news/2

Пояснение: комбинируем cURL для загрузки (учитывая возможную авторизацию) и SimpleXML для разбора. Обрабатываем ошибки разбора через libxml_get_errors.

Пример 3. Обработка невалидного HTML с исправлением структуры (DOMDocument + tidy)

Если HTML сильно повреждён, можно применить расширение tidy (если установлено).

Код:

Пример

<?php
$brokenHtml = '<p>Текст <b>жирный</i>';
$tidy = tidy_repair_string($brokenHtml, ['output-html' => true], 'utf8');
$dom = new DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML($tidy);
echo $dom->saveHTML();
?>

Результат (исправленный HTML):

<!DOCTYPE html>
<html>
<head></head>
<body><p>Текст <b>жирный</b></p></body>
</html>

Пояснение: tidy_repair_string автоматически закрывает теги и исправляет ошибки. После этого DOMDocument корректно разбирает документ. Метод полезен при работе с «грязными» источниками.

Пример 4. Асинхронный парсинг с использованием нескольких cURL-запросов (curl_multi)

Код:

Пример

<?php
$urls = ['http://example.com/page1', 'http://example.com/page2'];
$mh = curl_multi_init();
$channels = [];
foreach ($urls as $id => $url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_multi_add_handle($mh, $ch);
    $channels[$id] = $ch;
}
$running = null;
do {
    curl_multi_exec($mh, $running);
    curl_multi_select($mh);
} while ($running > 0);
foreach ($channels as $id => $ch) {
    $html = curl_multi_getcontent($ch);
    echo "Содержимое страницы $id: " . strlen($html) . " байт\n";
    curl_multi_remove_handle($mh, $ch);
    curl_close($ch);
}
curl_multi_close($mh);
?>

Результат:

Содержимое страницы 0: 4532 байт
Содержимое страницы 1: 3120 байт

Пояснение: curl_multi_exec выполняет запросы параллельно, что ускоряет сбор данных с нескольких страниц. Полезно при массовом парсинге, где каждый запрос не зависит от других.

Парсинг в PHP - comments

En
Parsing php (php)