Пользователь
0,0
рейтинг
14 апреля 2013 в 18:25

Разработка → PHP HTML DOM парсер с jQuery подобными селекторами recovery mode

PHP*
Добрый день, уважаемые хабровчане. В данном посте речь пойдет о совместном проекте S. C. Chen и John Schlick под названием PHP Simple HTML DOM Parser (ссылки на sourceforge).

Идея проекта — создать инструмент позволяющий работать с html кодом используя jQuery подобные селекторы. Оригинальная идея принадлежит Jose Solorzano's и реализована для php четвертой версии. Данный же проект является более усовершенствованной версией базирующейся на php5+.

В обзоре будут представлены краткие выдержки из официального мануала, а также пример реализации парсера для twitter. Справедливости ради, следует указать, что похожий пост уже присутствует на habrahabr, но на мой взгляд, содержит слишком малое количество информации. Кого заинтересовала данная тема, добро пожаловать под кат.

Получение html кода страницы

$html = file_get_html('http://habrahabr.ru/'); //работает и с https://

Товарищ Fedcomp дал полезный комментарий насчет file_get_contents и 404 ответа. Оригинальный скрипт при запросе к 404 странице не возвращает ничего. Чтобы исправить ситуацию, я добавил проверку на get_headers. Доработанный скрипт можно взять тут.
Поиск элемента по имени тега

foreach($html->find('img') as $element) { //выборка всех тегов img на странице
       echo $element->src . '<br>'; // построчный вывод содержания всех найденных атрибутов src
}

Модификация html элементов

$html = str_get_html('<div id="hello">Hello</div><div id="world">World</div>'); // читаем html код из строки (file_get_html() - из файла)
$html->find('div', 1)->class = 'bar'; // присвоить элементу div с порядковым номером 1 класс "bar"
$html->find('div[id=hello]', 0)->innertext = 'foo'; // записать в элемент div с id="hello" текст foo

echo $html; // выведет <div id="hello">foo</div><div id="world" class="bar">World</div>

Получение текстового содержания элемента (plaintext)

echo file_get_html('http://habrahabr.ru/')->plaintext; 


Целью статьи не является предоставить исчерпывающую документацию по данному скрипту, подробное описание всех возможностей вы можете найти в официальном мануале, если у сообщества возникнет желание, я с удовольствием переведу весь мануал на русский язык, пока же приведу обещанный в начале статьи пример парсера для twitter.

Пример парсера сообщений из twitter

require_once 'simple_html_dom.php'; // библиотека для парсинга
            $username = 'habrahabr'; // Имя в twitter
            $maxpost = '5'; // к-во постов
            $html = file_get_html('https://twitter.com/' . $username);
            $i = '0';
            foreach ($html->find('li.expanding-stream-item') as $article) { //выбираем все li сообщений
                $item['text'] = $article->find('p.js-tweet-text', 0)->innertext; // парсим текст сообщения в html формате
                $item['time'] = $article->find('small.time', 0)->innertext; // парсим время в html формате
                $articles[] = $item; // пишем в массив
                $i++;
                if ($i == $maxpost) break; // прерывание цикла
            }


Вывод сообщений

                for ($j = 0; $j < $maxpost; $j++) {
                    echo '<div class="twitter_message">';
                    echo '<p class="twitter_text">' . $articles[$j]['text'] . '</p>';
                    echo '<p class="twitter_time">' . $articles[$j]['time'] . '</p>';
                    echo '</div>';
                }


Благодарю за внимание. Надеюсь, получилось не очень тяжеловесно и легко для восприятия.

Похожие библиотеки

htmlSQL — спасибо Chesnovich
Zend_Dom_Query — спасибо majesty
phpQuery — спасибо theRavel
QueryPath — спасибо ZonD80
The DomCrawler(Symfony) — спасибо choor
CDom — спасибо автору samally
Небезызвестный XPath — спасибо за напоминание KAndy

