0,0
рейтинг
2 марта 2011 в 20:49

Разработка → Примеры xpath-запросов к html

Xpath — это язык запросов к элементам xml или xhtml документа. Также как SQL, xpath является декларативным языком запросов. Чтобы получить интересующие данные, необходимо всего лишь создать запрос, описывающий эти данные. Всю «черную» работу за вас выполнит интерпретатор языка xpath.
Очень удобно, не правда ли? Давайте посмотри какие возможности предлагает xpath для доступа к узлам веб-страниц.

Создание запроса к узлам веб-страниц


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

Для лабораторной нам понадобятся:
— веб-страница xhtml;
— браузер Mozilla Firefox с дополнениями;
firebug;
firePath;
(вы можете использовать любой другой браузер с визуальной поддержкой xpath)
— немного времени.

В качестве веб-страницы для проведения эксперимента предлагаю главную страницу сайта консорциума всемирной паутины ('http://w3.org'). Именно эта организация разрабатывает языки xquery(xpath), спецификацию xhtml и многие другие стандарты интернета.

Задача

Получить из xhtml-кода главной страницы w3.org информацию о конференциях консорциума при помощи запросов xpath.
Приступим к написанию xpath запросов.


Первый Xpath запрос

Открываем закладку Firepath в FireBug, выделяем с селектором элемент для анализа, нажимаем: Firepath создал xpath запрос к выбранному элементу.

Если вы выделили заголовок первого события, то запрос будет таким:

.//*[@id='w3c_home_upcoming_events']/ul/li[1]/div[2]/p[1]/a

После удаления лишних индексов запрос станет соответствовать всем элементам типа «заголовок».

.//*[@id='w3c_home_upcoming_events']/ul/li/div/p/a

Firepath подсвечивает элементы, которые соответствуют запросу. Вы можете в реальном времени увидеть, какие узлы документа соответствуют запросу.



Идем дальше. Создаем запросы для поиска мест проведения конференций и их спонсоров либо с помощью селектора, либо модифицировав первый запрос.

Запрос для получения информации о местах проведения конференций:
.//*[@id='w3c_home_upcoming_events']/ul/li/div/p[2]

Так мы получим список спонсоров:
.//*[@id='w3c_home_upcoming_events']/ul/li/div/p[3]

Синтаксис xpath


Давайте вернемся к созданным запросам и разберемся в том, как они устроены.
Рассмотрим подробно первый запрос



В этом запросе я выделил три части для демонстрации возможностей xpath. (Деление на части уловное)

Первая часть
.// — рекурсивный спуск на ноль или более уровней иерархии от текущего контекста. В нашем случае текущий контекст это корень документа

Вторая часть
* — любой элемент,
[@id='w3c_home_upcoming_events'] – предикат, на основе которого осуществляем поиск узла, имеющего атрибут id равным 'w3c_home_upcoming_events'. Идентификаторы элементов XHTML должны быть уникальны. Поэтому запрос «любой элемент с конкретным ID» должен вернуть единственный искомый нами узел.

Мы можем заменить * на точное имя узла div в этом запросе
div[@id='w3c_home_upcoming_events']

Таким образом, мы спускаемся по дереву документа до нужного нам узла div[@id='w3c_home_upcoming_events']. Нас абсолютно не волнует, из каких узлов состоит DOM-дерево и сколько уровней иерархии осталось выше.

Третья часть
/ul/li/div/p/a –xpath-путь до конкретного элемента. Путь состоит из шагов адресации и условия проверки узлов (ul, li и т.д.). Шаги разделяются символом " /"(косая черта).

Коллекции xpath

Не всегда удается получить доступ к интересующему узлу с помощью предиката или шагов адресации. Очень часто на одном уровне иерархии находится насколько узлов одинакового типа и необходимо выбрать «только первые» или «только вторые» узлы. Для таких случаев предусмотрены коллекции.

Коллекции xpath позволяют получить доступ к элементу по его индексу. Индексы соответствуют тому порядку, в котором элементы были представлены в исходном документе. Порядковый номер в коллекциях отсчитывается от единицы.

Исходя из того, что «место проведения» всегда второй параграф после «названия конференции», получаем следующий запрос:
.//*[@id='w3c_home_upcoming_events']/ul/li/div/p[2]
Где p[2] – второй элемент в наборе для каждого узла списка /ul/li/div.

