Парсинг данных на 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 выполняет запросы параллельно, что ускоряет сбор данных с нескольких страниц. Полезно при массовом парсинге, где каждый запрос не зависит от других.