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

понедельник, 10 августа 2009 г.

Парсинг HTML на PHP

Потихоньку изучаю возможности PHP для создания парсеров. Я уже писала о том, как парсить xml на php с помощью SimpleXML. Сейчас расскажу об одном из способов парсинга html (он подойдет и для xml тоже, кстати). Повторю, что в php я не гуру, поэтому буду очень признательна, если вы оставите свои комментарии к поднятой теме.

Побродив по нашим и англоязычным форумам, поняла, что спор о том, лучше ли парсить html регулярными выражениями или использовать для этих целей возможности PHP DOM, является холиваром. Сама же я пришла к выводу, что все зависит от сложности структуры данных. Ведь если структура достаточно сложная, то с помощью регулярок приходится парсить в несколько этапов: сначала выделить большой кусок, потом разделить его на более маленькие и т.д.. В итоге, если данные сложные (или их очень много), то процесс парсинга может значительно затянуться. Ресурсоемкость в этом случае еще будет зависеть, конечно же, от самих регулярных выражений. Если в регэкспах много ".*" (они являются самыми ресурсоемкими, т.к. "прочесывают" исходный код с максимальной жадностью), то замедление будет заметным.

И вот как раз в этом-то случае как нельзя кстати приходится PHP DOM. Это удобный инструмент для парсинга как XML, так и HTML. Некоторые придерживаются мнения, что парсить html регэкспами вообще нельзя, и яростно защищают PHP DOM.

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


<?php
$html = '
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<head>
<title>Parsing-and-i.blogspot.com Map</title>
</head>
<body>
<h2>Последние темы блога</h2>
<!-- на 09.08.2009 -->
<table border="0">
<tbody>
<tr>
<td><a href="http://parsing-and-i.blogspot.com/2009/08/blog-post_06.html" title="Базы">http://parsing-and-i.blogspot.com/2009/08/blog-post_06.html</a></td>
<td>Базы</td>
</tr>

<tr>
<td><a href="http://parsing-and-i.blogspot.com/2009/08/mysql-delphi-express.html" title="MySQL и Delphi. Express-метод">http://parsing-and-i.blogspot.com/2009/08/mysql-delphi-express.html</a></td>
<td>MySQL и Delphi. Express-метод</td>
</tr>

<tr>
<td><a href="http://parsing-and-i.blogspot.com/2009/08/blog-post.html" title="Пост о том, что лучше сто раз проверить">http://parsing-and-i.blogspot.com/2009/08/blog-post.html</a></td>
<td>Пост о том, что лучше сто раз проверить</td>
</tr>

</tbody>
</table>

</body>
</html>
';
/** создаем новый dom-объект **/
$dom = new domDocument;

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

/** элемент по тэгу **/
$tables = $dom->getElementsByTagName('table');

/** получаем все строки таблицы **/
$rows = $tables->item(0)->getElementsByTagName('tr');

/** цикл по строкам **/
foreach ($rows as $row)
{
/** все ячейки по тэгу **/
$cols = $row->getElementsByTagName('td');
/** выводим значения **/
echo $cols->item(0)->nodeValue.'<br>';
echo $cols->item(1)->nodeValue.'<br>';
echo '<hr>';
}
?>


В результате после запуска скрипта получаем такую картину:
Parsing html with dom php