Аналогично список спонсоров мы можем получить запросом:
.//*[@id='w3c_home_upcoming_events']/ul/li/div/p[3]

Некоторые функции хpath

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

last():
Возвращает последний элемент коллекции.
Запрос ul/li/div/p[last()] — возвратит последние параграфы для каждого узла списка «ul».
Функция first() не предусмотрена. Для доступа к первому элементу используйте индекс «1».

text():
Возвращает тестовое содержание элемента.
.//a[text() = 'Archive'] – получаем все ссылки с текстом «Archive».

position() и mod:
position() — возвращает позицию элемента в множестве.
mod — остаток от деления.

Комбинацией данных функций можем получить:
— не четные элементы ul/li[position() mod 2 = 1]
— четные элементы: ul/li[position() mod 2 = 0]

Операции сравнения
  • < — логическое «меньше»
  • > — логическое «больше»
  • <= — логическое «меньше либо равно»
  • >= — логическое «больше либо равно»

ul/li[position() > 2] , ul/li[position() <= 2] — элементы списка начиная с 3го номера и наоборот.

Полный список функций

Самостоятельно


Попробуйте получить:
— четные URL ссылки из левого меню «Standards»;
— заголовки всех новостей, кроме первой с главной страницы w3c.org.

Xpath в PHP5


	$dom = new DomDocument();
	$dom->loadHTML( $HTMLCode );

	$xpath = new DomXPath( $dom );

	$_res = $xpath->query(".//*[@id='w3c_home_upcoming_events']/ul/li/div/p/a");

	foreach( $_res => $obj ) {
                echo 'URL: '.$obj->getAttribute('href');
		echo $obj->nodeValue;
        }


В заключение


На простом примере мы увидели возможности xpath для доступа к узлам веб-страниц.
Xpath является отраслевым стандартом для доступа к элементам xml и xhtml, xslt преобразований.
Вы можете применять его для парсинга любой html-страницы. В случае если исходный html-код содержит значительные ошибки в разметке пропустите его через tidy. Ошибки будут исправлены.

Старайтесь отказаться от регулярных выражений при парсинге веб-страниц в пользу xpath.
Это сделает ваш код проще, понятнее. Вы допустите меньше ошибок. Сократиться время отладки.

Ресурсы