P.S.
Хаброжитель Groove подсказал что подобные материалы уже были
P.P.S.
Постараюсь в свободное время собрать все библиотеки и составить сводные данные по производительности и приятности использования.
Федор Майоров @maeln0r
карма
1,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (80)

  • +1
    А вот ещё интересная библиотека htmlSQL(https://github.com/hxseven/htmlSQL), позволяет работать с dom-элементами с помощью запроса напоминающего SQL

    Например, SELECT href,title FROM a WHERE $class == «list»
    href, title — вернуть атрибуты
    a — в каких тегах искать
    $class == «list» — аттрибут class=«list»
    • 0
      Спасибо, добавлю в конец статьи. Если у кого-то есть еще примеры, пишите, может получиться интересная подборка.
      • +1
        DomCrawler компонент от Symfony
        symfony.com/doc/current/components/dom_crawler.html
        • +1
          вот я… чудак, прикрутил simple html dom в качестве сервиса недавно, хотя есть штатное средство (

          спасибо
  • +3
    framework.zend.com/manual/1.12/en/zend.dom.introduction.html — на самом деле jQuery использует CSS-селекторы.
    • +1
      Изначально поэтому и полюбился мне jQuery, хотя у них не только заимствованные из css селекторы есть, но в основном вы абсолютно правы:

      Borrowing from CSS 1–3, and then adding its own, jQuery offers a powerful set of tools for matching a set of elements in a document.

      И спасибо за ссылку на zend.
  • 0
    Увы, уже было
    Из-за «recovery mode» голосовать за топик не стал.
    • –2
      habrahabr.ru/post/30781/ — еще в 2008 было.
      • 0
        Я отмечал это в самом начале топика.
        Справедливости ради, следует указать, что похожий пост уже присутствует на habrahabr, но на мой взгляд, содержит слишком малое количество информации

  • +3
    Еще есть похожая phpQuery
    • +1
      Причем в использовании она пожалуй удобнее, ближе к jQuery.
      Например:
      $pq->find('Item#id')->text();
      $pq->find('Item#id')->attr('class', 'new-class');
      $pq->find('Item#id')->children('.sub-item')->html();
      

      Интересно было бы услышать от автора о преимуществах PHP Simple HTML DOM Parser перед phpQuery.
      • 0
        Посмотрю и ближе к вечеру отпишусь из видимых только то, что phpQuery остановился в развитии в 2009 году судя по гуглкоду. Про сам код пока ничего сказать не могу.
      • 0
        simple html dom в сущности поддерживает тоже самое только названия другие.
    • 0
      Тут разница что там Pear, тут либа которая качается с каждым проектом. Зачастую это важно.
  • 0
    Автор пожалуйста упомяните в статье что file_get_contents() не лучший способ получать html со странички, он достаточно часто не работает на многих сайтах, к тому же вы не сможете получить http код, самый яркий пример когда вам выдало 404 страничку.

    Можно использовать например curl. Лично я использую в простых проектах эту простую функцию: gist.github.com/Fedcomp/5383203 (возможный быдлокод)
    • 0
      В PHP Simple HTML DOM Parser предусмотрели этот момент)
      • 0
        Если вы имеете ввиду file_get_html то вы не правы: i.imgur.com/Za3Pea8.png
        Если вы говорите про str_get_html то я про него и говорил. Скачать через курл и передать классу в виде строки.
        Возможно вы имели ввиду что то еще?
        • 0
          Ошибся, каюсь. На данный момент file_get_html не отдает вообще ничего с 404 страницы. Добавил проверку по заголовку страницы в скрипт. Спасибо за замечание.
        • 0
          Добавил проверку по get_headers. Новый скрипт можно взять тут
    • +3
      Насчет «часто не работает на многих сайтах» не знаю, но вот в остальном эта функция хорошо настраивается: www.php.net/manual/ru/context.php
      и 404-ю получить можно и даже редиректы отменить и пр. Конечно это не отменяет заслуг curl, но и так категорично говорить о file_get_contents я бы не стал.
      • 0
        Посыпаю голову пеплом, я не знал про эту функциональность.
  • +1
    В своём проекте использую. Не устраивала было скорость, решил сравнить на одной и той же странице:
    1. simple_html_dom — 5.5 сек
    2. nokogiri — 0.07 не работают выборки типа tr[id^=someid] и прочие сложные, нужные мне. Не подошло
    3. ganon — 68 сек (!) — увы.
    4. встроенный XML DOM не позволяет делать хоть сколько нибудь сложные выборки,
    как например в simple_html_dom ($item->find('p[class=one-product-description-title] a',0)
    5. htmlSQL — 0.09 сек. — неплохо, но результаты выборки не являются объектами как в simple_html_dom, что критично.

    Остался на simple_html_dom.
    • +1
      А phpQuery не тестировали?
    • 0
      Если парсить тот же твиттер (или подобные этому задачи рассматривать) можно кешировать и скрипт по сути будет отрабарывать раз в 10-30 минут. А вот для каких то более сложных задач думаю будет заметно. Сегодня попробую потестировать все библиотеки на страничке rbc, например.
      • 0
        У твиттера есть же апи?
        • 0
          Последние нововведения в API твиттера меня не радуют. Мне проще спарсить то, что нужно самому.
    • 0
      А какие селекторы юзаете? и как много? может дело не в парсере, а где то утечка производительности?
      • 0
        Тут лаги происходят не во время выборок (они быстро делаются) а во время загрузки HTML в парсер.
    • 0
      Сейчас разрабатываю вторую версию своей старой DOM библиотеки. Интересно мнение тех, кто использует альтернативные. Если будет время/возможность, посмотрите пожалуйста — работает ли моя разработка в вашем случае. Сложные и даже очень сложные селекторы есть в функционале.

      Описал библиотеку и функционал подробнее ниже.
    • +3
      встроенный XML DOM не позволяет делать хоть сколько нибудь сложные выборки

      О чем это вы?
      встроенный XML DOM позволяет делать выборки любой сложности и намного гибче CSS
      • 0
        Ну вот у меня есть выборка типа: $trs = $this->dom->find('tr[id^=row]');
        Разве XML DOM так может (имею в виду id^=row)?
        • +4
          Конечно может, учитывая что большенство библиотек трансформируют CSS в XPATH и выполняют его.
          Например используя SimpleXml это будет выглядить приблизительно так:
          $xml = simplexml_load_string($xml);
          $trRow = $xml->xpath('//tr[starts-with(@id, "row")]');
          

        • +1
          Ну совсем ппц. Ну откройте так любимые PHP'шниками доки и посмотрите список классов модуля. Потом почитайте зачем каждый из них нужен. Я давно уже считаю, что Хабр деградировал, но что настолько…
        • +1
          Он может даже даже в стандартные XPath выражения добавлять свои функции.

          Лучше XPath пока ни чего не видел. Активно использую его в парсере html страниц каталога товаров.
    • 0
      Говорят tidy самый быстрый (хорошо бы добавить в бенч), так как в виде Extension на C, но возможно некоторых вещей он не умеет.
      • 0
        раз сделан как расширение — есть проблема кроссплатформенности (на домашнем win — на продакшене Linux)
        • 0
          Это же стандартно есть в PECL везде.
  • 0
    Товарищи, вот монстр по парсингу, использую его в нескольких проектах: querypath.org/
  • 0
    однажды нужно было выдрать из буферизированного html блок с определенным классом. Описанная в посте библиотека потерпела фиаско, как и упомянутый выше querypath, справилась только, опять же, упомянутая выше phpQuery. Так что рекомендовать могу только её, остальные у меня уже доверия не вызывают
    • 0
      А можно конкретный пример если не сложно?
      • –1
        $class = ".".trim($_GET['select']);
        $html = phpQuery::newDocument($APPLICATION->EndBufferContent());
        $params['html'] = pq($class)->htmlOuter();
        

        в $_GET['select'] приходит требуемый класс. Simple HTML DOM Parser ничего не находил, как и querypath
  • +5
    Есть еще моя старая библиотечка: github.com/amal/CDom

    • jQuery подобное API
    • Автоматическое определение кодировки. Умеет читать кодировку указанную в самом html и некоторую эвристику для остальных случаев (не идеальное, но работает)
    • Поддерживает XML/HTML любой степени кривизны и поврежденности (насколько вообще возможно поддерживать)
    • Полная поддержка CSS3 селекторов и дополнительных «jQuery selector extensions»
    • Возможности по манипуляции DOM и экспорту результата
    • Умеет экспортировать HTML в текст с учетом блочных тегов, переводов строк и т.п.
    • Код покрыт тестами
    • Может работать также с простым BBCode и другими HTML подобными разметками

    Сейчас как раз работаю над второй версией (с composer, PSR-0, переработанным парсером и прочими плюшками). Скорее всего напишу сравнительно-обзорную статью на хабре, как закончу.
  • +4
    Кто то обьяснит, почему используются библиотеки для имуляции css, вместо более продвинутого, скоростного и нативного xpath?
    • –1
      Для тех, кто уже знает CSS или jQuery-селекторы, XPath — это ещё один язык, который надо изучать, и который, к тому же, теряет популярность: www.google.com/trends/explore#q=xpath
      • +1
        Ну во-первых, он очень прост — так что если вдруг кто то его не знает то лучше потратить час два на изучение
        Во-вторых для PHP существует SimpleXML API которое позврляет сдельть все тоже, и так само выразительно, без внешних зависимостей

        Имхо, CSS это вообще сплошное недорозумение

      • +2
        Он очень легко и просто изучается. Буквально осенью привлекал фронта на написание XPath выражений для парсера каталогов. Человек не знающий что такое DOM API, не представляющий что есть XML и ни разу не видевший XPath через 2 часа уже писал первые выражения для парсера. Еще где-то часа 2 ушло на получение требуемых выражений. Конечно, это потребовало не самостоятельного изучения, а я объяснял общий концепт и ткнул в нужные места спецификации. Но человек на следующий день уже сам делал следующий парсер, а я лишь контролировал конечный результат.

        Так что… я могу утверждать, что Xpath это достаточно легко осваиваемая идея.
    • 0
      С XPath все отлично, но вот PHP DOM имеет ряд серьезных недостатков, как то неудобное API (собственно, как и в браузерах, из-за чего в свое время набрал популярность jQuery), плохая поддержка html (на самом деле плохая, я встречал сотни html документов, которые не мог обработать DOMDocument/SimpleXML), фактическая невозможность получить на выходе все тот же html (например выбрать конкретные узлы и получить их код ТОЧНО как в исходном документе).
      • 0
        неудобное API
        — дело вкуса и всегда можна сделать обертку
        плохая поддержка html
        — с этим могу частично согласиться, так как действтельно все плохо с невалидным html. И это скорей связано с тем что все браузеры/движки разбора html востанавливали его поразному, пока не появился стандарт разбора в html5.
        получить их код ТОЧНО как в исходном документе
        — вполне возможно, вот только интересно зачем это вам?
        • 0
          плохая поддержка html

          Точного стандарта нет, но как разбирать вполне понятно. Даже хоть как нибудь было неплохо. Но как уже писал, встречал сотни html документов, которые вообще не мог обработать DOMDocument/SimpleXML. Целые куски обрабатывались некорректно, как-то корежились… в общем ад. При этом своей библиотекой или simple html dom обрабатывал спокойно.

          получить их код ТОЧНО как в исходном документе

          С этим встречался в очень многих задачах. XML DOM иной раз меняет исходный код достаточно сильно, «достраивая» код до валидного XML, что совершенно неприемлемо. Сделать то что-то можно при желании, но только с обертками и кучей костылей, что опять же не вариант. Нужен именно HTML DOM, причем заточенный под задачи парсера, а не рендеринга HTML (т.е. достраивать вообще что либо не нужно, надо обрабатывать как есть).
  • 0
    Уже несколько лет использую для парсинга. Очень удобно/быстро/мало кода!

    xdan.ru/Uchimsya-parsit-saity-s-bibliotekoi-PHP-Simple-HTML-DOM-Parser.html не плохое описание с примерами.
    • 0
      Подтверждамс — при вкуривании (небольшого) мануала — работа с библиотекой лаконична.
      Недостатки, повторюсь: медленный парсинг (не выборка) и утечки памяти, которые вроде в посдедней версии решены.
      • 0
        там вроде были методы для очистки памяти.
  • 0
    1. Т.к. парсер целиком на PHP, то работать будет медленно и памяти будет есть заметно больше, чем какой-нибудь биндинг к libxml2.

    2. Зачем-то без разрешения вырезает script, CDATA, style и ещё много информации
    php > require '/tmp/simple_html_dom.php';
    php> $html = '
    <html>
    <body>
      bodytxt1
      <script>var y = a<b;</script>
      <style>body {display:none}</style>
      <p>I-m here</p>
      bodytxt2
    </body>
    </html>';
    php > $tree =  str_get_html($html);
    php > echo $tree->find("body", 0)->plaintext;
    bodytxt1 I-m here bodytxt2
    php > 
    php > echo $tree->find("body > p", 0)->plaintext;
    I-m here
    php > 
    php > echo $tree->find("body > script", 0)->plaintext;
    php > 
    


    3. Не умеет преобразовывать HTML entities

    php > $html = '<html><body>M&amp;M-s, 3&gt;2</body></html>';
    php > $tree =  str_get_html($html);
    php > echo $tree->find("body", 0)->plaintext;
    M&amp;M-s, 3&gt;2
    php > 
    


    Вывод можете сами сделать.
    • –1
      1. libxml не даст таких хитровымудренных запросов, типа [attribute*=value] (есть такие верстальщики, использующие (почти) одинаковые классы при верстке рекламных блоков и блоков с контентом

      2. Библиотека для парсинга данных — нужно ди извлекать скрипты и стили?

      3. Поможет htmlspecialchars_decode
      • +2
        1. Use XPath, Luke!
        2. В них тоже могут быть данные
        3. Ок
  • 0
    Кто-нибудь (пожалуйста, пожалуйста, пожалуйста :) добавьте парсеры, упомянутые в этой статье к набору тестов в этом постинге. Ну очень любопытно…
    • +2
      Я постараюсь протестить максимум парсеров, которые хотя бы косвено здесь упоминались.
  • +1
    А как с обработкой ошибок? Мне как в том меме «Обычно я не парсю html, но если парсю, то в нем куча синтаксических ошибок». С этой стороны лучше всего себя ведут nokogiri и родной, но медленноватый и сложный Document Object Model.
  • +1
    Юзал эту библиотеку одно время.
    В ней тогда была утечка памяти — постоянно вылетала при работе в демонах.
    Штатная функция очистки не помогала.
    Решил проблему с помощью хака: загрузки в объект пустой строки:

    $html->load('');
  • +1
    Да, на php подобных библиотек как говна за баней, как у дурака фантиков. Поражает энергия авторов этих поделий, из-за того чтобы подфиксить что-то в открытом проекте пишут с нуля новый. Ну дай бог, раз силы есть.
    • 0
      Потом перестают поддерживать свой проект и чтобы пофиксить баги приходится создавать новый и так далее до бесконечности, это да.
    • 0
      Программы они же как дети, ну и что, что у соседа их три, а я своих хочу :))) Ну, а раса PHP очень плодовитая…
      • 0
        Чужими детьми ты пользоваться не можешь или детей ты можешь склонировать и исправить ошибки. Не раса, а масса. И не плодовитость это, а злокачественное какое-то деление.
        • 0
          Да проститься мне неполиткорректность, считайте их китайскими детьми
          • 0
            Неполиткорректность — может быть, но мягкий знак — сшивать вам за такое указательный и средний пальцы правой руки.
  • 0
    > simple_html_dom.php 1742 lines (1546 with data), 54.9 kB

    Oh, you must be joking.
  • 0
    Для одного из проектов пробовал разные подобные библиотеки, но в итоге остановился на Simple HTML DOM Parser.

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

    Но есть один плюс, который в моем случае перевесил все: т.к. все парсится чисто своим кодом, без всяких внешних DOM-библиотек, то с его помощью можно не только парсить отдельные куски HTML-кода (что другие парсеры часто понимают, как невалидный код), но и вносить в него изменения, не ломая исходной структуры. Аналогов, которые умеют делать то же самое, но при этом в чем-то превосходят Simple HTML DOM Parser, я просто не нашел
    • 0
      Попробуйте CDom :)
      habrahabr.ru/post/176635/#comment_6137137

      По крайней мере второй недостаток я в нём решил. С первым недостатком некоторые улучшения ожидаются в новой версией.
      А все фичи также есть, даже больше и удобнее.
  • 0
    Использую Simple HTML DOM Parser довольно часто, и в других подобных решениях необходимости пока не было.
    Один совет — не забывать релизить ресурсы, иначе утечка памяти обеспечена.
    $html_obj->clear(); unset($html_obj);

    Кроме того, существует обертка для этого парсера под Drupal drupal.org/project/simplehtmldom
    • 0
      Мало того, при больших объемах парсинга можно натолкнуться на segmentation fault (даже с учетом переодической очистки памяти через ->clear() и unset() ). Подробней — kirugan.ru/2013/01/kak-borotsya-s-segmentation-fault-v-simple_html_dom/
  • 0
    Скажите, какая максимальная величина файла, который сможет «обработать» simple_html_dom?
    Столкнулся с тем, что библиотека как бы не видит xml-файл величиной в 18 мегабайт (237425 строк).
    file_get_html и str_get_html возвращают false.
    На мелких файлах отрабатывает без проблем.
    Ограничения памяти и времени выполнения в php позволяют спокойно пережевать этот файл.
    • 0
      Чуть позже выложу статью со сравнением библиотек для парсинга, постараюсь подробнее расписать на счет вашего вопроса. Пока что посмотрите обсуждение на SF
  • 0
    Здравствуйте.
    Есть html код:

    div class=«title» data-action=«open» data-params='test'

    Как при помощи simple_html_dom получить содержимое атрибута data-params?

    Логически это должно выглядеть так: $dom = $html->find('div[class=title]', 0)->data-params
    Но, как вы понимаете, это не работает.
    • 0
      всего лишь предположение (просто первое, что пришло на ум — проверить нет возможности):

      $attr = 'data-params';
      $dom = $html->find('div[class=title]', 0)->$attr
      


      А вообще, если есть возможность, попробуйте phpQuery
      • 0
        Я сделал так
        $dom = $html->find('div[class=title]', 0)->attr['data-params'].
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Сам отказался от этой библиотеки в пользу tidy+xpath, оп тестам производительности она показала самый плохой результат, к сожалению сами тесты потеряны.

      На счет ошибки, попробуйте получить страницу через file_get_contents и посмотреть что возвращает.
  • 0
    Simple PHP DOM Parser — глючное говно (врожденные проблемы с выпадением в рекурсии, неспособность выдрать tbody без thead, невозможность выбрать вторую ссылку в ячейке таблицы по find('a') и так далее и тому подобное). Как оказалось в середине проекта, когда уже поздно метаться в сторону других библиотек. Имейте это ввиду, когда будете выбирать что использовать.
    • 0
      Вообще, я обещал обзор списка библиотек и возможно таки сделаю (нет смысла на самом деле, xPath — победил везде), но все уперлось именно в то, что некоторые из библиотек, перечисленных в конце статьи (в т.ч. Simple PHP DOM Parser) или умирают на больших деревьях или просто отказываются работать. На реальных проектах (где надо нечто большее, чем просто выбрать все ссылки на странице), я настоятельно рекомендую связку curl -> tidy -> xPath

      Единственный минус xPath — очень капризен к DOM, зато есть встроенные отладчики чего и где угодно, куча готового кода на этих ваших гитах и т.д.
      • 0
        Да, xpath рулит, бесспорно. Просто здесь сама идея селекторов и текучий интерфейс показались заманчивыми, а в результате оказалось, что библиотека имеет большое количество раздражающих недостатков.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.