Снова про phpQuery

Приветствую всех. Недавно я взял заказ, где нужно было автоматически подтягивать данные на сайт (проще говоря — написать парсер).

Содержание статьи:



Предисловие


Так как я работаю на 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();

Несмотря на удобство библиотеки, советую к ней не привыкать. Для решения мелких задач она подходит, наверное, лучше всех. Но это все же немного устаревшая библиотека.

В этой библиотеке есть возможность добавлять элементы «на лету». Но эту тему мы затронем в следующей статье.
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 35

    0

    Сдаётся мне, что большая часть задач просто решается с использованием штатного DOMXPath и, вероятнее всего, заметно быстрее. Да, синтаксис не как у CSS селекторов, но не намного сложнее. Потому библиотека, созданная 10 лет назад, и не обрела себе новой жизни.

      0

      Согласен. Но не каждый будет париться с DOMХpath, когда есть сиюминутное решение. Хотя я полностью поддерживаю изучение встроенной библиотеки. У неё, в отличии от phpQuery, есть какое-то будущее.

        0

        phpQuery это просто обёртка для стандартной библиотеки DOM.

          0

          В том же Chrome есть возможность скопировать XPath через панель разработчика. Так что париться не придется

            0
            xpath очень простой язык. Если его лень учить, можно dev mode браузера узнать xpath у любого элемента и все.
          +2
          А ещё есть DiDOM, современная, лёгкая и весьма шустрая библиотека. Можно даже на русском почитать.
            0

            Спасибо за совет!

            0
            Этот бесконечный
            pq($value)
            ужасно бесит, особенно, если нужно парсить вложенные элементы. DiDom гораздо удобнее для этого
              +5
              господи, неужели кто-то еще пользуется этой библиотекой? не, я понимаю лет эдак 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(),
                  ];    
              });


              Сильно сложно?
                –4

                Начнем с того, что пользоваться Symfony — уже огромная проблема. Сомневаюсь, что кто-то будет собирать маленький проект на Symfony. Да, согласен, есть новые библиотеки. Одну из них я в статье указал. Но, как мне кажется, phpQuery одна из самых легких для понимания.

                  +2
                  Не обязательно использовать Symfony целиком, можно надёргать нужных библиотек и подключить к своему проекту через композер.
                    –6

                    Может быть я легко отношусь к маленьким проектам, но разворачивать композер, подтягивать через него библиотеки. Немного наворочено. Хотя идея, соглашусь, здравая.

                      +2
                      Не совсем понимаю в чём проблема. Композер ставится глобально, библиотеки подключаются одной командой в консоли, которая копипастится из ридми репозитория, чаще всего. Дальше подключаете сформированный автолоадер в свой проект и пользуетесь. Не надо лазить по разным репозиториям, качать архивы, распаковывать, подключать. Банальная экономия времени.
                        –3

                        Замечательно. Давайте упакуем все это в докер, и, для полноты картины, будем следить за актуальностью БД с помощью доктрины. Тогда проект будет максимально удобным и максимально усложненным.
                        Повторюсь, я за использование актуальных библиотек. Но если уж кто-то услышал про phpQuery (а слышат о нем сейчас только новички, либо вспоминают старички), то почему бы не выдать нормальное объяснение с примером?
                        А потом эти люди спустятся в комментарии, увидят ваше объяснение про Crawler, заинтересуются им. Будут разворачивать замечательные проекты. Сплошные плюсы же.

                          0

                          вот вы любите все усложнять:)
                          тут к чему ведут, так это что установка пакета композером нисколько не тяжелей скачивания библиотеки руками.
                          три строки а консоли (хоть в linux, хоть в каком-нибудь OpenServer под windows) и можно начинать накидывать код. даже если представить что у кого-то еще остался шаред хост, без консоли и вот этого вот всего, то вам же все равно закидывать туда проект по какому-нибудь ftp, так какая разница это папка vendor или phpQuery? и какая разница, будете ли вы писать require 'vendor/autoload.php' в своём скрипте или require 'phpQuery/phpQuery.php'?

                            0
                            Но если уж кто-то услышал про phpQuery (а слышат о нем сейчас только новички, либо вспоминают старички), то почему бы не выдать нормальное объяснение с примером?

                            Потому что не надо уже. Есть более актуальные и изящные решения для ровно тех же задач.


                            Зачем новичкам открывать мир старого? Куда лучше учить их сразу хорошим практикам

                          +1
                          mkdir myproject
                          cd myproject
                          wget https://getcomposer.org/composer.phar
                          php composer.phar require symfony/dom-crawler
                          php composer.phar require symfony/css-selector


                          если использовать phpquery его же тоже надо скачать, разархивировать, подключить.
                            0
                            Композер подключить и пакет вытянуть (либу) это две строчки в командной строке. Во время работы скрипта Композер ни чего лишнего не грузит, кроме автозагрузчика.
                            То есть что я хочу сказать что Композер это не утяжеляет проект ни разу.
                              0

                              Я же не против. Ни разу, правда, так не делал.

                          +2
                          symfony/dom-crawler и symfony/css-selector — это два самобытных компонента, для жизни которых не нужна симфони. и вообще ничего не нужно, кроме стандартной DOM-библиотеки, на которой же и ваш phpQuery и основан.

                          phpQuery одна из самых легких для понимания.

                          очень субъективно.
                          на насколько, по-вашему, мой код вышел сложнее того, что вы привели в статье?
                          а еще она одна из самых тяжелых для исполнения. она тормозит и течет из всех щелей. попробуйте обойти несколько тысяч страниц.
                            0

                            Обходил ~ 3 тысячи страниц за ~ 4000-4500 секунд. Одна страница загружалась чуть больше, чем за секунду.
                            Не защищаю phpQuery. О её моральной старости написал в статье. Но она по-прежнему жива.

                            +1

                            Symfony это в первую очередь набор компонентов, а уж потом фреймворк. Вы всегда можете установить symfony/dom-crawler не устанавливая все остальное

                              +1
                              Мы и так отмыться от стереотипов никак не можем :( Так еще и динозавров выкапывают
                              0
                              интересное определение функции ) надеюсь это описка
                                0
                                да, в спешке набрасывал)
                                0
                                Я вообще регулярками все делаю, правда я не гуру в РНР.
                                Но, какая разница, если работает?
                                  +1
                                  неужели городить регулярку проще, чем написать что-то в духе
                                  (new Crawler($html))
                                      ->filter('.classname')
                                      ->first()
                                      ->attr('id')

                                    0
                                    Не проще, конечно, но как-то привычней. Возможно, просто дело в том, что большинство парсеров я писал на Perl… Как то пробовал разные РНР библиотеки, так ни на чем и не остановился. Тем более, современный РНР это уже совсем не то, что лет 10 назад. Композер и прочее, ради 2 строчек кода. Впрочем, это уже оффтоп, извините.
                                      0
                                      не ну само собой, что если нужно просто выдрать один атрибут или контент из тега, то регулярка сойдет.
                                      но посмотрите на пример из поста — там же регексп будет длинней, чем оставшийся код на php:)
                                      плюс DOM-парсеры более универсальное решение все же.
                                  0
                                  Тот случай когда комментарий к статье полезнее самой статьи.
                                  0
                                  Чем phpQuery лучше simplehtmldom?
                                    0

                                    На jQuery похож

                                    0
                                    Так как «пишу» и «писал» ранее много парсеров, то в своей работе использовал и simplehtmdom, затем по каким-то причинам перешёл на phpQuery (перешёл наверное из за того что сначала просто попробовал, а потом заметил кратное увеличение скорости работы парсреа) и она мне понравилась больше (она это библиотека). Совсем недавно попробовал для парсинга DomCrawler от Symfony и мне она по удобству показалась такой же как phpQuery. Даже сказал бы так что «phpQuery»==«DomCrawler» для разбора страниц.
                                    PS: Про удобство DomCrawler конечно же имею ввиду вкупе с css-selector пакетом
                                      0
                                      Сравнение с другими парсерами (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. Но он протекает и медленный.
                                        +2
                                        И все было бы чудесно, если бы не
                                        Function create_function() is deprecated in /phpquery/phpQuery/phpQuery/phpQueryObject.php


                                        В общем нет, спасибо, не надо.

                                        Only users with full accounts can post comments. Log in, please.