Firepath дополнение Mozzilla Firefox
Краткая аннотация языка в википедии
Xороший справочник по xpath. Не обращайте внимание на то, что он для NET Framework. Xpath во всех средах работает одинаково, за исключением пары специфичных функций
Cпецификация xpath 1.0
Cпецификация xpath 1.0 на русском
XQuery 1.0 and XPath 2.0
Tidy
PHP5 tidy::repairFile
Алексей Кузьмин @AlekseyKuzmin
карма
103,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

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

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

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

  • 0
    Полезно. Спасибо.
  • 0
    Отлично -> в избранное. Спасибо!
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Я думал это твоя статья, пока автора не увидел
      • НЛО прилетело и опубликовало эту надпись здесь
  • +3
    Лично я давным давно отказался от регулярок в своих парсерах в пользу DOM и Xpath. Это действительно удобный инструмент.
    • 0
      У вас были какие то проблемы с валидностью? Незакрытый тэг? Ведь всякое бывает.
      • +1
        От регулярок просто устал.
        А при использовании xpath сразу через tidy прогонял html и честно говоря еще не встречал проблем с валидностью.
        • 0
          Tidy. Я знаю что это, но никогда с ним не работал. Действительно крутая штука?
          • 0
            о, да.
          • 0
            Да. Но стоит учитывать некоторые моменты при работе с ним. Например, если в исходном коде html в таблице нет тега , то tidy его создает сам. Поэтому нужно повнимательней быть при составлении xpath запросов.
  • +1
    Вы можете применять его для парсинга любой html-страницы

    Если она является валидным xhtml-документом.
    • 0
      и в браузере.
    • 0
      то есть «или в браузере»
    • +3
      загрузчик html достаточно сообразителен, чтобы исправить ошибки, поэтому валидность не имеет особого значения — можно парсить даже полный бред:
      $crazyHtml = 'd<dd';
      $dom =new  DomDocument();
      $dom->loadHtml($crazyHtml);
      echo $dom->saveHtml();

      другое дело, что алгоритмы исправлений могут отличаться у разных библиотек и браузеров, давая на выходе разные деревья. например php, этот пример интерпретирует вот так:
      PHP Warning:  DOMDocument::loadHTML(): Couldn't find end of Start Tag dd in Entity, line: 1 in /tmp/a.php on line 3
      <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
      <html><body><p>d</p><dd></dd></body></html>
      
  • +1
    Также как SQL, xpath является декларативным языком программирования.
    Это не язык программирования, а язык запросов. Вы же CSS не считаете языком программирования?
    • 0
      SQL, кстати, тоже языком программирования не является.
      • 0
        SQL с некоторыми модификациями, между прочим, Тьюринг-полный. А значит, такой же язык программирования, как и php/java, etc. Хотя в базовой версии, согласен, не совсем.
        • 0
          С расширениями — вполне.
    • 0
      Спасибо
      исправил
    • 0
      А языки запросов — это не языки программирования? Почему?
  • +2
    Я знакомство с XPath начинал отсюда zvon.org/xxl/XPathTutorial/General_rus/examples.html

    А вообще очень классный язык. А для любителей парсить HTML регулярками — вот stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags#answer-1732454

    Кстати, про оси навигации тоже полезно почитать.

    И еще момент — можно XPath запросы прямо из FireBug консоли напрямую делать через функцию $x например $x("//a/@href");
  • 0
    спс за tidy и список функций — в избранное
  • 0
    После 5ти лет работы с xsl-шаблонами, строки типа ".//*[@id='w3c_home_upcoming_events']/ul/li[1]/div[2]/p[1]/a" уже не удивляют :)
  • 0
    >Старайтесь отказаться от регулярных выражений при парсинге веб-страниц в пользу xpath.
    >Это сделает ваш код проще, понятнее. Вы допустите меньше ошибок. Сократиться время отладки.

    Допустим у меня есть такой узел Numbers, я хочу заменить его на Количество

    Примитивнейше регулярное выражение, а как это сделать при помощи Xpath, ну хотя бы концептуально?
    • 0
      Сорри, пример такой:

      <тэг span>Numbers</тэг span>

      заменить на

      <тэг span>Количество</тэг span>
      • +1
        хотелось бы внести ясность — идея Xpath заключается в получении данных. Для трансформации существует XSLT.
  • +1
    Важное замечание к топику — если вы разбираете DOM дерево средствами PHP(или другого языка), то такой инструмент как FirePath — не лучший помошник, ибо он показывает код, уже адаптированный WebKitом.
    В свою очередь я делал это так:
    PHP(Curl)->Tidy->сохранял в виде файла. Файл открывал чем-нибудь типа SketchPath и оттуда брал готовый XPath.
  • +2
    Общий совет: не надо использоваться в xpath запросах конструкцию // без лишней надобности. Это неэффективно: запрос пройдет *весь* DOM, в поисках удовлетворяющих условиям элементов. Потрудитесь указать полный путь до элемента (ну или хотя бы путь до области где могут быть эти элементы), это поможет вам сэкономить нервы и время на больших объемах данных.
    • 0
      попробуйте SAXparser при работе с большими объемами. Так вы сэкономите не только свое время но и ресурсы сервера.
      • 0
        Разные вещи. Мне нужен DOM, SAX-пасеры тут не помогут.
        • 0
          ru.wikipedia.org/wiki/SAX

          Применяются SAX-парсеры… для чтения XML-потоков большого объема (когда построение DOM требует слишком большого объема памяти).
          • 0
            Я знаю что такое SAX, поэтому и говорю, что разные вещи.

            Смысл в том, xpath-конструкция // неэффективна, и применять ее повсеместно вместо указания пути к элементу не рекомендуется.
            • 0
              это мнимая эффектность.

              Да по полному пути элемент будет найден быстрее, но это мили секунды! DOM дерево в оперативной памяти и поиск по нему осуществляется очень быстро, даже если мы не укажем полный путь.

              Не достаток «полного пути» — в том что эта длинная цепочка узлов сделает ваш запрос плохо читаемым. Увеличивается вероятность допустить ошибку с Вашей стороны и программисту который получит это запрос после вас будет сложнее в нем разобраться.

    • +1
      Согласен, но есть различия при использовании XPath для парсинга сгенерированного XML какого-нибудь API и парсинга HTML сайта — в первом случае глубина дерева обычно гораздо меньше, соответственно xpath от корня гораздо короче. А когда нужно парсить сайты, то XPath от корня проще поломать незначительными изменениями в верстке.

      Еще гораздо более простая и полезная оптимизация — если нужно парсить, допустим, статью и комментарии на хабре, а все остальное (облако тегов, похожие топики) нет, то можно сперва выбрать DIV содержащий И комментарии И статью (для хабра //div[@id='main-content']), сохранить в переменную языка программирования и потом уже искать запросами в этом контексте.
      На примере lxml питона:
      context=document.xpath("//div[@id='main-content']")[0]
      article_title=context.xpath("div[@class='hentry']/h2/span")
      article_content=context.xpath("div[@class='hentry']/div[@class='content']")
      comments=context.xpath("div[@id='comments']/ul")
  • +1
    В качестве альтернативы (и дополнения) к Firepath я так же использую Firefinder
  • –1
    Когда уже люди перестану писать бред типа $_res и поймут что вместо конкатенации достаточно перечисления аргументов echo через запятую…
  • 0
    xPath — действительно замечательная вещь, давно её использую!
    Хотелось бы поделиться информацией об xPath в MySQL, с недавнего времени активно использую в одном проекте, работает чудесным образом и очень шустро. Работа с xml деревьями в MySQL поддерживается с версии 5.1 и выше.
    Работать с XML можно посредствам двух функций: ExtractValue() и UpdateXML()

    Пример:
    SELECT * FROM taxonomy WHERE EXTRACTVALUE(`cluster`,'/sys-cluster/meta/title[self:text()]') = 'products';
    хотя, можно и так:
    SELECT * FROM taxonomy WHERE EXTRACTVALUE(`cluster`,'/sys-cluster/meta/title') = 'products';

    Поражает скорость выборки данным методом:
    mysql> SELECT benchmark(1000000, ExtractValue(@xml, '/a/b/c'));
    +--------------------------------------------------+
    | benchmark(1000000, ExtractValue(@xml, '/a/b/c')) |
    +--------------------------------------------------+
    | 0 |
    +--------------------------------------------------+
    1 row IN SET (4.77 sec)


    Даю ссылки, где можно можно более подробнее с этим ознакомиться и посмотреть примеры:
    http://adw0rd.ru/2009/xpath-mysql/
    и официальный док http://dev.mysql.com/doc/refman/5.1/en/xml-functions.html
    • 0
      А индекс по XPath не умеет делать? Если нет, то не очень юзабельно ИМХО. (По крайней мере слышал, что в MSSQL индексы по полям XML поля можно строить, сам не щупал)
      • 0
        По XPath индексы делать нельзя. Но полнотекстовый поиск после некоторых шаманств с бубном сделать можно.
    • 0
      Хабралюди, помогите получить 4 кармы, есть интереснейшие посты по MySQL и PHP, уже подготовил, но нет возможности запостить. По XPath в том числе. Большое спасибо тем, кто откликнется. Прошу прощения за частичный офтоп.
  • 0
    Как раз появилась задача парсинга HTML страницы как XML документа и ваша статья очень пригодилась. Спасибо!
  • 0
    Спасибо, отличная статья. Но как быть с кодировкой? страница — в UTF-8, locale системы — UTF-8, на выходе — белиберда :(
    • 0
      Какая кодировка у файла скрипта на выходе которого белиберда?
      • 0
        UTF-8. Проблема решена простым хаком, найденным на странице документации в комментариях: $doc->loadHTML('<?xml encoding=«UTF-8»>'. $html); Это есть баг…
        • 0
          Или так

          //==================================================
          $content = mb_convert_encoding($content, 'HTML-ENTITIES', "UTF-8");
          //==================================================
          
  • 0
    Существует программа паук позволяющая обработать все странички сайта и применить к каждой страничке набор XPath выражений.
    Там же в ней есть сразу и возможность протестировать XPath выражения
    http://dirs.info/spider/

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