Upd: Без всякого сомнения, для более удобной работы со структурой HTML в PHP вам надо познакомиться с библиотекой PHP Simple HTML DOM Parser. Я бы отдала предпочтение именно ей.


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

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



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

  1. Подскажите пожалуйста, если важным критерием при парсинге является не производительность, а потребление памяти, что лучше использовать: регулярные выражения или парсить через DOM? Как на ваш взгляд?

    ОтветитьУдалить
  2. Спасибо, хороший пример для начала использования парсера ДОМа. Я лично всегда парсил все регулярными выражениями, ИМХО так проще. :)

    ОтветитьУдалить
  3. Маша, это очень долго. Может быть пример не самый удачный но, здесь нужно ровно одно регулярное выражение

    #<a\s+href="(.+)".+<td>(.+)</td>#sUi

    ОтветитьУдалить
  4. Спасибо за комментарий, про регулярные выражения я, конечно же, в курсе ))) Да, пример применения выбрала не самый удачный, но это просто пример) Вдруг кому-нибудь пригодится.

    ОтветитьУдалить
  5. Не БОЛЬШОЕ спасибо, хоть пример и простой но реально пригодился для построения более сложного, т.к. я раньше парсингом вообще не занимался ))..

    ОтветитьУдалить
  6. На будущее - DOM грузит весь документ разом в память, так что если у вас огромный объем дынных, то ни DOM, ни, те более regexp, не помогут. На помощь придет SAX. Им конечно нельзя создавать или редактивароть XML, но зато он в память не берет всё разом. И да, он годится только для XML. И раз уж пошла такая пьянка, то DOM не станет разбирать совсем невалидный документ (например теги незакрытые или порядок совсем неверный). Тут на помощь придет прекрасное расширение для PHP - tidy. Почитайте про него. Исправляет любой html из полного "г.." до адекватного и валидного типа.

    ОтветитьУдалить
  7. Здравствуйте.
    Подскажите пожалуйста, можно ли средствами DOM найти соответствующий закрывающий тег для заданного?
    Т.е., например, мы имеем такой код:
    див_Х .class=."foo"
    див_01 .class=."x1".../див_01
    див_02 .class=."x2".../див_02
    див_03 .class=."x3".../див_03
    /див_Х *- вот этот тег, например, нужно найти -*

    Не получается найти номер строки закрывающего тега.
    Можно ли найти закрывающий див для дива див_Х class.=."foo" средствами DOM?

    Извините, пришлось наставить точек и русских букв, т.к. blogger не пропускает код.

    ОтветитьУдалить
  8. А что с кодировкой.
    У меня выдает кракозябры...

    ОтветитьУдалить
  9. Сергей, у меня с кодировкой все нормально, непроверенные примеры не привожу обычно :) Если у вас что-то не так - поменять кодировку просто.

    ОтветитьУдалить
  10. Чтото у меня на денвере тоже с кодировкой проблемы.. Не пойму, даже если сохраняю в UTF-8 сам скрипт с примером, какую в итоге выдает кодировку скрипт, так и не смог подобрать... В опере ни одна не подошла. Тоже самое при сохранении скрипта в ANSI... Что может быть, не подскажете?

    ОтветитьУдалить
  11. !Большое, !Человеческое - !Спасибо за статью...

    ОтветитьУдалить
  12. echo mb_convert_encoding($cols->item(1)->nodeValue, "", "utf-8");
    -------------
    при этом не пишите заголовок документа...
    иначе, эксерементируйте...

    ОтветитьУдалить
  13. Анонимный9 июня 2010 г., 23:44

    Конвертация кодировки помогла. Спасибо.

    Вопрос.
    Как граммотно спарсить с помощью регулярных выражений блок страницы.
    Просто он берет код построчно, а не целиком.
    Если надо проверить все ссылки или опр тег, то это просто.
    А мне надо найти ссылку. выудить из неё кое-что. Это я сделал. А потом в этом же блоке выудить из нескольких < td > еще инфу. Потом бежать к следующей ссылке.
    Я примерно представляю, как это реализовать. Просто хочется спросить, как это сделать по-человечески, без нагораживания условий и тп.

    ОтветитьУдалить
  14. Сложно ответить на этот вопрос, не имея перед глазами исходного кода страницы. Ваши слова не дают полного представления о задаче.

    Может, если с данными требуется производить достаточно много манипуляций, вам стоит попробовать использовать другой, более удобный инструмент? Я недавно писала на блоге про PHP Simple HTML DOM Parser (http://parsing-and-i.blogspot.com/2010/05/php-simple-html-dom-parser.html). Скоро планирую добавить еще одну статью с примером его использования.

    Удачи.

    P.S. И, если не сложно, подписывайте комментарий своим именем, а то обращаться к Анонимусу — что обращаться в пустоту.

    ОтветитьУдалить
  15. 2Masha
    дополнение к посту от 9 июня 2010 г. 23:44

    код примерно такой
    < tr >
    < td >< a инфа1 >< /a >< /td >
    < td >инфа2< /td >
    < td >инфа3< /td >
    < td >инфа4< /td >
    < /tr >

    вот. надо выудить из множества таких блоков на странице инфу.
    Просто хотелось гарантию, что инфа1 будет соответствовать инфе2. А то в построчном парсе регулярными выражениями че-нить проскочит и сдвинется. Вот, думаю как это красиво обойти, что бы случайно не потерять ключи в ссылке к информации из ячеек таблицы.
    P.S. Пойду читать то, что Вы предложили.

    ОтветитьУдалить
  16. Алексей, да, тогда та библиотека - именно то, что надо.

    ОтветитьУдалить
  17. Да, благодарю. Я уже реализовал с её помощью. Довольно простой и понятный мануал. И регулярные выражения пригодились.

    ОтветитьУдалить
  18. Здравствуйте, Мария.
    Меня зовут Виктор.

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

    Вы можете помочь составить скрипт парсинга списков категорий на молоток.ру? Общий смысл: конечная ветка категории --> список товаров в формате: картинка, название, состояние товара, цена, ставки, время до окончания. При кликании на ссылку товара в списке, вывод его данных в редактируемую форму.

    Оплата через Webmoney или Paypal.
    Если возможно, хотелось бы устроить это в виде консультации по скайпу с почасовой оплатой. Также я готов заплатить за готовый скрипт.

    ОтветитьУдалить
  19. Виктор, добрый день!

    Спасибо за предложение, но, к сожалению, очень много работы, заказы не принимаю.

    ОтветитьУдалить
  20. Пример работает, все ок. Но как только попытался выдрать что-то из реального сайта, получил кучу сообщений вот такого вида:
    "Warning: DOMDocument::loadHTML() [domdocument.loadhtml]: htmlParseEntityRef: expecting ';' in Entity line: ххх"

    Гугление текста ошибки не помогает, не подскажете в чем может быть проблема?

    ОтветитьУдалить
  21. El Coyot, невалидный html, скорее всего. Попробуйте сначала прогнать код через валидатор (tidy, например).

    ОтветитьУдалить
  22. Статья хорошая, библиотека тоже, работает на ура, распарсил контактную форму, вставил во все поля инфо, вывел юзеру для редактирования контакта. Спасибо Маш. Все отлично.

    ОтветитьУдалить
  23. Анонимный4 июля 2011 г., 14:13

    а как извлекать каждый второй и третий TR ???

    ОтветитьУдалить
  24. Анонимный, какой запрограммируешь алгоритм - то и извлечешь. Я не консультирую по азам программирования.

    ОтветитьУдалить
  25. Анонимный15 июля 2011 г., 16:03

    Здесь описано решение проблемы с кодировкой:
    http://blog.fxposter.org/2008/07/20/domdocument-encoding-in-html/

    Кратко:
    - new DomDocument('1.0', 'UTF-8');
    - в html-файле должен присутствовать meta-тег с указанием кодировки.

    ОтветитьУдалить
  26. Спасибо помогло!!! А вот такой вопрос: надо сделать копию одного tr> делаю с помощью DOMNode::appendChild, ни чего не добавляет, но скрипт корректно выполняется. Не подскажите в чем может быть проблема???

    ОтветитьУдалить
  27. Simple HTML DOM годится только для идеалного html кода страницы, иначе начинает жестко тупить. Другое дело phpparserplus.esy.es, который работает с помощью multi-curl и проверен на сотни сайтах.

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

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

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

Поделиться