Пример парсинга html-страницы на phpQuery

phpQuery – это удобный HTML парсер взявший за основу селекторы, фильтры и методы jQuery, которые позволяют манипулировать элементами HTML/XML и получать их содержимое.

В примерах используется страница карточки товара https://snipp.ru/demo/76/index.html, все спарсенные данные помещаются в общий массив $data.

Пример страницы товара в интернет-магазине
1

Пред началом нужно сделать некоторые настройки PHP.

Включение вывода ошибок PHP:

error_reporting(E_ALL);
ini_set('display_errors', 1);
PHP

Локаль:

setlocale(LC_ALL, 'ru_RU');
date_default_timezone_set('Europe/Moscow');
header('Content-type: text/html; charset=utf-8');
PHP

Подключаем phpQuery и открываем страницу сайта:

include_once __DIR__ . '/phpQuery.php';
$doc = phpQuery::newDocument(file_get_contents('https://snipp.ru/demo/76/index.html'));
PHP

Если кодировка сайта-донора отличается от вашей, возможно в спарсенных данных кириллица будет иероглифами. В таком случаи потребуется перекодировка, например:

$html = file_get_contents('https://snipp.ru/demo/76/index.html');
$html = iconv('utf-8//IGNORE', 'windows-1251//IGNORE', $html);

include_once __DIR__ . '/phpQuery.php';
$doc = phpQuery::newDocument($html);
PHP

Подробнее в статье «Перекодировка текста UTF-8 и WINDOWS-1251».

Ещё бывают случаи, когда file_get_contents возвращает полученный контент сжатый в GZIP, например:

