Введение в XPath
Язык разметки XML с самого первого стандарта окружает пользователей компьютеров. Таблицы в Excel, выгрузки из интернет-магазинов, RSS-ленты с новостями — все это основано на XML. Хоть визуальное отображение отличается на устройствах и в программах, но в основе всегда лежит единый формат.
Внутри XML-файла может находиться огромное количество информации, поэтому и встает вопрос о перемещении и выборке внутри документа. Как это сделать быстро? Какие средства применять, чтобы в интернет-магазине найти нужный товар из десятков тысяч других? Для навигации и поиска внутри XML используется язык запросов XPath.
В этой статье разберем:
- для кого может быть полезен XPath
- базовые конструкции языка для поиска информации в XML
- чем XPath отличается от CSS-селекторов при поиске в HTML
Синтаксис XPath
Для начала создадим базовый пример XML, с которым и будем работать весь урок. Например, список курсов по верстке на Хекслете в XML будет выглядеть так:
<?xml version="1.0" encoding="UTF-8"?>
<courses>
<title>Курсы HTML и CSS (верстка)</title>
<description>На курсах по верстке вы познакомитесь с основами HTML и CSS, научитесь верстать адаптивные страницы, работать с препроцессорами. Освоите современные технологии и инструменты, включая Flex, Sass, Bootstrap.</description>
<course>
<name>Основы современной верстки</name>
<tags>HTML5, CSS, DevTools, верстка</tags>
<duration value="9">9 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/layout-designer-basics</url>
<url lang="en">https://hexlet.io/courses/layout-designer-basics</url>
</course>
<course>
<name>Основы верстки контента</name>
<tags>CSS3, HTML5, Селекторы, Доступность, CSS Columns, CSS Units, Верстка</tags>
<duration value="18">18 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/css-content</url>
<url lang="en">https://hexlet.io/courses/css-content</url>
</course>
<course>
<name>Bootstrap 5: Основы верстки</name>
<tags>Bootstrap 5, Адаптивность, HTML, CSS3</tags>
<duration value="10">10 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/bootstrap_basic</url>
<url lang="en">https://hexlet.io/courses/bootstrap_basic</url>
</course>
</courses>
Это учебный пример, но для отработки навыков XPath подойдет и любой другой XML. Принципы XPath сохранятся при любой структуре файла, потому что по стандарту XML можно использовать элементы с произвольными тегами.
Для тестирования результата подойдут такие онлайн-сервисы, как:
Абсолютные пути
Самый простой запрос состоит из обращения к корневому элементу. Для этого достаточно выполнить запрос /courses
. Нам вернется XML в почти таком же виде, что и в примере выше. Обратите внимание на строку <?xml version="1.0" encoding="UTF-8"?>
. Она отличается, потому что элемент не внутри <courses>
:
<courses>
<title>Курсы HTML и CSS (верстка)</title>
<description>На курсах по верстке вы познакомитесь с основами HTML и CSS, научитесь верстать адаптивные страницы, работать с препроцессорами. Освоите современные технологии и инструменты, включая Flex, Sass, Bootstrap.</description>
<course>
<name>Основы современной верстки</name>
<tags>HTML5, CSS, DevTools, верстка</tags>
<duration value="9">9 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/layout-designer-basics</url>
<url lang="en">https://hexlet.io/courses/layout-designer-basics</url>
</course>
<course>
<name>Основы верстки контента</name>
<tags>CSS3, HTML5, Селекторы, Доступность, CSS Columns, CSS Units, Верстка</tags>
<duration value="18">18 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/css-content</url>
<url lang="en">https://hexlet.io/courses/css-content</url>
</course>
<course>
<name>Bootstrap 5: Основы верстки</name>
<tags>Bootstrap 5, Адаптивность, HTML, CSS3</tags>
<duration value="10">10 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/bootstrap_basic</url>
<url lang="en">https://hexlet.io/courses/bootstrap_basic</url>
</course>
</courses>
В качестве результата XPath возвращает узлы XML-документа.
Продолжим цепочку и обратимся к описанию из элемента <description>
. Для этого добавим в запрос путь к description
: /courses/description
. Результатом выполнения станет:
<description>На курсах по верстке вы познакомитесь с основами HTML и CSS, научитесь верстать адаптивные страницы, работать с препроцессорами. Освоите современные технологии и инструменты, включая Flex, Sass, Bootstrap.</description>
Путь, который строится от корневого элемента, называется абсолютным. Используем схему из прошлого запроса и обратимся к любому элементу внутри XML.
Попробуем обратиться к имени курса. В этом случае вернется поле <name>
из всех курсов. Запрос /courses/course/name
вернет:
<name>Основы современной верстки</name>
<name>Основы верстки контента</name>
<name>Bootstrap 5: Основы верстки</name>
Вот список некоторых базовых запросов и их результат:
Запрос | Результат |
---|---|
/courses/course | Все данные из всех элементов <course></course> |
/courses/course/name | <name>Основы современной верстки</name> <name>Основы верстки контента</name> <name>Bootstrap 5: Основы верстки</name> |
/courses/course/duration | <duration value="9">9 часов</duration> <duration value="18">18 часов</duration> <duration value="10">10 часов</duration> |
Относительные пути
Прошлые запросы строились с помощью абсолютных путей — то есть мы указывали полный путь до информации. Бывают ситуации, когда полный путь не подходит: например, мы хотим обраться к какому-то уникальному полю или не знаем полный путь. В этом случае можно использовать относительный путь — он произведет поиск по всему XML и вернет узлы, подходящие под запрос.
Чтобы записать относительный путь, нужно использовать конструкцию //
. После нее можно написать любое поле и получить результат. Например, //name
вернет поля <name>
из всего XML:
<name>Основы современной верстки</name>
<name>Основы верстки контента</name>
<name>Bootstrap 5: Основы верстки</name>
Проблема такого подхода — уникальность полей. В документах одни и те же имена полей могут обозначать разные данные в зависимости от расположения. Поэтому используйте относительные пути только там, где уверены в возвращаемых данных. Например, в нашем примере название курса может быть заключено в <title>
:
<courses>
<title>Курсы HTML и CSS (верстка)</title>
<!-- ... -->
<course>
<title>Основы современной верстки</title>
<!-- ... -->
</course>
<course>
<title>Основы верстки контента</title>
<!-- ... -->
</course>
<course>
<title>Bootstrap 5: Основы верстки</title>
<!-- ... -->
</course>
</courses>
Запрос //title
вернет не только имена курсов, но и узел, который находится в <courses>
:
<title>Курсы HTML и CSS (верстка)</title>
<title>Основы современной верстки</title>
<title>Основы верстки контента</title>
<title>Bootstrap 5: Основы верстки</title>
Чтобы сэкономить пару секунд, разработчики опускают корневой элемент и пользуются относительными путями. Например, вместо /courses/course/name
они пишут //course/name
. Для практики попробуйте прошлые примеры перевести на относительные пути с помощью такого механизма.
Несколько примеров запросов с идентичными ответами, как и в прошлой таблице:
Запрос | Результат |
---|---|
//course | Все данные из всех элементов <course></course> |
//name | <name>Основы современной верстки</name> <name>Основы верстки контента</name> <name>Bootstrap 5: Основы верстки</name> |
//course/duration | <duration value="9">9 часов</duration> <duration value="18">18 часов</duration> <duration value="10">10 часов</duration> |
Предикаты
В примерах запросов к именам возвращались имена всех найденных курсов. В некоторых ситуациях это может быть избыточно. Что делать, если хочется получить данные только по первому курсу в <courses>
? На помощь приходят предикаты — конструкции, с помощью которых можно отфильтровать элементы по заданным условиям.
Выберем ключевые слова первого курса по верстке. Для этого достаточно использовать запрос //course[1]/tags
:
<tags>HTML5, CSS, DevTools, верстка</tags>
Обратите внимание на[1]
. Это предикат с таким условием: «Взять элемент по индексу 1». Попробуйте сделать запрос ко второму или третьему элементу. Достаточно поменять всего одну цифру!
В XPath индексы элементов начинаются с единицы, а не с нуля, как в принятых стандартах программирования. Если вы уже программируете, это может немного запутать.
Предикаты помогают делать точные выборки. Например, получить ссылки на русскоязычные страницы курсов. Для этого нужно получить элементы <url>
, у которых атрибут lang
равен ru
. Делается это указанием атрибута и значения. Чтобы XPath отличил атрибут от элемента перед атрибутом указывается символ @
.
Теперь запрос будет выглядеть так: //course/url[@lang="ru"]
<url lang="ru">https://ru.hexlet.io/courses/layout-designer-basics</url>
<url lang="ru">https://ru.hexlet.io/courses/css-content</url>
<url lang="ru">https://ru.hexlet.io/courses/bootstrap_basic</url>
Иногда полезно выбрать элементы, которые имеют хоть какой-то атрибут. Для этого можно использовать конструкцию //*[@*]
:
<duration value="9">9 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/layout-designer-basics</url>
<url lang="en">https://hexlet.io/courses/layout-designer-basics</url>
<duration value="18">18 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/css-content</url>
<url lang="en">https://hexlet.io/courses/css-content</url>
<duration value="10">10 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/bootstrap_basic</url>
<url lang="en">https://hexlet.io/courses/bootstrap_basic</url>
По примеру выше видно, знак *
обозначает «все/любой».
Когда выбраны элементы по атрибутам, можно произвести дополнительную фильтрацию по этим значениям. Например, найдем элементы <duration>
со значением атрибута value
больше 9
. Внутри предикатов используются операторы сравнения, знакомые по языкам программирования:
>
— больше<
— меньше>=
— больше или равно<=
— меньше или равно=
— равно!=
— не равно
Запрос будет выглядеть так: //course/duration[@value > 9]
:
<duration value="18">18 часов</duration>
<duration value="10">10 часов</duration>
Мы разобрались, как выбирать одно поле — это интересная, но редкая задача. Чаще разработчики обрабатывают данные по всему файлу или нескольким полям. Попробуем одновременно использовать предикат и обратиться к другим полям. Обратите внимание на два момента:
- Предикат необязательно должен идти в конце запроса
- Внутри предиката могут находиться новые пути, которые нужно проверить
Мы уже знаем, как с помощью предиката отфильтровать данные по полю <duration>
. Эту задачу мы выполняли с помощью конструкции duration[@value > 9]
. А теперь попробуем сделать эту конструкцию предикатом для <course>
. Так мы получим данные о курсах с длительностью больше 9 часов: //course[duration[@value > 9]]
:
<course>
<title>Основы верстки контента</title>
<tags>CSS3, HTML5, Селекторы, Доступность, CSS Columns, CSS Units, Верстка</tags>
<duration value="18">18 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/css-content</url>
<url lang="en">https://hexlet.io/courses/css-content</url>
</course>
<course>
<title>Bootstrap 5: Основы верстки</title>
<tags>Bootstrap 5, Адаптивность, HTML, CSS3</tags>
<duration value="10">10 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/bootstrap_basic</url>
<url lang="en">https://hexlet.io/courses/bootstrap_basic</url>
</course>
Можно продолжить этот запрос и получить только имена курсов. Тогда предикат будет в середине запроса, а не в его конце: `//course[duration[@value > 9]]/name
<name>Основы верстки контента</name>
<name>Bootstrap 5: Основы верстки</name>
Функции
В прошлых примерах запросы затрагивали теги и атрибуты. Сами данные мы не затрагивали, хотя это огромный пласт информации, по которой можно делать выборки. Для решения этой задачи используются встроенные в XPath функции. Они являются частью предикатов — например, @
. Попробуем найти курс с названием «Основы верстки контента».
Для поиска по тексту внутри элемента используется функция text()
. Ее задача — получить текстовое значение элемента и сравнить его с условием по необходимости. Вот как будет выглядеть запрос для поиска курса с нужным именем: //course[name[text()="Основы верстки контента"]]
<course>
<name>Основы верстки контента</name>
<tags>CSS3, HTML5, Селекторы, Доступность, CSS Columns, CSS Units, Верстка</tags>
<duration value="18">18 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/css:content</url>
<url lang="en">https://hexlet.io/courses/css:content</url>
</course>
Но что, если нам известно только часть названия? Для этого существует функция contains()
, которая принимает два аргумента:
- Строка, где будет производиться поиск
- Подстрока, которая будет искаться
Для примера найдем курс, у которого в ключевых словах есть слово «Bootstrap». Функция примет текстовое значение элемента tags
и найдет там слово «Bootstrap»: //course[tags[contains(text(), "Bootstrap")]]
<course>
<name>Bootstrap 5: Основы верстки</name>
<tags>Bootstrap 5, Адаптивность, HTML, CSS3</tags>
<duration value="10">10 часов</duration>
<url lang="ru">https://ru.hexlet.io/courses/bootstrap_basic</url>
<url lang="en">https://hexlet.io/courses/bootstrap_basic</url>
</course>
В стандарте XPath существует еще несколько функций, но цель статьи — показать принципы работы тех или иных механизмов, а не дать исчерпывающую документацию по всему языку.
Отличия от CSS-селекторов
Если вы писали на JavaScript, то знаете, что элементы можно искать с помощью CSS-селекторов, используя методы querySelector()
или querySelectorAll()
. Почему же разработчики иногда ищут элементы внутри HTML именно с помощью XPath?
Дело в концепции поиска элементов. Используя CSS, можно идти только в глубину без возможности обратиться к родительским элементам. В отличие от CSS, XPath позволяет в любой момент обращаться и к дочерним, и к родительским элементам.
Если вы хотите подробнее изучить поиск по HTML с помощью XPath, рекомендуем обратиться к статье Introduction to using XPath in JavaScript.
С помощью CSS нельзя найти все элементы div
, внутри которых есть ссылки — можно найти сами ссылки, но не их родителей. XPath позволяет это сделать простым сочетанием div[a]
. Постепенно ситуация меняется: в CSS появился селектор :has()
, но он поддерживается еще не всеми новыми версиями браузеров. Со временем это изменится, но пока реальность именно такая.
Другой пример — поиск элементов по тексту внутри них. С этой задачей CSS никогда не справится, так как такой цели у него нет. XPath, как мы изучили, умеет это делать с помощью функции text()
.
Кому нужен Xpath
Если коротко, Xpath нужен всем, кто работает с XML.
Чтобы разобраться подробнее, изучим несколько примеров:
SEO-специалисты. Специалисты по продвижению часто обрабатывают большие массивы данных и вытаскивают информацию со страниц сайта.
Например, для них критичны мета-теги — дополнительная информация, в которой содержатся иконки сайтов, название страницы, описание и так далее. Эту информацию SEO-специалист может автоматически парсить с помощью запросов в XPath.
Тестировщики. При работе с Front-end тестировщики часто проверяют тот или иной вывод информации на странице — для этого они выбирают отдельные элементы с нужной страницы. Это можно делать через XPath и DevTools, встроенный в браузеры на основе Chromium.
Разработчики. Они часто используют парсеры — это скрипты, которые ищут нужную информацию на страницах одного или нескольких сайтов. Например, мы хотим сравнить стоимость одного и того же товара в разных магазинах. Для такой задачи можно написать скрипт, который пройдется по всем нужным сайтам, сравнит цены и вернет данные. В этом случае для поиска информацию на странице можно использовать XPath.
Это лишь часть сценариев, в которых пригождается язык XPath — на самом деле, их десятки.
Заключение
В этой статье мы рассмотрели, где встречается XML и кому он может пригодиться. Мы научились составлять базовые запросы и изучили часто используемые конструкции XPath:
- Абсолютные и относительные пути
- Предикаты
- Поиск по атрибутам
- Операторы сравнения
- Функции
Также теперь вы знаете, что поиск по HTML с помощью XPath может быть эффективнее поиска с помощью CSS-селекторов.
В этой статье мы постарались дать знания, которые помогут справиться с большинством задач. Но это далеко не все возможности XPath — это более глубокий язык, чем представлено в статье. Как и с другими технологиями, тут важно набить руку. Чем больше вы практикуетесь, тем более точные и полезные запросы пишете.