Поиск по блогу

четверг, 20 мая 2010 г.

PHP Simple HTML DOM Parser

Сегодня я немного расскажу про библиотеку для парсинга HTML под названием PHP Simple HTML DOM Parser. В последнее время частенько ей пользовалась: нравятся ее возможности и простота. Скачать библиотеку можно со страницы. В комментарии к сказано:

A simple PHP HTML DOM parser written in PHP5+, supports invalid HTML, and provides a very easy way to handle HTML elements.


То есть нам обещают массу плюсов, основные из которых скорость и поддержка невалидного html-кода. Изучив документацию, можно вдохновиться на подвиги: возможности библиотеки действительно впечатляют. Кроме всего прочего - удобный, интуитивно понятный синтаксис.

Еще в числе плюсов, которые я отметила, — отсутствие проблем с кодировками. Часто бывает, что, получив содержимое страницы с помощью, например, file_get_contents, кодировку данных на промежуточном этапе приходится преобразовывать. Здесь же такой надобности у меня пока что не возникало.

С помощью этой библиотеки вы можете обращаться к элементам и атрибутам элементов, искать определенного уровня вложенные элементы, фильтровать их, искать текст и комментарии(!).

Приведу примеры из документации:

// Найти ссылки и возвратить массив найденных объектов
$ret = $html->find('a');

// Найти (N)-ую по счету ссылку и возвратить найденный объект или null в случае, если объект не найден
$ret = $html->find('a', 0);

// Найти все элементы <div>, у которых id=foo
$ret = $html->find('div[id=foo]');

// Найти все элементы <div>, имеющие атрибут id
$ret = $html->find('div[id]');

// Найти все элементы, имеющие атрибут id
$ret = $html->find('[id]');


Ну и, конечно, стандарто - в библиотеку заложена возможность перемещения по списку элементов объектного дерева. Для этого используются:
$e->children( [int $index] ), 
$e->parent(),
$e->first_child(),
$e->last_child(),
$e->next_sibling(),
$e->prev_sibling().


Прежде чем на страницах этого блога приводить примеры парсеров, написанных с использованием этой библиотеки, я решила протестировать, насколько соответствует реальности обещанное быстродействие. Явных тормозов я за ней не замечала, но хотелось бы получить доказательство в цифрах.

Для теста возьмем элементарную задачку - выведем на экран ссылки с анкорами с главной страницы Гугля. Для сравнения буду использовать работу через стандартный domDocument.

Вот так будет выглядеть код с использованием Simple HTML DOM Parser:

include_once('simple_html_dom.php');

// Создаем объект DOM на основе кода, полученного по ссылке
$html = file_get_html('http://www.google.com/');

// находим все ссылки
foreach($html->find('a') as $element)
echo $element->href .' ('. $element->innertext. ')<br>';


А вот код с использованием domDocument:

$html = file_get_contents('http://www.google.com/');
// создаем новый dom-объект
$dom = new domDocument;

// загружаем html в объект
$dom->loadHTML($html);
$dom->preserveWhiteSpace = false;

// элемент по тэгу
foreach ($dom->getElementsByTagName('a') as $row)
echo $row->GetAttribute('href').' ('.$row->nodeValue.')<br>';


Проведя по 5 запусков каждого варианта получила среднее время выполнения скрипта:
  • для Simple HTML DOM - 0,8096

  • для domDocument - 0,7326

Тут, хотя и Simple HTML DOM проигрывает, результаты различаются не сильно. Думаю, что надо будет потом протестировать скорость на чем-нибудь более сложном, с поиском и перемещением по узлам DOM-дерева.

Зато я визуально смогла оценить работу библиотеки Simple HTML DOM Parser с "невалидным" html. Если перед строкой
$dom->loadHTML($html);

не поставить "@", то при работе скрипта с domDocument вывалится куча варнингов типа:

Warning: DOMDocument::loadHTML() [function.DOMDocument-loadHTML]: Tag nobr invalid in Entity, line: 5
Warning: DOMDocument::loadHTML() [function.DOMDocument-loadHTML]: htmlParseEntityRef: expecting ';' in Entity, line: 5


Кроме того видна разница и в выводе результатов (кодировка):
Simple HTML DOM Parser library
domDocument

В общем, библиотека мне нравится, кто еще не пользовался — советую попробовать.


Чтобы быть в курсе обновлений блога, можно подписаться на RSS.

Статьи схожей тематики:



