phpQuery – это удобный HTML парсер взявший за основу селекторы, фильтры и методы jQuery, которые позволяют манипулировать элементами HTML/XML и получать их содержимое.
В примерах используется страница карточки товара https://snipp.ru/demo/76/index.html, все спарсенные данные помещаются в общий массив $data
.
Пред началом нужно сделать некоторые настройки PHP.
Включение вывода ошибок PHP:
Локаль:
setlocale(LC_ALL, 'ru_RU');
date_default_timezone_set('Europe/Moscow');
header('Content-type: text/html; charset=utf-8');
Подключаем phpQuery и открываем страницу сайта:
include_once __DIR__ . '/phpQuery.php';
$doc = phpQuery::newDocument(file_get_contents('https://snipp.ru/demo/76/index.html'));
Если кодировка сайта-донора отличается от вашей, возможно в спарсенных данных кириллица будет иероглифами. В таком случаи потребуется перекодировка, например:
$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);
Подробнее в статье «Перекодировка текста 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'));
Рассмотрим базовые методы phpQuery на примере элементов из <head>
страницы.
Заголовок страницы (title)
Метод find('selector')
находит элементы по селектору, далее вызывается функция pq()
, которая возвращает объект найденного элемента (подобно $ в jquery). К этому объекту можно применить методы, например text()
– получить текстовое содержимое элемента.
Результат:
Пример магазина - Электрогитара синего цвета 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'];
Результат:
Электрогитара,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']);
Результат:
Array
(
[0] => style.css
[1] => https://snipp.ru/cdn/fontawesome/5.11.2/css/all.css
)
На странице он должен быть один, поэтому вытаскиваем его по названию тега, как у <title>
.
Результат:
Электрогитара синего цвета Yamaha Pacifica012
Обычно верстаются списком <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']);
Результат:
Array
(
[Аудио] => /audio/
[Музыкальные инструменты] => /audio/instruments/
[Гитары и аксессуары] => /audio/instruments/guitar/
[Yamaha] => /audio/instruments/guitar/yamaha
)
В данном случаи нужно только значение, c помощью метода remove()
удаляется элемент <span>
с текстом «Артикул».
$entry = $doc->find('.prod .prod-sku');
$entry->find('span')->remove();
$data['sku'] = trim(pq($entry)->text());
echo $data['sku'];
Результат:
45463786535
Цены выводятся с форматированием, его можно удалить регулярным выражением и привести значение к типу 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']);
Результат:
float(17999)
float(15999)
Для оптимизации скорости, фото товаров в тегах <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']);
Результат:
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
)
Сложность парсинга списков определений заключается в том, что у нескольких <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']);
Результат:
Array
(
[Товара в наличии] => 14шт
[Доставка] => завтра
[Гарантия] => 12 мес.
[Страна-производитель] => Индонезия
)
$data['list'] = array();
$entry = $doc->find('ul.features li');
foreach ($entry as $row) {
$data['list'][] = pq($row)->text();
}
print_r($data['list']);
Результат:
Array
(
[0] => Универсальная шестиструнная электрогитара серии Pacifica
[1] => Корпус superstrat из агатиса
[2] => Кленовый гриф с палисандровой накладкой
[3] => Тремоло-бридж Vintage Tremolo
[4] => 22 лада
[5] => Хромированные колки
[6] => Два сингла и один хамбакер
)
Найдем все <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']);
Результат:
Array
(
[Тип] => электрогитара
[Материал корпуса] => агатис
[Количество струн] => 6
[Тип корпуса] => superstrat
[Материал грифа] => клён
[Накладка грифа] => палисандр
[Радиус накладки грифа] => 350 мм
[Мензура] => 648 мм (25,5’’)
[Бридж] => Vintage Tremolo, тремоло, хромированный
[Лады] => 22
[Звукосниматели] => 2 сингла у грифа и в средней позиции + хамбакер у бриджа
[Снятие] => HSS
[Колки] => хром
[Ориентация] => для правшей
[Цвета] => белый, темно-синий металлик, красный металлик, чёрный
[Аксессуары в комплекте] => нет
)
Найдем <div class="text">
, удалим из него заголовок <h2>
и получим html-содержимое.
$entry = $doc->find('div.text');
$entry->find('h2')->remove();
$data['text'] = pq($entry)->html();
echo $data['text'];
Результат:
<p>
Разработанная на основе модели Pacifica 112, новая, еще более доступная по цене модель 012
характеризуется аналогичными контурами корпуса, мощным хамбакером в бриджевой позиции и 2
синглами, обеспечивающими чистое звучание. Гитара оснащена удобным грифом, 5-позиционным
переключателем выбора звукоснимателей и тремоло типа Vintage.
</p>
<p>
Корпус: агатис. Гриф: привинченный, клен. Накладка грифа: сонокелинг, радиус 350 мм. Лады: 22.
Мензура: 648 мм. Звукосниматель: 2 сингла, 1 хамбакер. Регуляторы: 5-позиционный переключатель
звукоснимателей, мастер-громкость, тембр. Бридж: тремоло типа Vintage.
</p>
На многих сайтах выводит такую ошибку:
Array and string offset access syntax with curly braces is deprecated
Репозитарий поддерживающий php8
Да и сайты сейчас большенство делают на фронтенд фреймворках, где нет постоянных классов к которым можно привязываться, но, зато часто есть json в коде в котором почти все есть что нужно)