В процессе работы может возникнуть ситуация, когда очень сложно подобрать уникальный CSS-селектор для выбранного элемента. В этом случае может помочь инструмент XPath, который позволяет запускать поиск по пути в исходном дереве HTML-документа.
Представляет собой достаточно гибкий и мощный инструмент, однако в силу особенностей работы использовать его необходимо с осторожностью.
С одной стороны, XPath позволяет формировать достаточно сложные запросы, что предоставляет больше возможностей для реализации логики скрипта. С другой стороны, если разработчик веб-сайта изменит текущую структуру страницы, искомый элемент перестанет находиться, потребуется редактирование кода для восстановления работоспособности парсера.
В основе работы XPath лежит поиск по древовидной структуре документа, чем и объясняются его ключевые особенности.
Запрос XPath всегда начинается с символа // или /
Символ «//» аналогичен пробелу в CSS, символ «/» — аналог «>»
element1/element2 – будет найдены элементы element2, которые при этом являются прямыми потомками element1(следующий уровень вложенности).
element1//element2 – в этом случае уровень вложенности не будет учитываться.
Важный момент. Если запрос начать с символа /, в качестве корня запроса всегда будет использоваться элемент <html>, что достаточно удобно.
Если запрос начинается с //, осуществляется поиск всех потомков корневого элемента, без уточнения, кто является корнем. К примеру, если нужно найти хедер, достаточно использовать конструкцию //header, ожидая, что на странице только один элемент с таким названием.
В качестве примера разберем задачу поиска подзаголовка h3 в статье:
//article/header/h3[text()=’Подзаголовок в cтатье’]
Данная конструкция выполняет следующее:
//article – запускает поиск всех элементов article на указанной странице, начиная с корневого элемента, далее осуществляется поиск по всему дереву.
/header – переход к дочернему элементу header для каждого найденного article.
/h3 – переход к дочернему элементу h3 для каждого обнаруженного header.
[text()=’Подзаголовок в статье’] – дополнительная фильтрация найденных заголовков по текстовому содержимому. Остается только элементы, которые имеют указанный текст.Команда для фильтрации найденных элементов
Если по основному запросу найдено более одного элемента, XPath позволяет запустить дополнительную фильтрацию. Правила указываются в квадратных скобках.
Правил фильтрации достаточно много, что открывает широкие возможности для поиска:
По атрибутам. Фильтрацию можно произвести по любому атрибуту — id, class, title и других. Например, если необходимо найти ссылку с определенным id, необходимо использовать следующую конструкцию:
//a[@id='url_back']
Символ @ указывает на атрибут, ‘url_back’ – значение id, которое необходимо найти.
По порядковому номеру. Данный режим удобно использовать при работе со списком элементов, при этом точно известен порядковый номер нужного. Например, чтобы выбрать четвертый элемент li внутри ul достаточно использовать конструкцию:
//ul/li[4]
В этом случае в скобках явно указывается, какой элемент необходимо взять.
По полному совпадению текста. Если возникла необходимость найти элемент по точному текстовому содержанию, то такая задача под силу только XPath. Для иллюстрации будет использован пример с поиском кнопки с надписью «Зарегистрироваться»:
//button[text()="Зарегистрироваться"]
Стоит помнить, что элемент будет найден только при полном совпадении текста. Такой подход может оказаться полезным, однако также стоит проявлять осторожность. Некоторые сайты могут динамически менять данный параметр, например, при смене языка интерфейса.
Частичное совпадение атрибута или текста. В этом случае необходимо использовать функцию contains. Она имеет следующий формат:
//p[contains(text(), "Цена")]
Будут возвращены все абзацы, в которых есть указанное слово или фраза. Такой подход полезен, если текст формируется динамически или содержит дополнительные символы. Метод также можно использовать для поиска по частичному совпадению атрибутов, что выручает во многих ситуациях.
К примеру, в коде присутствует тег ul, внутри используются теги li с атрибутами id, которые имеют значения material_bracer и material_frame. Чтобы не искать их отдельно можно использовать селектор:
//li[contains(@id, "material")]
//li – поиск всех тегов <li>, двойной слеш указывает, что поиск необходимо вести на всех уровнять вложенности, начиная от корневого узла.
Далее в квадратных скобках уточняются критерии поиска:
contains() – функция, которая проверят, есть ли в заданном атрибуте указанное значение.
@id – символ @ применяется в XPath для обозначения атрибута;
material – строка, наличие которой проверяется в атрибуте id.
Выбор всех элементов при помощи символа *
В XPath данный символ указывает, что необходимо выбрать все элементы, которые соответствуют указанному элементу. Полезно, если нет возможности узнать точный тег для поиска. К примеру, нужно найти текст цены в определенном заголовке. Для этого можно использовать запрос:
//div/*[@class="price"]
Будут найдены все элементы внутри тега div, которые имеют класс price, тип учитываться не будет.
Такая конструкция удобна для организации широкого поиска, однако всегда стоит помнить о вероятность попадания лишних элементов.
Чувствительность к регистру
Поиск через XPath чувствителен к регистру, что необходимо учитывать при написании кода. Для последнего примера «Price» и «price» Дадут разные результаты, с такой ошибкой часто сталкиваются начинающие пользователи.
Общие рекомендации по использованию XPath
Рекомендуется избегать непонятных и сложных селекторов вида //div[1]/div[2]/div[3]. Такая структура сложна для понимания и подразумевает сильную привязку к текущей структуре веб-страницы. Если разработчик даже незначительно изменит расположение ключевых элементов, парсер перестанет корректно работать.
Необходимо отдавать предпочтение CSS-селекторам. Если есть возможность их применить, лучше использовать данную возможность. Такой формат более нагляден и не настолько зависит от изменений на сайте.
Поиск по тексту и атрибутам. Функционал XPath позволяет запускать поиск по частичному или полному совпадению текста или атрибуту. Однако такой подход также может работать нестабильно.
Использовать удобство навигации по документу. Встроенные инструменты позволяют реализовать удобную навигацию по исходной структуре документа. Можно переходить к дочернему элементу и обратно. Можно использовать, к примеру, если нет возможности сразу выделить дочерний элемент. Можно сформировать запрос на поиск родительского элемента, после чего уже двигаться дальше по структуре.
Поиск всех дочерних элементов:
//div[@id='parent']/child::*
Переход к родительскому элементу:
//div[@id='child']/parent::*
Также есть возможность переходить к соседним элементам. Возврат всех следующих соседних элементов после div с id=’prev_sibling’:
//div[@id='prev_sibling']/following-sibling::*
Аналогично можно перейти к предыдущим:
//div[@id='next_sibling']/preceding-sibling::*
Также можно перейти к конкретному дочернему элементу. Получение первого дочернего элемента p, который принадлежит div с id=’parent’:
//div[@id='parent']/child::p[1]
Поиск по вложенным элементам. Данный запрос вернет все элементы span, которые являются потомками div с id=’ancestor’:
//div[@id='ancestor']//child::span
Если код сайта не позволяет полноценно искать информацию по атрибутам, XPath позволяет находить обходные пути. Самая простая альтернатива – искать по структурному расположению. Это реализуется достаточно просто и обеспечивает нужную стабильность работы парсера.
Получение первых дочерних элементов для всех div:
//div/*[1]
Для получения последних элементов можно использовать запрос:
//div/*[last()]
Предусмотрена возможность найти по порядковому номеру. Получение второго элемента li во всех ul:
//ul/li[position()=2]
Можно использовать логические операции. Получение всех div, которые имеют хотя бы один дочерний элемент:
//div[count(*) > 0]
Поиск на определенной глубине. Получение всех элементов p, которые расположены на четвертом уровне вложенности:
//*/*/*/*[name()='p']
Автоматические генераторы XPath
Предусмотрены готовые инструменты для генерации нужных запросов, например, в формате расширений для браузеров. Однако не стоит им слепо доверять, поскольку часто они предоставляют очень сложные и плохо читаемые селекторы. Лучше все же потратить время и разобраться в синтаксисе, чтобы научиться самостоятельно писать нужные запросы.
Также полученные запросы могут быть очень чувствительными к изменению структуры сайта. Такие изменения могут периодически появляться на сайте, ломая исходную логику скрипта.