27 комментариев:

  1. Анонимный2 июня 2010 г., 20:41

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

    ОтветитьУдалить
  2. Анонимный, это совсем просто сделать, в будущем когда-нибудь, может, и напишу статью. Просто написание каждого материала на блог занимает определенное время, которого сейчас явно не хватает.

    ОтветитьУдалить
  3. А знать английский любому программисту, по-моему, знать очень полезно. По крайней мере на уровне чтения технической документации.

    ОтветитьУдалить
  4. Маша, а как в этой библиотеке можно получить теги, которые обрамляют искомый фрагмент текста? :)

    Т.е. есть часть текста, нужно определить где в DOM документе этот фрагмент находится.
    Заранее благодарен за рекомендацию! :)

    ОтветитьУдалить
  5. Анатолий, я не знаю, никогда не сталкивалась с такой необходимостью :) Явных решений не вижу.

    ОтветитьУдалить
  6. Анонимный22 июня 2010 г., 14:53

    Добрый день!
    Подскажите, как с помощью этой библиотеки решить такую задачу:
    есть несколько элементов находящихся на одном уровне иерархии, я знаю id и class первого из них, как мне получить все последующие за ним элементы?
    пробовал делать так:
    foreach($html->find('h3#el_id.el_class')->nextSibling() as $element) {
    $out .= $element->innertext;
    }
    Выдается ошибка:Fatal error: Call to a member function nextSibling() on a non-object

    ОтветитьУдалить
  7. Анонимный, сорри, руки до комментов не доходили. Может, уже нашли ответ на свой вопрос...

    Что-то у вас намудрено, непонятно, как вы это себе представляете :) Наверное, не совсем правильно представляете обход дерева.

    Сначала найдите элемент:
    $element = $html->find('h3#el_id.el_class',0);

    Потом примерно так обход будет выглядеть:
    while($element) {
    $out .= $element->innertext;
    $element = $element->next_sibling();
    }
    (код не проверяла, но, по идее, должно быть как-то так).

    ОтветитьУдалить
  8. Здравствуйте!,
    Некоторые таги моего HTML кода имеют так называемые пользовательские аттрибуты, стандартного вида с приставкой data-.... к примеру data-thumb, data-zoom и так далее.

    используется следующая конструкция

    $ret = $html->find('div', 0);
    $atrr=$ret->id;

    Но если я хочу обратиться к пользовательскому аттрибуту

    $atrr=$ret->data-zoom;

    То он мне невыдает ничего путного, а точней если сделать echo то выдаст 0;

    Что мне делать? Атрибуты типа data-zoom нужны с приставкой, чтобы их воспринемал javascript, поэтому поменять их на одно слово не имею возможности.

    Заранее спасибо!

    ОтветитьУдалить
  9. Илья, вам надо обращаться к атрибуту так:
    $atrr = $ret->getAttribute('data-zoom');

    ОтветитьУдалить
  10. А у меня на небольшом HTML-документе быстрее отработала либа phpQuery. Советую попробовать, функционал схож.

    ОтветитьУдалить
  11. Спасибо!
    Уже какой раз обращаюсь как к справочнику!

    ОтветитьУдалить
  12. Спасибо за статью! У меня сейчас стоит задача распарсить кучу html страниц, вырезав оттуда div'ы с определенными качествами. Пару дней назад наткнулся на Simple HTML DOM Parser, удивившись его простоте (селекторы похожи на JQuery), решил заюзать его - но был чрезвычайно очень огорчен большим расходом памяти. Парсится 10 страниц, при парсинге каждой новой страницы память, используемая скриптом (http://ru2.php.net/manual/en/function.memory-get-usage.php), увеличивается скачком на 5-10 мегабайт в зависимости от сложности и размера страницы. Побороть это так и не получилось, делал и unset(), и приравнивал к null - видимо ни то, ни другое не очищает память, занимаемую объектом парсера.
    Такие пироги. Нет, можно поставить побольше ограничение на максимальное занимаемое скриптом место в оперативе, но это плохой костыль.

    ОтветитьУдалить
  13. sterx, что-то очень сильно у вас память кушается, не могу сказать, почему. Сама с таким не сталкивалась, видела, что ресурсоемко получается иногда, но не на столько... К сожалению, ничего подсказать не могу. Может, у других пользователей найдется решение?

    ОтветитьУдалить
  14. Столкнулся с проблемо.
    Используя эту библиотеку при парсинге натыкаюсь на досадную ситуацию: классом некоторых дивов является "\".
    <div class="details"><div class="\">ТЕКСТ_1</div><div class="\">ТЕКСТ_2</div><div class="\">ТЕКСТ-3</div></div>

    В итоге, как можно догадаться, ничего путнего парсер не выдаёт, т.к. бекслеш экранирует кавычку (видимо). И в итоге код:
    $item_info->find('div', 0)->plaintext;
    выдаёт только ТЕКСТ_2.
    Ну оно и поняно -- всё что после "сэкранированной" кавычки до следующей кавычки считается атрибутом...

    Есть ли идеи как с этим бороться :(
    А то как-то я даже не знаю. А переписывать весь парсинг ну оочень не хочется, т.к. такая штука при тестировании и написании не вылазила, а там на какой-то уже ЭН-ной странице только появилась :)

    ОтветитьУдалить
  15. test, ну первое, что приходит в голову, это в исходном html-коде заменить "\" на что-нибудь другое, "потенциально неопасное". :) Вреда от этого не будет... или просто вырезать все class="\".

    ОтветитьУдалить
  16. Dimaseo, я так полагаю, что именно это sterx и делал, но ему не помогло...

    ОтветитьУдалить
  17. очень удобная библиотека, но, к сожалению, за удобство надо платить: время генерации самой структуры dom например из текста (у меня в проекте подключен прокси) составляет на Dual-Core AMD Opteron(tm) Processor 1218 2615.989 Mhz X 2 Debian Lenny, php без акселераторов порядка 1 - 1,5 секунд (!!) что при задаче парсить ресурс где примерно 13 тыс.страниц (парсить нужно ежедневно, процесс должен укладываться в час-два) - оказывается невозможным. Структуры же извлекаемых данных местами сложны, писать впрямую regex совсем не хочется :(

    ОтветитьУдалить
  18. Удалось значительно ускорить работу парсера без отказа от использования библиотеки:
    1) $html->clear(); unset($html);
    действительно помогает в отношении памяти.
    2) Если перед генерацией ДОМ регекспом вырезать все внутри тегов head,script,style и еще допустим form, то как правило время генерации DOM-модели сокращается в несколько раз (резать конечно надо не в тупую).
    3) Запуск множества потоков, как не странно, убыстряет процесс.

    Удалось сократить время обхода 13+к страниц с 8-10 часов до 30-40 минут.
    И это без установки php-акселератора.

    ОтветитьУдалить
  19. Андрей, акселератор на этот процесс вообще не повлияет, поскольку он кеширует лишь байткод и дисковые операции при запуске и подключении скриптов. Разве что у вас на каждую обработку скрипт запускается заново, что было бы нерационально.

    Маша, тесты производительности совершенно не соответствуют моим представлениям о том, как должны производиться тесты этой самой производительности ))
    Большую часть времени скрипт ожидает данных от гугла, на что, между всем прочим, может уходить очень разное время, зависящее от загрузки сети, на фоне чего время работы парсера вообще не заметно на этом фоне.
    Тут нужно было бы загрузить данные, после этого засечь время начала, провести тест несколько тысяч, десятков, а то и сотен тысяч раз, чтобы процесс занимал значимое время, хотя бы в секунды, засечь время конца и уже его анализировать.

    От себя вот добавлю, что DomDocument разочаровал с первой же попытки использования, тупо потеряв большую часть документа с корректным (!) html'ом.

    А эта библиотечка поддерживает работу с XPath или насколько развиты её возможности по обработке запросов? Это сопоставимо, например, с привычными возможностями JQuery, в стиле которой, я смотрю, и делаются запросы?

    ОтветитьУдалить
  20. Ахха, надо было сначала по ссылке пройти - всё очень лаконично и информативно: возможности действительно впечатляют, хотя селекторов нет и функционал скромнее, но всё, что нужно для детального парсинга - как на ладони )

    ОтветитьУдалить
  21. Aleks, по поводу парсинга локальной страницы - согласна, надо было... но это было так давно, больше года назад :)

    ОтветитьУдалить
  22. Сегодня проповал юзать PHP Simple HTML DOM Parser...
    Страница для парсинга в кодировке 1251, парсер выдает кряказябры...
    Как заставить парсер прочитать все верно - не представляю, хотя уже все перепрообовал...

    ОтветитьУдалить
  23. Нашел всетаки выход :))

    iconv('CP1251', 'UTF-8', $str);

    ОтветитьУдалить
  24. Что по поводу утечек памяти? На самом сайте в комментах говорят - жутко мешает. И на кодировки ругаются - говорят мол utf8 не держит. Неужели до сих пор нет ни одной нормальной библиотеки для полноценного парсинга?

    ОтветитьУдалить
  25. Понимаю что глупый вопрос: а подскажите куда именно в сайт joomla 2.5 вставлять php simple dom ... и где вписывать данные скрипты для провирки как все работает? Не могу стартануть так как недостаточно знан

    ОтветитьУдалить

Комментарии модерируются, вопросы не по теме удаляются, троллинг тоже.

К сожалению, у меня нет столько свободного времени, чтобы отвечать на все частные вопросы, так что, может, свой вопрос лучше задать на каком-нибудь форуме?

Поделиться