�mw�Ƒ0�����&IkAI��f��j4/{�</�&�h�� ��({�񌝷o�����:/��<g���g��(�=�9�Paɭ�n�

Тогда нужно работать через CURL:

function getcontents($url){
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
	curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
	$output = curl_exec($ch);
	curl_close($ch);
	return $output;
}

include_once __DIR__ . '/phpQuery.php';
$doc = phpQuery::newDocument(getcontents('https://snipp.ru/demo/76/index.html'));
PHP
2

Рассмотрим базовые методы phpQuery на примере элементов из <head> страницы.

Заголовок страницы (title)

Метод find('selector') находит элементы по селектору, далее вызывается функция pq(), которая возвращает объект найденного элемента (подобно $ в jquery). К этому объекту можно применить методы, например text() – получить текстовое содержимое элемента.

$entry = $doc->find('title');
$data['title'] = pq($entry)->text();
echo $data['title'];
PHP

Результат:

Пример магазина - Электрогитара синего цвета Yamaha Pacifica012

Keywords и Description

Т.к. текст находится в атрибуте content="", используется метод attr().

$entry = $doc->find('head meta[name="keywords"]');
$data['keywords'] = pq($entry)->attr('content');
echo $data['keywords'];
 
$entry = $doc->find('head meta[name="description"]');
$data['description'] = pq($entry)->attr('content');
echo $data['description'];
PHP

Результат:

Электрогитара,Yamaha,Pacifica012
Интернет-магазин цифровой и бытовой техники

Несколько элементов

Для примера извлечём ссылки на CSS-файлы из тегов <link>.

$data['css'] = array();

$entry = $doc->find('head link[rel="stylesheet"]');
foreach ($entry as $row) {
	$data['css'][] = pq($row)->attr('href');
}

print_r($data['css']);
PHP

Результат:

Array
(
    [0] => style.css
    [1] => https://snipp.ru/cdn/fontawesome/5.11.2/css/all.css
)
3

На странице он должен быть один, поэтому вытаскиваем его по названию тега, как у <title>.

$entry = $doc->find('h1');
$data['h1'] = pq($entry)->text();
echo $data['h1'];
PHP

Результат:

Электрогитара синего цвета Yamaha Pacifica012
4

Обычно верстаются списком <ol> или <ul> с ссылками:

В контейнере с классом .breadcrumbs находим все ссылки и достанем их href и анкоры.

$data['breadcrumbs'] = array();

$entry = $doc->find('.breadcrumbs li a');
foreach ($entry as $row) {
	$ent = pq($row);
	$name = $ent->text();
	$url = $ent->attr('href');
	$data['breadcrumbs'][$name] = $url;
}

print_r($data['breadcrumbs']);
PHP

Результат:

Array 
(
	[Аудио] => /audio/
	[Музыкальные инструменты] => /audio/instruments/
	[Гитары и аксессуары] => /audio/instruments/guitar/
	[Yamaha] => /audio/instruments/guitar/yamaha
)
5

В данном случаи нужно только значение, c помощью метода remove() удаляется элемент <span> с текстом «Артикул».

$entry = $doc->find('.prod .prod-sku');
$entry->find('span')->remove();
$data['sku'] = trim(pq($entry)->text());

echo $data['sku'];
PHP

Результат:

45463786535
6

Цены выводятся с форматированием, его можно удалить регулярным выражением и привести значение к типу float или сразу применить PHP-функцию clean_price().

/* Старая цена - strike */
$entry = $doc->find('.price-price strike');
$val = pq($entry)->text();
$data['price_old'] = floatval(mb_ereg_replace('[^0-9.,]', '', $val));
var_dump($data['price_old']);

/* Старая цена - span */
$entry = $doc->find('.price-price span');
$val = pq($entry)->text();
$data['price'] = floatval(mb_ereg_replace('[^0-9.,]', '', $val));
var_dump($data['price']);
PHP

Результат:

float(17999)
float(15999)
7

Для оптимизации скорости, фото товаров в тегах <img> уменьшают и оборачивают их ссылками на оригинальные изображения.

Найдем все изображения в элементе .prod-left, далее методом parents('a') их ссылки.

$data['images'] = array();

$entry = $doc->find('.prod-left img');
foreach ($entry as $row) {
	$link = pq($row)->parents('a');
	$data['images'][] = pq($link)->attr('href');
}

print_r($data['images']);
PHP

Результат:

Array
(
    [0] => /demo/76/Yamaha-1.jpg
    [1] => /demo/76/Yamaha-2.jpg
    [2] => /demo/76/Yamaha-3.jpg
    [3] => /demo/76/Yamaha-4.jpg
)
8

Сложность парсинга списков определений заключается в том, что у нескольких <dt> и <dd> может быть один контейнер <dl>. Решение – по отдельности спарсить все dt и dd и слить их в один массив функцией array_combine().

/* dt */
$dt = array();
$entry = $doc->find('dl dt');
foreach ($entry as $row) {
	$dt[] = trim(pq($row)->text(), ':');
}

/* dl */
$dd = array();
$entry = $doc->find('dl dd');
foreach ($entry as $row) {
	$dd[] = pq($row)->text();
}

$data['attrs'] = array_combine($dt, $dd);
print_r($data['attrs']);
PHP

Результат:

Array
(
    [Товара в наличии] => 14шт
    [Доставка] => завтра
    [Гарантия] => 12 мес.
    [Страна-производитель] => Индонезия
)
9
$data['list'] = array();

$entry = $doc->find('ul.features li');
foreach ($entry as $row) {
	$data['list'][] = pq($row)->text();
}

print_r($data['list']);
PHP

Результат:

Array
(
    [0] => Универсальная шестиструнная электрогитара серии Pacifica
    [1] => Корпус superstrat из агатиса 
    [2] => Кленовый гриф с палисандровой накладкой
    [3] => Тремоло-бридж Vintage Tremolo 
    [4] => 22 лада 
    [5] => Хромированные колки 
    [6] => Два сингла и один хамбакер
)
10

Найдем все <tr> в таблице, далее в цикле найдем в них ячейки по порядковым номерам с помощью селекторов td:eq(0) и td:eq(1).

$data['table'] = array();

$entry = $doc->find('table.base-table tr');
foreach ($entry as $row) {
	$row = pq($row);
	$name = $row->find('td:eq(0)')->text();
	$value = $row->find('td:eq(1)')->text();
	$data['table'][$name] = $value;
}

print_r($data['table']);
PHP

Результат:

Array
(
    [Тип] => электрогитара
    [Материал корпуса] => агатис
    [Количество струн] => 6
    [Тип корпуса] => superstrat
    [Материал грифа] => клён
    [Накладка грифа] => палисандр
    [Радиус накладки грифа] => 350 мм
    [Мензура] => 648 мм (25,5’’)
    [Бридж] => Vintage Tremolo, тремоло, хромированный
    [Лады] => 22
    [Звукосниматели] => 2 сингла у грифа и в средней позиции + хамбакер у бриджа
    [Снятие] => HSS
    [Колки] => хром
    [Ориентация] => для правшей
    [Цвета] => белый, темно-синий металлик, красный металлик, чёрный
    [Аксессуары в комплекте] => нет
)
11

Найдем <div class="text">, удалим из него заголовок <h2> и получим html-содержимое.

$entry = $doc->find('div.text');
$entry->find('h2')->remove();
$data['text'] = pq($entry)->html();

echo $data['text'];
PHP

Результат:

<p>
	Разработанная на основе модели Pacifica 112, новая, еще более доступная по цене модель 012 
	характеризуется аналогичными контурами корпуса, мощным хамбакером в бриджевой позиции и 2 
	синглами, обеспечивающими чистое звучание. Гитара оснащена удобным грифом, 5-позиционным 
	переключателем выбора звукоснимателей и тремоло типа Vintage.
</p>
<p>
	Корпус: агатис. Гриф: привинченный, клен. Накладка грифа: сонокелинг, радиус 350 мм. Лады: 22. 
	Мензура: 648 мм. Звукосниматель: 2 сингла, 1 хамбакер. Регуляторы: 5-позиционный переключатель 
	звукоснимателей, мастер-громкость, тембр. Бридж: тремоло типа Vintage.
</p>		
18.03.2020, обновлено 22.09.2021
67045
Предыдущая запись Метрика API
Следующая запись Постраничный вывод массива

Комментарии 7

Андрей Мирный Андрей Мирный
14 июня 2022 в 20:47
Не все получилось.
На многих сайтах выводит такую ошибку:
Warning: file_get_contents(): SSL operation failed with code 1. OpenSSL Error messages: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed in
Андрей Мирный Андрей Мирный
14 июня 2022 в 21:04
Хотя заметил что работает на реальном сервере, а на локальном не всегда. Но штука полезная, решить бы вопрос тестирования на локалке.
Алек Садлер Алек Садлер
31 августа 2022 в 12:25
Делайте запрос через CURL, (есть в примере) а не file_get_contents() и будет вам счастье
Олег Ковалев Олег Ковалев
3 сентября 2022 в 17:04
Печально, но данная библеотека не работает на PHP8
Array and string offset access syntax with curly braces is deprecated
Олег Ковалев Олег Ковалев
3 сентября 2022 в 17:22
https://github.com/unit27/phpQuery/tree/master/phpQuery
Репозитарий поддерживающий php8
Олег Ковалев Олег Ковалев
4 сентября 2022 в 01:26
И еще момоент. Отключите в браузере JS и попробуйте открыть страницу, если нужного вам контента не будет, то библеотека, по моему опыту, не подойдет т.к. она не позвалает "выполнять" JavaScript.
Евгений Левачев Евгений Левачев
25 июля 2024 в 12:17
мне больше https://github.com/Imangazaliev/DiDOM нравится, намного мощнее библиотека
Да и сайты сейчас большенство делают на фронтенд фреймворках, где нет постоянных классов к которым можно привязываться, но, зато часто есть json в коде в котором почти все есть что нужно)

, чтобы добавить комментарий.

Другие публикации

Извлечение данных с помощью регулярных выражений PHP
Получение данных с помощью функций preg_match и preg_match_all.
42541
+10
Регулярные выражения для удаления тегов
Подборка регулярных выражений для удаления HTML тегов и атрибутов.
21510
+10
Перекодировка текста UTF-8 и WINDOWS-1251
Проблема кодировок часто возникает при написании парсеров, чтении данных из xml и csv файлов. Ниже представлены способы...
79914
-12
Запись в лог-файл в PHP
Несколько вариантов как быстро организовать запись данных в лог-файл.
87367
+13
Создание товарной накладной в PHPExcel
Пример, как сформировать товарную накладную с помощью библиотеки PHPExcel. В результате получится файл в формате xlsx...
20488
+8
Генерация счета на оплату PDF PHP
С помощью расширения dompdf можно легко сформировать PDF файл. По сути, dompdf - это конвертер HTML в PDF который...
70073
+34