Содержание статьи:
Предисловие
Так как я работаю на php, то мой взор пал на библиотеку phpQuery. Я, конечно, соглашусь, что есть множество других библиотек, в том числе и встроенная в php по умолчанию, но для рядового программиста, который подрабатывает фрилансом на выходных, нужно некое чудо. К счастью, всеми нами движет лень. Одного чеха лень привела к созданию phpQuery.
Документации на русском языке к данной библиотеке я не нашел (может быть плохо искал?). Найдя кучу вопросов от новичков на форумах, и не имея возможности прочитать документацию на английском, я задумался о написании этой статьи. Прошу учесть, что статья написана, в основном, для новичков.
Приступим
PhpQuery не самая быстрая библиотека, но одна из. С новыми версиями php она почти незаметна. Основная нагрузка, как и раньше, ложится на подгрузку страниц.
У неё полно возможностей, о которых не говорится во многих русскоязычных руководствах.
Некоторые программисты, так и не разобравшись с phpQuery, бегут создавать собственные библиотеки (прямо как наши коллеги из мира js). Да, у этой библиотеки есть главный недостаток — код устарел, но вполне себе работает.
Начало работы
Новичкам довольно сложно сходу понять работу phpQuery. Но я постараюсь максимально «разжевать» все сложные моменты.
Многие методы это библиотеки нацелены на работу с Dom, как будто мы работаем на jQuery. Да и названия у данных библиотек максимально похожи.
И так. Для начала нам нужно определиться с сайтом, с которого мы будем забирать HTML код. К слову, это не обязательно должен быть сайт. Если у нас уже есть html (xml) в файле (переменной), то можем подгрузить и оттуда.
/**
Если сайт:
$siteName = "site.com/";
Если файл:
$siteName = "index.html";
*/
$html = file_get_contents("$siteName");
Далее нам нужно передать полученный код обработчику phpQuery
$dom = phpQuery::newDocument($html);
Метод «newDocument()» вернет dom объект, с которым мы можем работать.
Теперь мы можем что-то найти в этом dom объекте. Давайте представим, что мы подтягиваем страничку сайта, где есть такой блок:
<div class="product-essential">
<a class="brand-link" href="https://какой-то_сайт.com/какой-то_бренд" title="Какой-то бренд">
<span class="brand-name">Какой-то бренд</span>
</a>
<div class="product-name">
<h1>Jeans Denim</h1>
</div>
<div class="price-info">
<div class="price-box">
<span class="regular-price" id="product-price-424337">
<span class="price">€ 200</span>
</span>
</div>
</div>
<div class="description">
<span class="product-description">Описание товара</span>
<div class="sku">
<span> ID продукта:</span>
<span>830214303</span>
</div>
</div>
</div>
В данном примере есть строчка со ссылкой на бренд, название бренда, название продукта, его описание, ID и цена.
Практическая часть
Попробуем получить все вышеперечисленные данные.
// Получаем код
$html = file_get_contents("https://какой-то_сайт.com/");
// Получаем объект dom
$dom = phpQuery::newDocument($html);
// Ищем в объекте dom элемент с классом .product-essential, обращаясь к методу find(). Он вмещает в себя все данные о продукте.
foreach($dom->find(".product-essential") as $key => $value){
// Преобразуем dom объект в объект phpQuery. Делаем сие действие с помощью метода pq(); который является аналогом ($) в jQuery.
$pq = pq($value);
// Находим в этом элементе элемент с классом .brand-link и получаем значение атрибута "href" с помощью метода attr();
$productHref[$key]["brand-href"] = $pq->find(".brand-link")->attr("href");
// Получаем название бренда. Оно находится в строке <span class="brand-name">Какой-то бренд</span>.
// Мы можем получить текст, содержащийся в <span> и других тегах с помощью метода text();
$productHref[$key]["brand-name"] = $pq->find(".brand-name")->text();
// Далее нам необходимо получить название товара.
// Помимо указания класса элемента, мы можем указать имя вложенного элемента.
// В данном случае имя бренда находится в элементе <h1>, который находится в элементе <div class="brand-name">
$productHref[$key]["product-name"] = $pq->find(".product-name h1")->text();
// PhpQuery позволяет перечислять классы нескольких, вложенных друг в друга, элементов.
// Только не забывайте следить за порядком!
// Тут мы получаем цену товара.
$productHref[$key]["product-price"] = $pq->find(".price-info .price-box .regular-price .price")->text();
// Получаем описание товара
$productHref[$key]["product-description"] = $pq->find(".description .product-description")->text();
// Так же есть возоможность шагать по элементам.
// Деется это с помощью метода next();
// В данном случае мы получим только числовой идентификатор без лишних строк.
$productHref[$key]["product-id"] = $pq->find(".description .sku span")->next()->text();
}
На выходе получаем вот такой массив:
Array
(
[0] => Array
(
[brand-href] => https://какой-то_сайт.com/какой-то_бренд
[brand-name] => Какой-то бренд
[product-name] => Jeans Denim
[product-price] => € 200
[product-description] => Описание товара
[product-id] => 830214303
)
)
Заключение
PhpQuery очень удобная библиотека, но, к сожалению, слишком тяжелая. Так что после прохода по элементам рекомендуется выгружать документ:
phpQuery::unloadDocuments();
Несмотря на удобство библиотеки, советую к ней не привыкать. Для решения мелких задач она подходит, наверное, лучше всех. Но это все же немного устаревшая библиотека.
В этой библиотеке есть возможность добавлять элементы «на лету». Но эту тему мы затронем в следующей статье.
Комментарии (33)
atcliff
15.10.2019 22:25Этот бесконечный
ужасно бесит, особенно, если нужно парсить вложенные элементы. DiDom гораздо удобнее для этогоpq($value)
NiceDay
15.10.2019 22:25+2господи, неужели кто-то еще пользуется этой библиотекой? не, я понимаю лет эдак 10 назад еще ладно, но мы ведь вроде в 2019ом, php развивается, сообщества перестают выкладывать говнокод и начали задумываться над качеством того, что они делают.
есть symfony/dom-crawler, который враппер для стандартной DOM библиотеки. И с помощью symfony/css-selector можно писать в без XPath, оно само конвертирует.
Только вдобавок в отличии от phpQuery оно еще и написано не на статических свойствах, которые хранят хрен пойми, включая прошлые документы и не течет как соломенная крыша.
// Получаем код $html = file_get_contents("https://какой-то_сайт.com/"); // Получаем объект dom $dom = phpQuery::newDocument($html); // Ищем в объекте dom элемент с классом .product-essential, обращаясь к методу find(). Он вмещает в себя все данные о продукте. foreach($dom->find(".product-essential") as $key => $value){ // Преобразуем dom объект в объект phpQuery. Делаем сие действие с помощью метода pq(); который является аналогом ($) в jQuery. $pq = pq($value); // Находим в этом элементе элемент с классом .brand-link и получаем значение атрибута "href" с помощью метода attr(); $productHref[$key]["brand-href"] = $pq->find(".brand-link")->attr("href"); // Получаем название бренда. Оно находится в строке <span class="brand-name">Какой-то бренд</span>. // Мы можем получить текст, содержащийся в <span> и других тегах с помощью метода text(); $productHref[$key]["brand-name"] = $pq->find(".brand-name")->text(); // Далее нам необходимо получить название товара. // Помимо указания класса элемента, мы можем указать имя вложенного элемента. // В данном случае имя бренда находится в элементе <h1>, который находится в элементе <div class="brand-name"> $productHref[$key]["product-name"] = $pq->find(".product-name h1")->text(); // PhpQuery позволяет перечислять классы нескольких, вложенных друг в друга, элементов. // Только не забывайте следить за порядком! // Тут мы получаем цену товара. $productHref[$key]["product-price"] = $pq->find(".price-info .price-box .regular-price .price")->text(); // Получаем описание товара $productHref[$key]["product-description"] = $pq->find(".description .product-description")->text(); // Так же есть возоможность шагать по элементам. // Деется это с помощью метода next(); // В данном случае мы получим только числовой идентификатор без лишних строк. $productHref[$key]["product-id"] = $pq->find(".description .sku span")->next()->text(); }
а можно было вот так:
$html = file_get_contents("https://какой-то_сайт.com/"); $crawler = (new Crawler($html)); $productHref = $crawler->filter('.product-essential')->each($node) { return [ 'brand-href' => $node->filter('.brand-link')->first()->attr('href'), 'brand-name' => $node->filter('.brand-name')->first()->text(), 'product-name' => $node->filter('.product-name h1')->first()->text(), 'product-price' => $node->filter('.price-info .price-box .regular-price .price')->text(), 'product-description' => $node->filter('.description .product-description')->text(), 'product-id' => $node->filter('.description .sku span')->eq(1)->text(), ]; });
Сильно сложно?gjnext Автор
15.10.2019 22:31-1Начнем с того, что пользоваться Symfony — уже огромная проблема. Сомневаюсь, что кто-то будет собирать маленький проект на Symfony. Да, согласен, есть новые библиотеки. Одну из них я в статье указал. Но, как мне кажется, phpQuery одна из самых легких для понимания.
DarkPreacher
15.10.2019 22:36+1Не обязательно использовать Symfony целиком, можно надёргать нужных библиотек и подключить к своему проекту через композер.
gjnext Автор
15.10.2019 22:44-2Может быть я легко отношусь к маленьким проектам, но разворачивать композер, подтягивать через него библиотеки. Немного наворочено. Хотя идея, соглашусь, здравая.
DarkPreacher
15.10.2019 22:52+1Не совсем понимаю в чём проблема. Композер ставится глобально, библиотеки подключаются одной командой в консоли, которая копипастится из ридми репозитория, чаще всего. Дальше подключаете сформированный автолоадер в свой проект и пользуетесь. Не надо лазить по разным репозиториям, качать архивы, распаковывать, подключать. Банальная экономия времени.
gjnext Автор
15.10.2019 23:17-1Замечательно. Давайте упакуем все это в докер, и, для полноты картины, будем следить за актуальностью БД с помощью доктрины. Тогда проект будет максимально удобным и максимально усложненным.
Повторюсь, я за использование актуальных библиотек. Но если уж кто-то услышал про phpQuery (а слышат о нем сейчас только новички, либо вспоминают старички), то почему бы не выдать нормальное объяснение с примером?
А потом эти люди спустятся в комментарии, увидят ваше объяснение про Crawler, заинтересуются им. Будут разворачивать замечательные проекты. Сплошные плюсы же.NiceDay
16.10.2019 00:20вот вы любите все усложнять:)
тут к чему ведут, так это что установка пакета композером нисколько не тяжелей скачивания библиотеки руками.
три строки а консоли (хоть в linux, хоть в каком-нибудь OpenServer под windows) и можно начинать накидывать код. даже если представить что у кого-то еще остался шаред хост, без консоли и вот этого вот всего, то вам же все равно закидывать туда проект по какому-нибудь ftp, так какая разница это папка vendor или phpQuery? и какая разница, будете ли вы писать require 'vendor/autoload.php' в своём скрипте или require 'phpQuery/phpQuery.php'?
trawl
16.10.2019 04:14Но если уж кто-то услышал про phpQuery (а слышат о нем сейчас только новички, либо вспоминают старички), то почему бы не выдать нормальное объяснение с примером?
Потому что не надо уже. Есть более актуальные и изящные решения для ровно тех же задач.
Зачем новичкам открывать мир старого? Куда лучше учить их сразу хорошим практикам
NiceDay
15.10.2019 22:58mkdir myproject cd myproject wget https://getcomposer.org/composer.phar php composer.phar require symfony/dom-crawler php composer.phar require symfony/css-selector
если использовать phpquery его же тоже надо скачать, разархивировать, подключить.
SbWereWolf
16.10.2019 15:30Композер подключить и пакет вытянуть (либу) это две строчки в командной строке. Во время работы скрипта Композер ни чего лишнего не грузит, кроме автозагрузчика.
То есть что я хочу сказать что Композер это не утяжеляет проект ни разу.
NiceDay
15.10.2019 22:38+1symfony/dom-crawler и symfony/css-selector — это два самобытных компонента, для жизни которых не нужна симфони. и вообще ничего не нужно, кроме стандартной DOM-библиотеки, на которой же и ваш phpQuery и основан.
phpQuery одна из самых легких для понимания.
очень субъективно.
на насколько, по-вашему, мой код вышел сложнее того, что вы привели в статье?
а еще она одна из самых тяжелых для исполнения. она тормозит и течет из всех щелей. попробуйте обойти несколько тысяч страниц.gjnext Автор
15.10.2019 22:50Обходил ~ 3 тысячи страниц за ~ 4000-4500 секунд. Одна страница загружалась чуть больше, чем за секунду.
Не защищаю phpQuery. О её моральной старости написал в статье. Но она по-прежнему жива.
baldrs
16.10.2019 08:50Symfony это в первую очередь набор компонентов, а уж потом фреймворк. Вы всегда можете установить
symfony/dom-crawler
не устанавливая все остальное
pOmelchenko
16.10.2019 17:41Мы и так отмыться от стереотипов никак не можем :( Так еще и динозавров выкапывают
morgot
16.10.2019 16:03Я вообще регулярками все делаю, правда я не гуру в РНР.
Но, какая разница, если работает?NiceDay
16.10.2019 16:59+1неужели городить регулярку проще, чем написать что-то в духе
(new Crawler($html)) ->filter('.classname') ->first() ->attr('id')
morgot
17.10.2019 02:34Не проще, конечно, но как-то привычней. Возможно, просто дело в том, что большинство парсеров я писал на Perl… Как то пробовал разные РНР библиотеки, так ни на чем и не остановился. Тем более, современный РНР это уже совсем не то, что лет 10 назад. Композер и прочее, ради 2 строчек кода. Впрочем, это уже оффтоп, извините.
NiceDay
17.10.2019 17:57не ну само собой, что если нужно просто выдрать один атрибут или контент из тега, то регулярка сойдет.
но посмотрите на пример из поста — там же регексп будет длинней, чем оставшийся код на php:)
плюс DOM-парсеры более универсальное решение все же.
northmule
16.10.2019 08:20Так как «пишу» и «писал» ранее много парсеров, то в своей работе использовал и simplehtmdom, затем по каким-то причинам перешёл на phpQuery (перешёл наверное из за того что сначала просто попробовал, а потом заметил кратное увеличение скорости работы парсреа) и она мне понравилась больше (она это библиотека). Совсем недавно попробовал для парсинга DomCrawler от Symfony и мне она по удобству показалась такой же как phpQuery. Даже сказал бы так что «phpQuery»==«DomCrawler» для разбора страниц.
PS: Про удобство DomCrawler конечно же имею ввиду вкупе с css-selector пакетом
Oniry
16.10.2019 14:23Сравнение с другими парсерами (1.6.3)
Потребления памяти (в байтах)
Максимальное
Nokogiri — 763568
DiDom — 793096
Zend Dom — 954712
DomCrawler — 1534512
Simple HTML DOM — 16839400
В конце теста
Nokogiri — 157168
DiDom — 158896
Zend Dom — 329232
DomCrawler — 567440
Simple HTML DOM — 14113456
Затраченное время (в секундах)
DiDom — 27.0787
Nokogiri — 27.1009
DomCrawler — 36.0982
Zend Dom — 48.3222
Simple HTML DOM — 188.0247
1 в поисковиках вылазит в большом количестве Simple. Но он протекает и медленный.
alexxz
Сдаётся мне, что большая часть задач просто решается с использованием штатного DOMXPath и, вероятнее всего, заметно быстрее. Да, синтаксис не как у CSS селекторов, но не намного сложнее. Потому библиотека, созданная 10 лет назад, и не обрела себе новой жизни.
gjnext Автор
Согласен. Но не каждый будет париться с DOMХpath, когда есть сиюминутное решение. Хотя я полностью поддерживаю изучение встроенной библиотеки. У неё, в отличии от phpQuery, есть какое-то будущее.
NiceDay
phpQuery это просто обёртка для стандартной библиотеки DOM.
BoShurik
В том же Chrome есть возможность скопировать XPath через панель разработчика. Так что париться не придется
den_rad
xpath очень простой язык. Если его лень учить, можно dev mode браузера узнать xpath у любого элемента и все.