Как составить xpath элемента

Язык разметки XML с самого первого стандарта окружает пользователей компьютеров. Таблицы в Excel, выгрузки из интернет-магазинов, RSS-ленты с новостями — все это основано на XML. Хоть визуальное отображение отличается на устройствах и в программах, но в основе всегда лежит единый формат.

Внутри XML-файла может находиться огромное количество информации, поэтому и встает вопрос о перемещении и выборке внутри документа. Как это сделать быстро? Какие средства применять, чтобы в интернет-магазине найти нужный товар из десятков тысяч других? Для навигации и поиска внутри XML используется язык запросов XPath.

В этой статье разберем:

  • для кого может быть полезен XPath
  • базовые конструкции языка для поиска информации в XML
  • чем XPath отличается от CSS-селекторов при поиске в HTML
  • Синтаксис XPath
  • Отличия от CSS-селекторов
  • Кому нужен Xpath
  • Заключение

Синтаксис 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 можно использовать элементы с произвольными тегами.

Для тестирования результата подойдут такие онлайн-сервисы, как:

  • Code Beautify
  • XPather

Абсолютные пути

Самый простой запрос состоит из обращения к корневому элементу. Для этого достаточно выполнить запрос /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(), которая принимает два аргумента:

  1. Строка, где будет производиться поиск
  2. Подстрока, которая будет искаться

Для примера найдем курс, у которого в ключевых словах есть слово «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 — это более глубокий язык, чем представлено в статье. Как и с другими технологиями, тут важно набить руку. Чем больше вы практикуетесь, тем более точные и полезные запросы пишете.

article feature image

When it comes to parsing web-scraped HTML content, there are multiple techniques to select the data we want.
For simple text parsing, regular expression can be used, but HTML is designed to be a machine-readable text structure. We can take advantage of this fact and use special path languages like CSS selectors and XPath to extract data in a much more efficient and reliable way!

You are probably familiar with CSS selectors from the style sheet documents (.css) however, XPath goes beyond that and implements full document navigation in its own unique syntax.

Parsing HTML with CSS Selectors

For parsing using CSS selectors see the CSS version of this article

Parsing HTML with CSS Selectors

In this article, we’ll be taking a deep look at this unique path language and how can it be used to extract needed details from modern, complex HTML documents. We’ll start with a quick introduction and expression cheatsheet and explore concepts using an interactive XPath tester.

Finally, we’ll wrap up by covering XPath implementations in various programming languages and some common idioms and tips when it comes to XPath in web scraping. Let’s dive in!

What is Xpath?

XPath stands for “XML Path Language” which essentially means it’s a query language that described a path from point A to point B for XML/HTML type of documents.
Other path languages you might know of are CSS selectors which usually describe paths for applying styles, or tool-specific languages like jq which describe paths for JSON-type documents.

For HTML parsing, Xpath has some advantages over CSS selectors:

  • Xpath can traverse HTML trees in every direction and is location-aware.
  • Xpath can transform results before returning them.
  • Xpath is easily extendable with additional functionality.

Before we dig into Xpath let’s have a quick overview of HTML itself and how it enables xpath language to find anything with the right instructions.

HTML Overview

HTML (HyperText Markup Language) is designed to be easily machine-readable and parsable. In other words, HTML follows a tree-like structure of nodes and their attributes, which we can easily navigate programmatically.

Let’s start off with a small example page and illustrate its structure:

<head>
  <title>
  </title>
</head>
<body>
  <h1>Introduction</h1>
  <div>
    <p>some description text: /p>
    <a class="link" href="https://example.com">example link</a>
  </div>
</body>

In this basic example of a simple web page, we can see that the document already resembles a data tree. Let’s go a bit further and illustrate this:

html tree illustration

HTML tree is made of nodes which can contain attributes such as classes, ids and text itself.

Here we can wrap our heads around it a bit more easily: it’s a tree of nodes and each node can also have properties attached to them like keyword attributes (like class and href) and natural attributes such as text.

Now that we’re familiar with HTML let’s familiarize ourselves with Xpath itself!

Xpath Syntax Overview

Xpath selectors are usually referred to as “xpaths” and a single xpath indicates a destination from the root to the desired endpoint. It’s a rather unique path language, so let’s start off with a quick glance over basic syntax.

Average xpath selector in web scraping often looks something like this:

xpath protocol illustration

illustration of a usual xpath selector’s structure

In this example, XPath would select href attribute of an <a> node that has a class “button” which is also directly under <div> node:

<div>
<a class=”button” href=”http://scrapfly.io”>ScrapFly</a>
</div>

Xpath selectors are made up of multiple expressions joined together into a single string. Let’s see the most commonly used expressions in this XPath cheat sheet:

expression description
/node selects a direct child that matches node name.
//node selects any descendant – child, grandchild, gran-grandchild etc.
* wildcard can be used instead of node name
@ selects an attribute of a node e.g. a/@href will select value of href attribute of an a node
text() selects text value of a node
[] selector constraint – can be used to filter out nodes that do no match some condition
parent or .. select current nodes parent e.g. /div/a/.. will select div element
self or . select current node (this is useful as argument in xpath function, we’ll cover more later)
following-sibling::node selects all following siblings of type, e.g. following-sibling::div will select all div siblings below the current node
preceding-sibling::node selects all preceding siblings of type, e.g. preceding-sibling::div will select all div siblings below the current node
function() calls registered xpath function e.g. /a/text() will return text value of a node

This XPath cheatsheet might be a lot to take in – so, let’s solidify this knowledge with some real-life examples.

Basic Navigation

When writing xpaths the first thing we should be aware of is that all xpaths have to have a root (aka point A) and final target (aka point B). Knowing this and xpath axis syntax, we can start describing our selector path:

<div>
<p class=”socials”>
Follow us on
<a href=”https://twitter.com/@scrapfly_dev”>Twitter!</a>
</p>
</div>

Here, our simple xpath simply describes a path from the root to the a node.
All we used is / direct child syntax, however with big documents direct xpaths are often unreliable as any changes to the tree structure or order will break our path.

It’s better to design our xpaths around some smart context. For example, here we can see that this <a> node is under <p class="socials"> node – we can infer strong sense that these two will most likely go together:

<div>
<p class=”socials”>
Follow us on
<a href=”https://twitter.com/@scrapfly_dev”>Twitter!</a>
</p>
</div>

With this xpath, we get rid a lot of structure dependency in favor of context. Generally, modern websites have much more stable contexts than structures, and finding the right balance between context and structure is what creates reliable xpaths!

Further, we can combine constrains option ([]) with value testing functions such as contains() to make our xpath even more reliable:

<div>
<p class=”socials”>
Follow us on
<a href=”https://twitter.com/@scrapfly_dev”>Twitter!</a>
</p>
</div>

Using XPath contains() text function, we can filter out any results that don’t contain a piece of text.

Xpath functions are very powerful and not only they can check for truthfulness but also modify content during runtime:

<div>
<p class=”socials”>
Follow us on
<a href=”https://twitter.com/@scrapfly_dev”>Twitter!</a>
or connect with us on
<a href=”https://www.linkedin.com/company/scrapfly/”>Linkedin</a>
</p>
</div>

Here, we’ve added concat() function, which joins all provided arguments into a single value and only then perform our match check.

Navigating Complex Structures

Sometimes tree complexity outgrows context based selectors and we have to implement some complex structure checks. For that, xpath has powerful tree navigation features that allow to select ancestors and siblings of any level:

<div>
<span>For price contact </span>
<a>Sales department </a>
<div>
<span>total: </span>
</div>
<span>166.00$</span>
<span>*taxes apply</span>
</div>

In this example, we find a text containing the phrase “total”, navigate up to its parent and get the first following sibling using XPath.

Other times, we need to use position-based predicates and even combine multiple XPaths to reliably parse HTML data:

<div>
<span>items: </span>
<span>(taxes not included)</span>
<span>166.00$</span>
<span>25.00$</span>
<span>*taxes apply</span>
<div>
<span>addons:</span>
<span>0.5$</span>
</div>
</div>

In this example, we used position() function to select only siblings that are in specific range. We also combined to xpaths using the | operator (for or operation or operator can being used) to fully retrieve all pricing info.

As you can see, xpaths can be very powerful and parse almost any html structure if we get creative with path logic!

Extending Functions

Xpath in most clients can be extended with additional functions, and some clients even come with pre-registered non-standard functions.

For example, in Python’s lxml (and it’s based packages like parsel) we can easily register new functions like this:

from lxml import etree

def myfunc(context, *args):
    return True 

xpath_namespace = etree.FunctionNamespace(None)
xpath_namespace['myfunc'] = myfunc

Other language clients follow a similar process.

Xpath Clients

Almost every programming language contains some sort of xpath client for XML file parsing. Since HTML is just a subset of XML we can safely use xpath in almost every modern language!

Xpath in Python

In Python there are multiple packages that implement xpath functionality, however most of them are based on lxml package which is a pythonic binding of libxml2 and libxslt C language libraries. This means Xpath selectors in Python are blazing fast, as it’s using powerful C components under the hood.

While lxml is a great wrapper, it lacks a lot of modern API usability features used in web scraping. For this, lxml based packages parsel (used by scrapy) and pyquery provide a richer feature set.

Example usage with parsel:

from parsel import Selector

html = """
<html>
  <head>
    <title>Page Title</title>
  </head>
  <body>
    <h1 class="title">Page header by <a href="#">company</a></h1>
    <p>This is the first paragraph</p>
    <p>This is the second paragraph</p>
  </body>
</html>
"""
sel = Selector(html)
sel.xpath("//p").getall()
# [
# "<p>This is the first paragraph</p>",
# "<p>This is the second paragraph</p>",
# ]

Other tool recommendations:

  • cssselect – converts css selectors to xpath selectors
  • parsel-cli – real time REPL for testing css/xpath selectors

Xpath in PHP

In PHP most popular xpath processor is Symphony’s DomCrawler:

use SymfonyComponentDomCrawlerCrawler;

$html = <<<'HTML'
<html>
  <head>
    <title>Page Title</title>
  </head>
  <body>
    <h1 class="title">Page header by <a href="#">company</a></h1>
    <p>This is the first paragraph</p>
    <p>This is the second paragraph</p>
  </body>
</html>
HTML;

$crawler = new Crawler($html);
$crawler->filterXPath('//p');

Other tool recommendations:

  • css-selector – converts css selectors to xpath selectors

Xpath in Javascript

Javascript supports xpath natively, you can read more about it on MDN’s Introduction to Using Xpath in Javascript

Other tool recommendations:

  • jQuery – extra syntax sugar and helpers for xpath querying.
  • cash – lightweight, modern jQuery alternative.

Xpath in Other Languages

Most other languages have some sort of XPath client as XML parsing is an important data exchange feature. Meaning, we can parse web-scraped content in the language of our choice!

For example, C# supports XPath natively as well, you can read more about it over at the official documentation so does Objecive C and other low-level languages.

While some languages might not have first-party XPath clients it’s very likely there’s a community package. For example, Go language has community packages for xpath in xml, html and even json.

Xpath in Browser Automation

Browser automation tools support XPath without any additional Javascript packages.

To access XPath in Selenium we can use by.XPath selector. For example, for Selenium in Python:

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://httpbin.org/html")

element = driver.find_element(By.XPATH, '//p')

To access XPath in Playwright we can use the locator functionality which take either CSS selectors or XPath as the argument. For example in Playwright and Python:

from playwright.sync_api import sync_playwright

with sync_playwright() as pw:
    browser = pw.chromium.launch(headless=False)
    context = browser.new_context(viewport={"width": 1920, "height": 1080})
    page = context.new_page()

    page.goto("http://httpbin.org/html")
    paragraphs = page.locator("//p")  # this can also take CSS selectors

To access XPath in Puppeteer we can use the $ and $$ methods. For example in Puppeteer in Javascript:

const puppeteer = require('puppeteer')
const browser = await puppeteer.launch();
let page = await browser.newPage();
await page.goto('http://httpbin.org/html');
await page.$("//p");

FAQ

To wrap this introduction up let’s take a look at some frequently asked questions regarding HTML parsing using XPath selectors:

Is XPATH faster than CSS selectors?

Many CSS selector libraries convert CSS selectors to XPATH because it’s faster and more powerful. That being said it depends on each individual library and complexity of the selector itself. Some XPATH selectors which use broad selection paths like // can be very expensive computationally.

My xpath selects more data than it should

XPATH broad selector paths like // are global rather than relative. To make them relative we must add the relativity marker . -> .//

How to match nodes by multiple names?

To match nodes by multiple names we can use wildcard selector together with a name check condition: //*[contains("p h1 head", name())] – will select h1, p and head nodes.

How to select select elements between two nodes?

If we know two nodes like text headers we can select text between with clever use of preceding-sibling notation:
//h2[@id="faq"]//following-sibling::p[(preceding-sibling::h2[1])[@id="faq"]] – this xpath selects all paragraph nodes under h2 tag with id faq and not elements under other h2 nodes.

<div>
<span>items: </span>
<span>(taxes not included)</span>
<span>166.00$</span>
<span>25.00$</span>
<span>*taxes apply</span>
<div>
<span>addons:</span>
<span>0.5$</span>
</div>
</div>

Summary

In this article, we’ve introduced ourselves with xpath query language. We’ve discovered that HTML documents are data trees with nodes and attributes which can be machine parsed efficiently.

We glanced over the most commonly used XPath syntax and functions and explored common HTML parsing scenarios and best practices using our interactive XPath tester.

Xpath is a very powerful and flexible path language that is supported in many low-level and high-level languages: Python, PHP, Javascript etc. – so, whatever stack you’re using for web-scraping, XPath can be your to-go tool for HTML parsing!

For more XPath help we recommend visiting Stackoverflow’s #xpath tag and see our data parsing tag for more articles on data parsing.

XPath – это невероятно гибкий, мощный, и вместе с тем сравнительно простой инструмент для навигации по документам XML. Предлагаю перевод руководства по XPath, сделанный на основе руководства консорциума W3C.

Краткий справочник по XPath

XPath

XPath используется для навигации по элементам и атрибутам XML-документа. XPath является одним из основных элементов в стандарте XSLT консорциума W3C.

1Что такое XPath

XPath
  • XPath определяет синтаксис для определения частей документа XML;
  • XPath использует выражения пути для навигации по элементам XML-документов;
  • XPath содержит библиотеку стандартных функций;
  • XPath является одним из основных элементов в XSLT;
  • XPath является рекомендацией W3C.

Выражения XPath

XPath использует выражения пути для выбора отдельных узлов или набора узлов в документе XML. Эти выражения очень похожи на выражения, которые вы видите, когда работаете с традиционной файловой системой компьютера.

Стандартные функции XPath

XPath включает в себя более 100 встроенных функций. Есть функции для строковых и числовых значений, даты и времени, сравнения узлов и манипулирования QName, управления последовательностями, булевых значений, и многое другое.

XPath используется в XSLT

XPath является одним из основных элементов в стандарте XSLT. Без знания XPath вы не будете иметь возможность создавать XSLT-документы.

XPath является рекомендацией консорциума W3C

XPath стал рекомендацией W3C 16 ноября 1999 года. XPath был разработан для использования в XSLT, XPointer и другом программном обеспечении для разбора (парсинга) документов XML.

2Терминология XPath

Узлы

В XPath существует семь видов узлов: элемент, атрибут, текст, пространство имён, инструкции обработки, комментарии и узлы документа. XML-документы обрабатываются в виде деревьев узлов. Верхний элемент дерева называется корневым элементом. Посмотрите на следующий документ XML:

<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
    <book>
        <title lang="en">Harry Potter</title>
        <author>J. K. Rowling</author>
        <year>2005</year>
        <price>29.99</price>
    </book>
</bookstore>

Пример узлов в документе XML выше:

<bookstore> (корневой элемент)
<author>J. K. Rowling</author> (узел)
lang="en" (атрибут)

Атомарные значения

Атомарные значения являются узлами, не имеющие детей или родителей. Пример атомарных значений:

J. K. Rowling
"en"

Элементы

Элементы – это атомарные значения или узлы.

3Отношенияузлов

Родитель

Каждый элемент и атрибут имеет одного родителя. В следующем примере элемент «книга» (book) является родителем элементов «название» (title), «автор» (author), «год» (year) и «цена» (price):

<book>
    <title>Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
</book>

Потомки

Узлы элементов могут иметь ноль, один или более потомков. В следующем примере элементы «название», «автор», «год» и «цена» – они все потомки элемента книга:

<book>
    <title>Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
</book>

Элементы одного уровня

Это узлы, которые имеют одного и того же родителя. В следующем примере элементы «название», «автор», «год» и «цена» все являются элементами одного уровня:

<book>
    <title>Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
</book>

Предки

Родитель узла, родитель родителя узла и т.д. В следующем примере предки элемента «название» (title) – это элементы «книга» (book) и «книжный магазин» (bookstore):

<bookstore>
    <book>
        <title>Harry Potter</title>
        <author>J K. Rowling</author>
        <year>2005</year>
        <price>29.99</price>
    </book>
</bookstore>

Потомки

Дети узла, дети детей узла и т.д. В следующем примере потомками элемента «книжный магазин» являются элементы «книга», «название», «автор», «год» и «цена»:

<bookstore> 
    <book> 
        <title>Harry Potter</title>
        <author>J K. Rowling</author>
        <year>2005</year>
        <price>29.99</price>
    </book>
</bookstore>

4Синтаксис XPath

XPath использует выражения пути для выбора узлов или множества узлов в документе XML. Узел можно выбрать, следуя пути или по шагам. Мы будем использовать следующий XML-документ в приведённых ниже примерах.

<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
    <book>
        <title lang="en">Harry Potter</title>
        <price>29.99</price>
    </book>
    <book>
        <title lang="en">Learning XML</title>
        <price>39.95</price>
    </book>
</bookstore>

Выбор узлов

С помощью выражений XPath для выбора узлов в документе XML можно выбрать узел, следуя пути или шагам. Самые полезные выражения пути перечислены ниже:

Выражение Описание
имя_узла Выбирает все узлы с именем имя_узла
/ Выбирает от корневого узла
// Выбирает узлы в документе от текущего узла, который соответствует выбору, независимо от того, где они находятся
. Выбирает текущий узел
.. Выбирает родителя текущего узла
@ Выбирает атрибуты

В приведенной ниже таблице перечислены некоторые пути выражения и результат выполнения выражения:

Выражение XPath Результат
bookstore Выбирает все узлы с именем “bookstore”
/bookstore Выбирает корневой элемент книжного магазина

Примечание: Если путь начинается с косой черты (/), он всегда представляет собой абсолютный путь к элементу!

bookstore/book Выбирает все элементы «книга» (book), которые являются потомками элемента «книжный магазин» (bookstore)
//book Выбирает все элементы «книга» независимо от того, где они находятся в документе
bookstore//book Выбирает все элементы «книга», которые являются потомком элемента «книжный магазин», независимо от того, где они находятся под элементом «книжный магазин»
//@lang Выбирает все атрибуты, которые называются “lang”

Предикаты

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

Выражения XPath Результат
/bookstore/book[1] Выбирает первый элемент «книга», который является потомком элемента «книжный магазин».

Примечание: В IE 5,6,7,8,9 первый узел имеет индекс [0], но в соответствии с рекомендациями W3C, это [1]. Для решения этой проблемы в IE, задаётся опция “SelectionLanguage” для XPath:

На JavaScript: xml.setProperty(“SelectionLanguage”, “XPath”);

/bookstore/book[last()] Выбирает последний элемент «книга» (book), который является дочерним элементом элемента «книжный магазин» (bookstore)
/bookstore/book[last()-1] Выбирает предпоследний элемент «книга», который является дочерним элементом элемента «книжный магазин»
/bookstore/book[position()<3] Выбор первых двух элементов «книга», которые являются потомками элемента «книжный магазин»
//title[@lang] Выбирает все элементы «название» (title), которые имеют атрибут с именем “lang”
//title[@lang=’en’] Выбирает все элементы «название», которые имеют атрибут «язык» со значением “en”
/bookstore/book[price>35.00] Выбирает все элементы «книга» после элемента «книжный магазин», которые имеют элемент «цена» со значением больше, чем 35.00
/bookstore/book[price>35.00]/title Выбирает все элементы «название» книги элемента «книжный магазин», которые имеют элемент «цена» со значением больше, чем 35.00

Выбор неизвестных узлов

Специальные символы XPath могут использоваться для выбора неизвестных XML узлов.

Wildcard Описание
* Соответствует любому узлу
@* Соответствует узлу-атрибуту
node() Соответствует любому узлу любого типа

В приведённой ниже таблице мы перечислили некоторые пути выражения и результаты выражений:

Выражение пути Результат
/bookstore/* Выбирает все дочерние узлы элемента «книжный магазин» (bookstore)
//* Выбирает все элементы в документе
//title[@*] Выбирает все элементы «название» (title), которые имеют по крайней мере один атрибут любого вида

Выбор нескольких путей

С помощью оператора | в выражениях XPath вы можете выбрать несколько путей. В таблице ниже перечислены несколько выражений путей и результаты их применения:

Выражение пути Результат
//book/title | //book/price Выбирает все элементы «название» (title) И «цена» (price) всех элементов «книга» (book)
//title | //price Выбирает все элементы «название» (title) И «цена» (price) в документе
/bookstore/book/title | //price Выбирает все элементы «название» элемента «книга» элемента «книжный магазин» И все элементы «цена» в документе

5ОсиXPath

Мы будем использовать следующий XML документ далее в примере.

<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
    <book>
        <title lang="en">Harry Potter</title>
        <price>29.99</price>
    </book>
    <book>
        <title lang="en">Learning XML</title>
        <price>39.95</price>
    </book>
</bookstore>

Оси определяют наборы узлов, относительно текущего узла.

Название оси Результат
ancestor Выбирает всех предков (родителей, прародителей и т.д.) текущего узла
ancestor-or-self Выбирает всех предков (родителей, прародителей и т.д.) текущего узла и сам текущий узел
attribute Выбирает все атрибуты текущего узла
child Выбирает всех потомков текущего узла
descendant Выбирает всех потомков (детей, внуков и т.д.) текущего узла
descendant-or-self Выбирает всех потомков (детей, внуков и т.д.) текущего узла и сам текущий узел
following Выбирает всё в документе после закрытия тэга текущего узла
following-sibling Выбирает все узлы одного уровня после текущего узла
namespace Выбирает все узлы в данном пространстве имён (namespace) текущего узла
parent Выбирает родителя текущего узла
preceding Выбирает все узлы, которые появляются перед текущим узлом в документе, за исключением предков, узлов атрибутов и узлы пространства имён
preceding-sibling Выбирает всех братьев и сестёр до текущего узла
self Выбирает текущий узел

6Выраженияпути выборки

Путь определения местоположения может быть абсолютным или относительным. Абсолютный путь расположения начинается с косой черты (/), а относительный – нет. В обоих случаях путь выборки состоит из одного или нескольких шагов, разделённых косой чертой:

Абсолютный путь расположения:

/step/step/...

Относительный путь выборки расположения:

step/step/...

Каждый шаг оценивается по узлам в текущем наборе узлов. Шаг состоит из:

  • ось (определяет древовидную связь между выбранными узлами и текущим узлом);
  • проверка узла (идентифицирует узел в пределах оси);
  • ноль или более предикатов (для дальнейшего уточнения выбранного набор узлов)

Синтаксис шага выборки такой:

axisname::nodetest[predicate]
имяОси::проверкаУзла[предиктор]
Пример Результат
child::book Выбирает все узлы «книга» (book), которые являются потомками текущего узла
attribute::lang Выбирает атрибут «язык» (lang) текущего узла
child::* Выбирает всех потомков текущего узла
attribute::* Выбирает все атрибуты текущего узла
child::text() Выбирает все текстовые узлы текущего узла
child::node() Выбирает всех ближайших потомков текущего узла
descendant::book Выбирает всех потомков текущего узла
ancestor::book Выбирает всех предков «книга» (books) текущего узла
ancestor-or-self::book Выбирает всех предков «книга» (book) текущего узла – и текущий узел, если он также «книга» (book)
child::*/child::price Выбирает все потомки «цена» (price) через один уровень от текущего узла

7Операторы XPath

Выражения XPath возвращают как набор узлов, строки, булевы или числовые значения. Ниже представлен список операторов, используемых в выражениях XPath:

Оператор Описание Пример
| Вычисляет два набора узлов //book | //cd
+ Сложение 6 + 4
Вычитание 6 – 4
* Умножение 6 * 4
div Деление 8 div 4
= Равенство price=9.80
!= Неравенство price!=9.80
< Меньше, чем price<9.80
<= Меньше или равно price≤9.80
> Больше, чем price>9.80
>= Больше или равно price≤9.80
or Или price=9.80 or price=9.70
and И price>9.00 and price<9.90
mod Остаток от деления 5 mod 2

8Примеры XPath

Давайте рассмотрим базовый синтаксис XPath на нескольких примерах. Мы будем использовать следующий XML документ “books.xml” в примерах ниже:

<?xml version="1.0" encoding="UTF-8"?> 
<bookstore> 
    <book category="COOKING"> 
        <title lang="en">Everyday Italian</title>
        <author>Giada De Laurentiis</author>
        <year>2005</year>
        <price>30.00</price>
    </book>
    <book category="CHILDREN">
        <title lang="en">Harry Potter</title>
        <author>J K. Rowling</author>
        <year>2005</year>
        <price>29.99</price>
    </book>
    <book category="WEB">
        <title lang="en">XQuery Kick Start</title>
        <author>James McGovern</author>
        <author>Per Bothner</author>
        <author>Kurt Cagle</author> 
        <author>James Linn</author>
        <author>Vaidyanathan Nagarajan</author> 
        <year>2003</year>
        <price>49.99</price>
    </book>
    <book category="WEB"> 
         <title lang="en">Learning XML</title> 
         <author>Erik T. Ray</author> 
         <year>2003</year> 
         <price>39.95</price> 
    </book>
</bookstore>

Загрузка XML документа

Используйте XMLHttpRequest для загрузки XML документов, который поддерживается большинством современных браузеров:

var xmlhttp=new XMLHttpRequest()

Код для устаревших браузеров Microsoft (IE 5 и 6):

var xmlhttp=new ActiveXObject("Microsoft.XMLHTTP")

Выбор узлов

К сожалению, работа с XPath в Internet Explorer и в других браузерах может отличаться. В наших примерах мы будем использовать код, который должен работать в большинстве браузеров. Internet Explorer использует метод “selectNodes()” для выбора узлов XML документа:

xmlDoc.selectNodes(xpath);

Firefox, Chrome, Opera и Safari используют метод evaluate() для выбора узлов из XML документа:

xmlDoc.evaluate( xpath, xmlDoc, null, 
                 XPathResult.ANY_TYPE, null );

Выбор всех заглавий

Следующий пример выбирает все узлы заголовков:

/bookstore/book/title

Выбор заголовка первой книги

Следующий пример выбирает заголовок первого узла «книга» после элемента «книжный магазин» (bookstore):

/bookstore/book[1]/title

Выбор всех цен

Следующий пример выбирает текст всех узлов «цена» (price):

/bookstore/book/price[text()]

Выбирает узлы с ценой >35

Следующий пример выбирает все узлы с ценами выше 35:

/bookstore/book[price>35]/price

Выбор узлов заголовков с ценой >35

Следующий пример выбирает все узлы заголовков с ценой выше 35:

/bookstore/book[price>35]/title

Для отладки XPath удобно пользоваться специальными инструментами. Например, в IDE Stylus Studio или в Altova XMLSpy имеется генератор и отладчик XPath. Также есть бесплатные онлайн-инструменты для отладки XPath: XPather или CodeBeautify и другие.

При работе с Selenium если элемент на веб-странице не обнаруживаются общеизвестными локаторами locators, использующими значения атрибутов дерева DOM таких как id, class и name, то для его поиска используют либо CSS селекторы, либо локаторы XPath (XML Path).

Важным отличием локаторов, основанных на синктаксисе XPath от CSS селекторов является то, что используя XPath, мы можем при поиске нужного элемента перемещаться как вглубь иерархии дереву документа, так и возвращаться назад (вверх по дереву). Что касается CSS, то тут мы можем двигаться только в глубину. Это означает, например, что с XPath мы сможем найти родительский элемент по дочернему.

В этом руководстве мы познакомимся с некоторами особенностями языка ХРath применительно к практике использования выражений XPath для поиска сложных или динамически подгружаемых элементов, атрибуты которых также могут динамически изменяться (обновляться).

При рассмотрении примеров, я буду использовать следующий скрипт, который осуществляет поиск элементов на странице поиска Яндекса:

from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

import os
import time
from pprint import pprint

# тестовая страница, на которой мы ищем
target_page = "https://yandex.ru/" 
# то самое выражение XPath, которое мы тестируем
xpath_testing = "//div[contains(@class, 'home-logo')]//child::*"  


dir_current = os.getcwd()
driverLocation = dir_current + "chromedriver.exe"
chrome_options = Options()
chrome_options.add_argument("--headless")

driver = Chrome(driverLocation, chrome_options=chrome_options)
data_text = driver.get(target_page)
time.sleep(3)

try:   
    elements_ = driver.find_elements(By.XPATH, xpath_testing)
    
    for element_ in elements_:
        pprint(f"Выбран элемент с тегом: "{element_.tag_name }"")
        pprint(f"Содержимое атрибута class: "{element_.get_attribute('class')}"")
        pprint(f"Текстовое содержимое элемента: {'Нет содержимого' if not element_.text else element_.text}")   
        
except:
    print('Элемент по заданному XPath выражению не найден :(')
    
finally:
    driver.quit()

Переменной target_page присваивается строковое значение, содержащие адрес страницы, на которой мы будем осуществлять поиск элементов. Критерий поиска будем задавать с использованием XPath выражения, которое также в виде строки присваиваем переменной xpath_testing.

Содержание

  • Коротко о XML и XPath
  • Маршруты поиска
    • Абсолютные пути
    • Относительные пути
    • Подстановочные выражения
  • Предикаты
  • Используем индексы для указания позиции элемента
  • Используем логические операторы OR и AND в выражения XPath
  • Используем функции языка XPath
    • Функция text()
    • Функция contains()
    • Функция starts-with()
    • Функция last()
    • Функция position()
  • Используем полные маршруты поиска элементов
    • Ось предков (ancestor axis)
    • Ось следующих одноуровневых узлов (following-sibling axis)
    • Ось дочерних элементов (child axis)
    • Ось следующих узлов (following axis)
    • Ось предыдущих одноуровневых узлов (preceding-sibling axis)
    • Ось предыдущих узлов (preceding axis)
    • Ось потомков (descendant axis)
    • Ось потомков, включая контекстный узел (descendant-or-self axis)
    • Ось предков, включая контекстный узел (ancestor-or-self axis)

Коротко о XML и XPath

Некоторые разработчики ошибочно полагают, что язык Html является подмножеством XML, но на самом деле это не так, код на обоих языка не возможно комбинировать в одном документе. Так язык XML предназначен для хранения и передачи структурированных данных. В свою очередь HTML предназначен для их более или менее читаемого отображения. Самое существенное различие между HTML и XML в том, что в HTML есть предопределенные элементы и атрибуты, поведение которых так предопределено и ожидаемо, в то время как в XML такого нет. Кроме того существуют определенные различия в синктаксисе инструкций этих внешне схожих языков.

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

  1. HTML и XML документы состоят из элементов, каждый из которых включает «начальный тэг» (<element>), «конечный тэг» (</element>), а также информацию, заключенную между этими двумя тэгами (содержимое элемента).
  2. Элементы могут быть аннотированы атрибутами, содержащими метаданные об элементе и его содержимом.
  3. Любой документ представляет собой дерево, состоящее из узлов (элементов). Некоторые типы узлов могут содержать другие узлы.
  4. Существует единственный корневой узел, который в конечном счете включает в себя все остальные узлы.

Для выбора узлов и наборов узлов дерева документа и последующей обработки Xml использует особый язык XPath. XPath – это отличный от XML язык, используемый для идентификации определенных частей XML документов (элементов страницы). Применительно к html страницам XPath позволяет писать выражения, позволяющие получить, например, ссылку на первый элемент li неупордоченного списка ul, седьмой дочерний элемент третьего элемента div, ссылку а, содержащую строку «Купить по акции» и т. д. XPath позволяет получать ссылки на элементы по их положению на странице (дереве документа), положению относительно другого элемента, тегу, текстовому содержимому и другим критериям.

Согласно методологии XPath существует пять типов узлов, которые могут находиться в дереве документа на обычной html странице:

  1. Корневой узел;
  2. Узлы элементов;
  3. Текстовые узлы;
  4. Узлы атрибутов;
  5. Узлы комментариев.

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

Маршруты поиска

И хотя выражения XPath в Xml могут также возвращать числа, логические и строковые выражения, то есть производить обработку элементов и их содержимого. В Selenium используется лишь подмножество выражений XPath, называемых маршрутами поиска. Маршруты поиска указывают на определенный узел или набор узлов документа (элементов страницы), отвечающих заданным критериям поиска. Каждый маршрут поиска использует как минимум один шаг для идентификации узла или набора узлов документа. Этот набор может быть пустым, содержать один или содержать несколько узлов. Узел может быть корневым, узлом определенного элемента, атрибута, текста или комментария.

Абсолютные пути

Простейшим маршрутом поиска является тот, который указывает на корневой узел документа (страницы). Этот маршрут представляется простой наклонной чертой / и всегда обозначает одно и то же: корневой узел документа. Каждый документ имеет только один корневой узел, являющийся общим корнем дерева узлов. Корневой узел не имеет родительских узлов. Значением корневого узла является значение элемента документа.

С наклонной черты / всегда начинается абсолютный путь к элементу (маршрут поиска). Получить его можно используя либо специальные расширения браузера, либо так как это делается в браузере Chrome. Вызвать окно Chrome DevTools, выделить нужный элемент, кликнув правой клавишей мыши, вызвать контекстное меню, выбрать команду Copy , а затем Copy full XPath.

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

/html/body/div[1]/div[3]/div[3]/div/edvrt/aqwf/ftgr/fdpprt/fgn/dbgrsae/fwap/fwap/ftgr/div/fgn/dhtaq/div/div/div[1]/div[1]/a/div

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

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

Отметим, что корневой элемент страницы имеет абсолютный путь (маршрут) /html и если в качестве XPath выражения мы введем просто ‘/’, то будет возбуждено исключения типа InvalidSelectorException с сообщением «Селектор некоректен. Результат поиска с использованием XPath выражения не возвратил объект элемента. Элемент не найден».

Вывод. Символ / объединяет различные шаги в составной маршрут поиска. Каждый шаг в маршруте является относительным по отношению к предшествующему. Если маршрут начинается с /, то путь является абсолютным, а его первый шаг является относительным по отношению к корневому узлу.

Относительные пути

Относительный путь начинается с двух наклонных черт и следующим за ним одиночным тегом нужного нам элемента. Он может идентифицировать элементы в любом месте веб-страницы. И это позволяет избегать необходимости писать весь длинный абсолютный XPath путь, и вы можете начать его с середины структуры документа страницы (DOM). Он позволяет выбрать все элементы, по заданному тегу на странице, удовлетворяющие указанному критерию поиска. Например, выражение XPath //li ссылается на все элементы li находящиеся на странице (в дереве DOM). Так относительный путь к логотипу Яндекса на странице поиска будет выглядеть следующим образом:

//div[@role = 'img']

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

Логично было бы думать, что при задании относительных путей можно задавать начальную точку поиска нужных элементов страницы или, как принято говорить в терминологии XPath контекстный узел. В этом случае маршрут поиска будет иметь следующий вид:

//ol[@class = 'list news__list']/li/a

Это XPath выражение позволяет получить пути к ссылкам из списка новостей, размещенного на главной странице поисковика Яндекс. Отметим, этот маршрут получился намного короче чем маршрут, использующий абсолютный путь, а также более понятна его логика. Также отметим, что значение атрибута класса элемента упорядоченного списка ol представляет собой строковое значение состоящее из двух имен соответствующих CSS классов. Если вы укажете в выражении наименование только одного из них, например, так //ol[@class = 'news__list'] ,то получите пустой набор элементов.

Попробуем теперь переписать выражение выше следующим образом:

//ol[@class='list news__list']//a

И мы получим точно такой же результат, как и для выражения выше. Фактически мы заменили все промежуточные элементы из пути к ссылкам на две наклонные черты // и упростили его вид. Таким образом можно убирать из пути к искомому элементу любое количество промежуточных шагов (элементов).

Вывод. В начале выражения XPath символы // по сути позволяют выбрать всех потомков корневого узла с указанным тегом. Например, выражение XPath //div выбирает в документе все элементы div. Если мы будем использовать символ // в маршруте для разделения отдельных шагов, то можем опускать промежуточные шаги сокращая при этом запись маршрута. Относительный путь Xpath всегда предпочтительнее, так как он является более логичным и понятным, а также устойчивым к динамическому изменению структуры дерева DOM страницы средствами движка Javascript.

Подстановочные выражения

Подстановочные выражения позволяют выбирать несколько типов элементов одновременно. Существуе два следующих вида подстановочных выражений, которые выможете использовать при парсинге страниц: * , @ *.

Звездочка * или астерикс соответствует любому узлу элемента, независимо от его типа. Звездочка * является одним из наиболее часто используемых подстановочных выражений, используемых в XPath выражениях в Selenium.

Символ @ указывает, что слдующий за ним идентификатор является наименованием атрибута элемента и используется для задания предикатов. Так выражение @ * мы можем использовать вместо любого имени атрибута.

Приведем некоторые примеры их использования.

  • //* – соответствует всем элементам, находящимся на странице (включая тег html).
  • //div/* – соответствует всем элементам, являющимися непосредственными потомками элемента с тегом div .
  • //input[@*] – соответствует всем элементам с тегом input, которые имеют хотя бы один любой атрибут, при этом значение атрибута может быть любым, присутствовать или отсутствовать.
  • //*[@*] – соответствует всем элементам на странице, имеющим хотя бы один атрибут.

Предикаты

Как мы уже знаем, что в общем случае выражение XPath может ссылаться более чем на один узел (элемент страницы), то есть метод, в котором оно используется будет возвравращать массив элементов. Иногда это именно то, что нам нужно, однако в некоторых случаях приходится «просеивать» его по определенным критериям, чтобы выбрать только некоторые из них. Для этих целей в XPath используется синктаксис предикатов. Каждый шаг в маршруте поиска может иметь свой предикат или даже несколько, который задает свой критерий выбора из текущего списка узлов на каждом шаге маршрута поиска. То есть на каждом шаге поиска могут существовать один или более предикатов. По сути предикат содержит логическое выражение, которое проверяется для каждого узла в полученном по указанному пути наборе элементов страницы. Если выражение ложно, этот узел удаляется из набора, в противном случае соответствено сохраняется.

Предикат – это часть выражения XPath, заключенная в квадратные скобки, которое следует в инструкции для шага поиска за критерием выбора узла (элемента). В общем виде выражение с предикатом будет выглядеть следующим образом:

//выбор_элементов[правило_предиката1][правило_предиката2][правило_предиката3]  

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

//div[@class='search2__button']/button[@role='button'] 

В начале на первом шаге выбираем все элементы div, для которых справедливо следующее логическое значение предиката: значение атрибута класса соответствует строке search2__button. На втором шаге выбираем у них элементы с тегом button, являющиеся их непосредственными потомками, у которых значение атрибута role содержит строковое значение button.

В следующем примере выбираем ссылку на корзину Яндекс Маркет, которая находится также на основной странице поисковика. Использование нескольких атрибутов в выражении XPath сужает поиск нужного элемента на странице до одного.

//а[@title='Корзина на Маркете'][@class='home-link market-cart']

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

Используем индексы для указания позиции элемента

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

//ol[@class='list news__list']/li[2]
//ol[@class='list news__list']/li[2]//span[@class='news__item-content']

Второе выражение XPath позволянт получить у выбранного элемента списка элемент с тегом span, который содержит текстовое содержимое заголовока новости. Отметим, что индексы элементов начинают отсчитываться от 1, а не от 0, как принято для индексации массивов в языке Python.

Используем логические операторы OR и AND в выражения XPath

Логические оператры используются в инструкциях предикатов для комбинирования критериев поиска нужных элементов на странице.

В примере ниже приведены выражения для фильтрации ссылок на новости, которые показывают на странице поиска Яндекс.

//a[@rel='noopener' or @target='_blank']
//a[@rel='noopener' and @target='_blank']
//a[@rel='noopener' and @target='_blank' and contains(@class, 'home-link_black_yes')]

Как видно в предикате для фильтрации элементов можно применять сколько угодно логических операторов, а также комбинировать их с XPath функциями, которые рассмотрим ниже.

Функция text()

Функция XPath text() – это встроенная в синтаксис XPath Selenium функция, которая используется для поиска элементов на основе строкового значения, содержащегося в его текстовом узле. То есть если элемент имеет текстовое содержимое в виде строки, то элемент можно найти следующим образом:

<span class="button__text">Найти</span>
​
//span[text()='Найти']

Функция contains()

Функция contains() часто используется в предикатах выражений XPath для случаев если значение атрибута элемента или его текстовое содержимое может динамически изменяться.

Например в значение атрибута класса элемента //element[@class='class1 class2'] средствами Javascript может быть добавлен для его анимации класс class3, а потом также динамически убран. При этом значение предиката в случае добавления нового класса станет ложным, то есть элемент не будет выбран. Для этого случая мы можем использовать функцию contains() следующим образом:

//element[contains(@class, 'class1 class2')]

Если выражение выбора элемента переписать в указанном выше виде, то мы ориентируясь на атрибут класса элемента будем выбирать его в любом случае.

Функция contains() позволяет находить элемент по текстовой подстроке значения атрибута или его текстового содержимого, как показано в примере XPath ниже.

//a[contains(@title, 'Корзина')]
//span[contains(text(),'Найти')]

В примере мы нашли ссылку на корзину Яндекс маркета из примера выше по части значения атрибута title. А также по части текстового содержимого кнопку отправки запроса поисковику.

Функция starts-with()

Эта функция используется если нам известна первая часть (начальная подстрока) текстового содержимого искомого элемента на странице, либо часть значения его атрибута.

//a[starts-with(@title, 'Корзина')]
//span[starts-with(text(),'Найти')]

Функция last()

Эта функция позволяет выбрать последний элемент (указанного типа) из набора элементов. Пример ее использования представлен ниже.

//ol[@class='list news__list']/li[last()]//span[@class='news__item-content']

Это выражение возвращает элемент, содержащий наименование последней новости из списка новостей со страницы поисковика Яндекс.

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

//ol[@class='list news__list']/li[last()-1]//span[@class='news__item-content']

Функция position()

Эта функция позволяет выбирать из полученного набора элементы в зависимости от указанного номера позиции. Начало отсчета позиции элемента, по аналогии с индексами также начинается с 1. Действие этой функции полностью идентично индексам, о которых мы говорили выше. В примере ниже представлены два эквивалентных по результату выполнения выражения.

//ol[@class='list news__list']/li[position()=1]"))
//ol[@class='list news__list']/li[1]

Используем полные маршруты поиска элементов

До этого момента мы говорили о том, что в терминологии языка XPath называется сокращенными маршрутами поиска. Эти маршруты значительно проще для набора, менее многословны и знакомы большинству разработчиков. Кроме того, они являются именно теми выражениями XPath, которые лучше всего подходят для использования в простейших шаблонов поиска. Однако в XPath также предлагается полный синтаксис для маршрутов поиска, который более многословен, но, возможно, менее загадочен и определенно более гибок.

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

По сути в сокращенном маршруте поиска ось и критерий узла объединены вместе. Например, следующий сокращенный маршрут поиска состоит из трех шагов.

//ol/li/a[@rel='noopener']

Первый шаг выбирает на странице элементы упорядоченных списков ol по оси дочерних узлов, второй – элементы li вдоль оси дочерних узлов, третий шаг – так же по оси дочерних узлов выбирает элементы ссылок a, а затем с помощью предиката выбирает из них только содержащие атрибут rel='noopener' с заданным значением . Если переписать это выражение в полной форме тот же маршрут поиска будет выглядеть следующим образом:

//child::ol/child::li/child::a[@rel='noopener']

Полные несокращенные маршруты поиска, как и сокращенные, могут быть также абсолютными, если начинаются с корневого узла.

В целом полная форма очень многословна и мало используется на практике. Однако она предоставляет одну исключительную возможность, которая делает эту форму записи XPAth выражений достойной внимания. Это единственный способ использования направлений осей поиска, по которым выражения XPath осуществляют выбор нужных элементов.

Так сокращенный синтаксис позволяет перемещаться по оси непосредственно дочерних узлов (child), оси атрибутов (attribute) и оси всех его потомков с включением контекстного узла (descendant-or-self). Полный синтаксис добавляет еще восемь осей, которые применимы для использования в XPath выражениях и поиска элементов на страницах с использованием Selenium:

Ось предков (ancestor axis)

Все узлы элементов, содержащие контекстный узел; родительский узел, родитель родителя, родитель родителя родителя и т.д. вверх вплоть до корневого узла в порядке, обратном расположению узлов в документе.

//div[text()='Маркет']//ancestor::a
//div[text()='Маркет']//ancestor::*

В данном примере мы получаем ссылку на Яндекс Маркет по текстовому содержимому элемента div находящегося внутри нее. А следующее выражение позволяет выбрать последовательность всех предков этого элемента до корня документа /html.

Ось следующих одноуровневых узлов (following-sibling axis)

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

//div[contains(@class, 'home-logo')]//following-sibling::div

В примере выше выражение выбирает блок div по содержимому атрибута класса, который содержит элементы строки ввода слов для поиска.

Ось дочерних элементов (child axis)

Ось содержит все дочерние узлы текущего контекстного, то есть выбирает все элементы, содержащиеся в текущем узле. В примере ниже будут выбраны все элементы находящиеся внутри блока div содержащего логотип Яндекса.

//div[contains(@class, 'home-arrow__search-wrapper')]//child::*

Ось следующих узлов (following axis)

Все узлы, следующие после контекстного узла, в том же порядке, в каком узлы присутствуют в документе. Отличием поиска вдоль этой оси от оси following-sibling является то, что будут выбраны все узлы (элементы) находящиеся в документе за закрывающим тегом контекстного узла. Так в аналогичном примере ниже будут выбраны все элементы div, следующие в документе за разметкой логотипа Яндекс. Сравните результаты поиска с примером выше.

//div[contains(@class, 'home-logo')]//following::div

Ось предыдущих одноуровневых узлов (preceding-sibling axis)

Выбирает все узлы, предшествующие контекстному узлу и содержащиеся в том же узле родительского элемента последовательно в обратном порядке.

//div[contains(@class, 'search2__input')]//preceding-sibling::input[@type='hidden']

В этом примере выражение позволяет выбрать скрытые поля в блоке со строкой основного поиска Яндекса.

Ось предыдущих узлов (preceding axis)

Все узлы, предшествующие началу контекстного узла, в порядке, обратном порядку в документе. Отличием поиска вдоль этой оси от оси following-sibling является то, что будут выбраны все узлы (элементы) находящиеся в документе перед открывающим тегом контекстного узла. Так в аналогичном примере ниже будут выбраны все элементы div, следующие в документе перед разметкой логотипа Яндекс. Сравните результаты поиска с примерами выше.

//div[contains(@class, 'home-logo')]//preceding::div

Ось потомков (descendant axis)

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

//div[contains(@class, 'home-arrow__search-wrapper')]//descendant::*

Ось потомков, включая контекстный узел (descendant-or-self axis)

Ее действие аналогично оси потомков descendant за исключением того, в набор будет включен и сам контекстный узел.

//div[contains(@class, 'home-arrow__search-wrapper')]//descendant-or-self ::*

Ось предков, включая контекстный узел (ancestor-or-self axis)

Все предки контекстного узла, включая сам контекстный узел. В примере ниже будут выбраны все предки элемента div блока с логотипом Яндекса, а также сам элемент.

//div[contains(@class, 'home-arrow__search-wrapper')]//ancestor-or-self::*

В этой статье мы рассмотрели основы использования синтаксиса XPath при составлении выражений для поиска элементов на странице. Отличительной особенностью такого поиска является то, что используется информация о структуре документа страницы, что позволяет более гибко составлять выражения маршрутов к искомым элементам в любом направлении от заданного контекстного узла. В отличии от использования CSS селекторов, которые позволяю осуществлять поиск только в глубину, выражения XPath позволяют выбирать как родительские узлы так и узлы предков выше до любого уровня вложенности.

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

Надеюсь, что это руководство поможет вам разобраться с принципом использования XPath выражений при работе в Selenium Python. А также в дальнейшем послужит справочным пособием для разработки.

xPath это такой язык запросов, который позволяет среди множества элементов веб-страницы найти нужный, — и обратиться к нему, чтобы достать необходимые данные:

  • Заголовок и описание.
  • Названия статей с количеством просмотров.
  • Список ссылок.
  • Цены на товары.
  • Изображения и т. п.

xPath поддерживают платные инструменты для парсинга (например, Screaming Frog Seo Spider), его выражения можно использовать в программировании на JavaScript, PHP и Python, и даже сделать простой бесплатный парсер прямо в Google Таблицах. Разбираемся, как именно — на трех практических примерах.

Когда начинаешь изучать большинство видео/статей по теме, начинает взрываться мозг — кажется, что все это очень сложно и подвластно только крутым технарям/хакерам. На самом деле все 200 встроенных функций xPath (как сообщает туториал W3C) знать совсем не обязательно, и на практике освоить язык получается гораздо проще. Процесс напоминает привычное ориентирование в папках и файлах в компьютере, а сами выражения xPath — адреса вроде «C:Program Files (x86)R-Studio».

1. Сбор и проверка заголовков и метатегов

Работа с заголовками (h1) и метатегами (title и description, реже keywords) — одна из составляющих поисковой оптимизации сайта. SEO-специалист (маркетолог, предприниматель) может проверять эти текстовые фрагменты на наличие, по длине, вхождениям определенных запросов. Если нужна массовая проверка, лучше воспользоваться специальным парсером (например, от Promopult или Click.ru), но небольшую задачу можно легко решить прямо в Google Spreadsheets.

Подготовка таблицы и разбор синтаксиса IMPORTXML

Начать можно с дизайна самой таблицы. Допустим, в первой колонке (A) будут ссылки на страницы, а правее уже результаты, извлеченные данные: H1, тайтл, дескрипшн, ключевые слова. 

Тогда стоит первую строку отдать под заголовки (если планируются десятки ссылок, не помешает «Вид → Закрепить → 1 строку»), в A2 указать URL (можно пока любой — для проверки работоспособности) и приступить к написанию первой функции. (А так как текстовые фрагменты довольно длинные, можно заодно выделить все ячейки, нажать «Формат → Перенос текста → Переносить по словам».)

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыНачало работы с парсер-таблицей. В качестве примера разберем заголовки и метатеги главной страницы Webartex — это такая платформа для работы с блогерами и сайтами.

Для импорта данных с сайтов (в форматах HTML, XML, CSV) в Google Таблицах есть функция IMPORTXML. Она принимает такие аргументы:

  1. Полный адрес веб-страницы с указанием протокола (например, “https://”). Можно передать сам URL в кавычках или адрес ячейки, где он лежит.
  2. Непосредственно запрос xPath — тоже в кавычках, так как это тоже текстовая строка.
  3. locale — локальный код для указания языка и региона, необязательный параметр, по умолчанию используются настройки самого документа.

Читайте также: 20+ продвинутых функций Google Таблиц (Spreadsheets)

Составление функций для импорта XML с разными запросами xPath

Для парсинга H1 получится довольно просто: =IMPORTXML(A2;”//h1″).

“//” это оператор для выбора так называемого корневого узла — откуда нужно будет сразу взять данные или же «плясать» дальше (к дочернему элементу, соседнему или др.). В данном случае не нужно прописывать длинный путь, указывать дополнительные параметры — тег <h1> такой один единственный (как правило, но может быть и несколько заголовков первого уровня, тогда запрос “//h1” выгрузит их в несколько строк).

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыВот что вернула функция IMPORTXML с «https://webartex.ru» по запросу “//h1”

Правда, есть нюанс — часть заголовка первого уровня оказывается в ячейке D2, а там нужны совсем другие данные. Все из-за тега <br>, который внутри <h1> используется для перевода строки. Решение — функция самого xPath “normalize-space()“, в которую нужно упаковать текст из H1. Дополненная функция получается такой: =IMPORTXML(A2;”normalize-space(//h1)”)

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыxPath-локатор работает корректно, можно идти дальше

В ячейке C2 — по тому же принципу, только выражение xPath, соответственно, будет “//title”

А вот для загрузки дескрипшна в соседнюю ячейку D2 нельзя указать просто “//description”, потому что такого отдельного тега нет. Эти данные лежат в теге <meta>, у которого есть дополнительный параметр (атрибут) — “name” со значением “description“.

Если в запросе xPath нужно указать не просто элементы веб-страницы, а элементы с конкретным атрибутом, то соответствующие условия указываются в квадратных скобках. Название атрибута пишется с собакой “@”, а его значение передается через одинарные кавычки. Если нужно проверить эквивалентность, то условие записывается просто как «атрибут = значение».

То есть для решения этой задачи нужно указать элемент так: “//meta[@name=’description’]”.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыШпаргалка: из чего состоят HTML-элементы, из которых уже состоят веб-страницы (иллюстрация из курса Hexlet по основам HTML, CSS и веб-дизайна).

Однако если оставить такое выражение, то функция IMPORTXML вернет значение #N/A — значит, нет данных для импорта. Хотя путь к элементу указан верно. Дело в том, что внутри этого тега <meta> нет ничего — результат соответствующий. 

Это хорошо видно, если открыть исходный код страницы (например, через сочетание клавиш Ctrl + U в Google Chrome). У <meta> нет закрывающего тега </meta>, как это бывает у многих других, получается, нет и внутреннего содержания. Нужные данные лежат в другом атрибуте — @content.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыИсходный код страницы Webartex, на которых хорошо видно устройство тегов <meta>

Решение — дополнить запрос xPath, через “/” указав путь к конкретному атрибуту выбранного элемента. В данном случае вся формула будет такой: =IMPORTXML(A2;”//meta[@name=’description’]/@content”)

Если нужно указать не корневой элемент (узел), а его параметр или вложенный тег, тогда уже используется одинарный слеш, а не двойной. По аналогии с URL страниц сайтов или адресами файлов и папок операционной системы.

По такому же принципу составляется запрос для метатега с ключевыми словами — “//meta[@name=’keywords’]/@content”. Если все ок, то, значит, можно протягивать формулы ниже, а в столбец URL добавлять новые адреса.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыРезультаты после запуска всех функций. Все формулы написаны верно, данные собираются корректно, все работает нормально.

Если нужно, аналогичным образом можно извлекать и другие данные: подзаголовки H2—H6, метатеги для разметки OpenGraph и Viewport, robots и др.

Читайте также: Микроразметка на сайте: что это, для чего нужно и как внедрить

Бонус: оценка полученных метатегов и заголовков

Допустим, нужно проверить, находится ли длина title и description в пределах нормы. Для этого можно воспользоваться функцией гугл-таблиц ДЛСТР (LEN). Она работает довольно просто: на входе текстовая строка, на выходе — число символов.

Согласно рекомендациям из блога Promopult, отображаемая длина тайтла в Google — до 50-55, а в Яндексе — до 45-55. Поэтому желательно не писать его слишком длинным, по крайней мере в первых 45–55 символах должна быть законченная мысль, самое главное о странице.

Чтобы не создавать дополнительных ячеек с цифрами по количеству символов, можно прописать формулу LEN в условном форматировании. Выделить третий столбец C, кликнуть в меню на «Формат → Условное форматирование», выбрать в списке «Правила форматирования» вариант «Ваша формула». И туда уже прописать, допустим, =LEN($C$2:$C)>55. А цвет, например, желтый, который как бы будет сигнализировать: «Тут надо посмотреть!».

В данном примере строка C2 пожелтеет, так как длина title составляет 59 знаков, а не 55. Но в принципе вся ключевая мысль, призыв к действию, умещается в лимит, так что все нормально.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыНастройка условного форматирования Google Таблиц для подсвечивания тайтлов, длина которых больше рекомендуемой

По такому же алгоритму можно сделать оценку description. В вышеупомянутой статье blog.promopult.ru сказано: лучше, чтобы вся важная информация метаописания умещалась в 100-120 символов. 

А еще там есть рекомендация не указывать в метатеге keywords больше 10 ключевых слов. Но чтобы проверить это, нужен не подсчет длины, а количества самих слов, разделенных запятыми. 

В гугл-таблицах нет специальной функции, которая считает количество вхождений определенных символов в текстовую строку, но эту задачу можно решить через условное форматирование с помощью такой формулы: =COUNTA(SPLIT($E$2:$E;”,”))>10. Небольшой ликбез:

  • SPLIT — разделяет текст по определенным символам и выводит в разные ячейки. Два обязательных параметра: 1) собственно, текст, который нужно разделить, или ссылку на ячейку с таковым 2) один или несколько символов в кавычках, по которым как раз и нужно разделять текст.
  • СЧЁТЗ (COUNTA) подсчитывает количество значений в наборе данных: принимает неограниченное число аргументов (значений и диапазонов). В данном случае забирает на вход результаты SPLIT, выдающей массив текстовых значений, и подсчитывает их общее число.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыА вот так работают эти функции отдельно (конечно, все результаты SPLIT не поместились, функция располагает их в строке, поэтому они уходят далеко вправо).

Получилось, что количество keywords на странице webartex.ru составляет 14, а не 10 штук, значит, их лучше подсократить. Яндекс может использовать этот метатег при ранжировании страницы, но большое количество ключевых слов может, наоборот, привести к пессимизации, исключению из индекса.

«Поисковое продвижение» — бесплатный видеокурс по SEO в обучающем центре CyberMarketing. В программе структура поисковой выдачи, санкции поисковых систем, инструменты для сбора семантического ядра и другие важные темы. Преподаватель — Евгений Костин, руководитель департамента продаж системы Promopult.

2. Парсинг ссылок из топ-10 поисковика

Допустим, нужно регулярно мониторить топ Яндекса по определенному запросу, чтобы узнать, попал ли туда конкретный сайт и на какую позицию. Можно с помощью xPath извлечь все ссылки с органической выдачи, а благодаря текстовым функциям Google Таблиц уже искать совпадения с названием нужного сайта.

Поиск и анализ нужных элементов через DevTools

В качестве примера — запрос «отложенный постинг». Для начала нужно в браузере Chrome перейти на соответствующую страницу, кликнуть правой кнопкой на один из элементов, который нужно будет извлечь (пусть это будет ссылка ниже заголовка), и нажать на «Просмотреть код» (горячие клавиши — Ctrl + Shift +I). Тогда откроются «Инструменты разработчика» (Chrome DevTools) с кодом этого элемента.

В коде документа сразу можно заметить древовидную структуру. На самом верху — корневой тег <html>, внутри на одном уровне <head> и <body>, затем <body> раскрывается на десятки <div> и <script>, а в некоторых <div> еще другие <div> с <ul>, <li>, <h2> и т. п. Написание xPath-запроса напоминает квест: нужно правильно описать искомый элемент и путь к нему.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыТак выглядит просмотр кода нужного элемента в Chrome DevTools. (И было бы удобно кликнуть еще раз правой кнопкой, потом выбрать Copy и Copy XPath, затем вставить этот код в соответствующую функцию Таблиц, но, увы, как правило, так не работает. Приходится разбираться.)

Напоминаем: страница состоит из элементов, а каждый элемент включает тег и содержание (что между открывающим и закрывающим тегом), а еще в открывающем теге может быть дополнительная информация: атрибуты и их значения. В данном случае необходимые данные — ссылка на страницу, которая попала в топ Яндекса — находятся в значении атрибута “href” тега <a>, у которого еще есть атрибут “class” со значением “Link Link_theme_outer Path-Item link path__item i-bem link_js_inited

(А этот тег <a> находится внутри тега <div> с атрибутом “class” и значением “Path Organic-Path path organic__path”… но весь путь писать нет смысла, если сам <a> достаточно уникальный и правильно находится.)

Фрагмент кода (на скриншоте он не помещается целиком):

<div class="Path Organic-Path path organic__path"><a class="Link Link_theme_outer Path-Item link path__item i-bem link_js_inited" tabindex="0" data-counter="["b"]" data-log-node="ip71w0i-02" href="https://blog.cybermarketing.ru/7-servisov-otlozhennogo-postinga-v-socialnye-seti-i-messendzhery/" target="_blank"><b>blog.cybermarketing.ru</b><span class="Path-Separator" aria-hidden="true" aria-label=" ">›</span>7-servisov…postinga-v…seti…</a></div>

Но прежде чем писать запрос xPath, стоит проверить — действительно ли все нужные элементы имеют соответствующие атрибуты и значения. “href”, понятно, будет везде разный, а вот что насчет “class” со значением “Link Link_theme_outer Path-Item link path__item i-bem link_js_inited”?

Для этого в окне «Инструменты разработчика» нужно нажать «Ctrl + F» и внизу появится поле «Find by string, selector, or xPath». Если вставить эту большую и страшную строку, видно, что подсвечивается с десяток элементов.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыВ процессе поиска нужного значения в коде через Chrome DevTools. Вроде все хорошо, и подсвечиваются нужные элементы с необходимыми ссылками…

Ссылка из блока быстрых ответов не попадает — отлично, иначе она бы дублировалась. Но есть нюанс — и органическая, и платная выдача имеет такое же значение атрибута “class” тега <a>. Но их можно развести через дополнительное условие (все рекламные ссылки начинаются с “http://yabs.yandex.ru/”).

Читайте также: Чем отличается контекстная реклама от таргетированной

Написание xPath-локатора с учетом изученных элементов и их параметров

Вспоминаем: “//” — это оператор, который выбирает так называемый корневой узел — элемент для непосредственного извлечения данных или тот, от которого нужно будет дальше «плясать». Значит, нужно начать с «//a». Но если оставить так, то загрузятся все <a> со страницы, а для решения задачи нужны конкретные. То есть нужно указать, что нужен элемент <a> с атрибутом @class, у которого есть конкретное значение.

Делаем, как это уже было с метатегом дескрипшн из предыдущего раздела: атрибут с собакой, значение в одинарных кавычках, все условие в квадратных скобках → //a[@class=’Link Link_theme_outer Path-Item link path__item i-bem link_js_inited’] Можно проверить работоспособность запроса сразу же в «Инструментах разработчика» — в поле «Find by string, selector, or xPath». Вроде все работает.

Если перенести в Google Таблицы, формула получится такой: =IMPORTXML(“https://yandex.ru/search/?lr=45&text=отложенный постинг&p=0″;”//a[@class=’Link Link_theme_outer Path-Item link path__item i-bem link_js_inited’]”) Но результат — #N/A!, нет данных для импорта.

Ах, да — как и в случае с description и keywords, искомые данные лежат в другом атрибуте. То есть нужно продолжить путь с помощью “/@href”. Но функция снова не может импортировать данные.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыВроде все написано правильно, но импорт данных не работает…

На самом деле в атрибуте это не один такой класс с длинным названием, а несколько, которые разделены пробелами. Возможно, поэтому IMPORTXML не может найти данные по условию [@class=’]. Решение — искать не полное совпадение, а часть значения атрибута с помощью функции contains.

Если взять начало “Link Link_theme_outer Path-Item”, то поиск по документу в DevTools выдает те же элементы, ничего лишнего не подмешивается. Значит, можно написать запрос таким образом: “//a[contains(@class,’Link Link_theme_outer Path-Item’)]”

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыВставили в IMPORTXML такой запрос xPath — все заработало.

Функция contains через запятую принимает два аргумента: название параметра, где нужно искать вхождение, и текстовую строку, которую нужно искать. В данном случае нужно указать @class, но можно любой другой атрибут (или text(), если требуется найти вхождения во внутреннее содержание элемента). Альтернативой может стать другая функция starts-with — она ищет не в любом месте, а в начале строки. В данном случае результат такой же при “//a[starts-with(@class,’Link Link_theme_outer Path-Item’)]/@href”.

Осталось только исключить из списка ссылки из контекстной рекламы, ведь нужна только органическая выдача. Для этого требуется указать два условия: чтобы взять все @href в теге <a> с классом, содержащим “Link Link_theme_outer Path-Item”, но в то же время, чтобы в этих @href не было ссылок, где URL включает “yabs.yandex.ru”. Решение — дополнить запрос xPath таким образом: “//a[contains(@class,’Link Link_theme_outer Path-Item’) and not(contains(@href,’yabs.yandex.ru’))]/@href”

Что здесь нового: логический оператор “and” указывает, что должны быть выполнены оба условия, а функция not() выполняет другую логическую операцию — отрицание. contains() внутри нее возвращает TRUE, когда находит в ссылке “yabs.yandex.ru”, но в списке таковые как раз не нужны, поэтому TRUE надо превратить в FALSE. А логическое «И» работает только, когда оба условия — TRUE. Поэтому на выходе желаемый результат.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыВыражение работает корректно: в списке URL’s органической выдачи, без рекламных ссылок и колдунщиков

Кстати, вместо <a> с классом, включающим “Link Link_theme_outer Path-Item”, можно взять другую ссылку — с заголовков страниц. То есть составить запрос так: “//a[contains(@class, ‘OrganicTitle-Link’) and not (contains(@href, ‘yabs.yandex.ru’))]/@href” (ну и, конечно, вместо второй функции contains можно взять start-with, в данном случае все рекламные ссылки будут начинаться одинаково, с “http://yabs.yandex.ru”).

А если захочется парсить не первую страницу, а, допустим, вторую, то достаточно в URL — первом аргументе функции IMPORTXML — увеличить значение параметра &p (в конце ссылки) с нуля до единицы. То есть изменить адрес на “https://yandex.ru/search/?lr=45&text=отложенный постинг&p=1”.

Читайте также: Исчерпывающий гид по поисковым операторам Google и Яндекса

3. Выгрузка статистики по популярным статьям в блоге

Допустим, автору (редактору, маркетологу или блогеру) хочется следить за популярными материалами в других медиа, чтобы черпать идеи по новым темам уже для своего ресурса. Можно делать это вручную — заходить на каждый сайт, скроллить, тратить время на поиск соответствующего блока — или собирать такие данные в таблицу. Рассмотрим, как это можно делать, на примере сайта Yagla (не самый посещаемый тематический ресурс, но интересный вариант с точки зрения освоения языка xPath).

Изучение сайта и подходящих элементов

На сайте yagla.ru много разных блоков, но для этих целей больше подходят два: «Обсуждаемое» (в самом верху) и «Самые читаемые статьи за последние 3 недели» чуть пониже. Информации по просмотрам нет, но есть количество комментариев (чтобы узнать просмотры, нужно открывать конкретную страницу, но, если нужно, можно с помощью дополнительных IMPORTXML загружать данные и по каждой из них).

Для начала: кликнуть правой кнопкой мыши на один из нужных заголовков в вышеперечисленных блоках, выбрать «Просмотреть код». Chrome DevTools подсвечивают тег <p> с атрибутом @class равным “small-post__title”. Но если ввести это значение в поле «Find by string, selector, or xPath» видно, что оно есть и у материалов другого блока «Примеры роста конверсий, заказов и прибыли», который не нужно импортировать.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыНачинаем изучать элементы главной страницы сайта в «Инструменты разработчика» Google Chrome

Родительский элемент, в который вложен вышеупомянутый <p>, — это <a> с классом ‘small-post‘. Но он еще более неуникальный, на странице таких 40 штук. Соседний (на одном и том же уровне) с <a> элемент — <h2> с классом “small-list__title title title_size_middle” тоже найден на странице в количестве четырех штук.

Но ведь можно прописать путь к элементу не только по значению атрибута, но и его содержанию, тексту.

Читайте также: Чек-лист: как проверить верстку

Составление запроса xPath

Обратиться к элементу можно и так — “//*[text()=’Обсуждаемое ‘]”, чтобы взять только тот, где текст полностью соответствует строке ‘Обсуждаемое’. Сам тег в таком случае тоже прописывать необязательно.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыПроверка первой части запроса xPath в DevTools показывает, что все ищется верно

Но при написании дальнейшего пути не получится как обычно продолжить с одинарным слешем, ведь нужен не потомок этого элемента, а элемент того же уровня — «сосед» («брат», «сестра»). В таких случаях нужен специальный оператор — ‘following-sibling::’. В итоге выражение xPath получится таким: “//*[text()=’Обсуждаемое ‘]/following-sibling::a/p”. (Дополнительно указывать классы для <a> и <p> нет необходимости, так как других похожих вариантов путей нет.)

Таким же способом можно составить выражение для загрузки данных из другого блока: “//*[text()=’Самые читаемые статьи за последние 3 недели ‘]/following-sibling::a/p”

Базовая настройка и оформление таблицы

Как вариант. В ячейку A1 положить заголовок «Обсуждаемое», а ниже — в A2 — уже написать функцию: =IMPORTXML(“https://yagla.ru/”;”//*[text()=’Обсуждаемое ‘]/following-sibling::a/p”). Затем оставить необходимое пространство (если ячейки будут заняты, функция не сможет отобразить результаты), A5 отдать под следующий заголовок, а в A6 — вторую формулу: =IMPORTXML(“https://yagla.ru/”;”//*[text()=’Самые читаемые статьи за последние 3 недели ‘]/following-sibling::a/p”)

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыТакая вот таблица с популярными материалами получается в итоге

Внутри искомого <p> есть еще <span> с указанием формата, поэтому IMPORTXML требуется дополнительный столбец справа. (Так как эта информация излишняя, можно просто выделить все ячейки B, кликнуть правой кнопкой и выбрать «Скрыть столбец».)

‘following-sibling::’ — это одна из осей, основы запросов языка xPath. Есть и другие, например, child::’ — возвращает множество потомков на один уровень ниже; ‘attribute::’ — выдает, соответственно, атрибуты; ‘parent::’ — ищет родительский узел. И с частью этих осей мы уже знакомы, просто для наиболее распространенных действуют сокращения. Так, child:: вообще прописывать необязательно, а attrubute:: заменяется на ‘@’.

Бонус: прокачка мини-парсера в Google Spreadsheets

Допустим, названия статей мало, нужны еще и просмотры, которых нет на главной странице. Тогда придется немного усовершенствовать гугл-таблицу. Разберем на примере блока «Обсуждаемое» — с другим все будет так же.

Для начала нужно выгрузить URL’s материалов. Как обычно ссылки лежат в атрибутах @href тега <a>, так что достаточно просто поменять концовку выражения xPath: “//*[text()=’Обсуждаемое ‘]/following-sibling::a/@href”.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыВсе работает — в таблице появились ссылки на статьи.

Правда, ссылки не полные, а относительные — нужно превратить их в URL’s с названием домена. Решить задачу можно с помощью текстовой функции гугл-таблиц — СЦЕПИТЬ (CONCATENATE). Она работает просто: принимает на вход несколько строк, а возвращает объединенный текст.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыВ отдельном столбце можно дополнить выгруженные относительные ссылки до полных путей

Дальше уже к каждой странице сделать отдельные запросы xPath, чтобы извлечь данные со счетчика просмотров. Если посмотреть через DevTools, таковые находятся в теге <div> c атрибутом @class равным ‘post__prop‘. Однако элемент есть и наверху, и внизу, а в таблице нужен один. В такой ситуации в квадратных скобках указывается индекс, порядковый номер (если говорить терминами xPath — предикат).

Судя по шпаргалкам и справочникам, кажется, что нужно просто написать “//div[@class=’post__prop’][1]”, но в таблице все равно оказываются два значения — да еще и с лишними пустыми ячейками.

Пока что получился такой некрасивый результатПока что получился такой некрасивый результат

Однако эксперты Stackoverflow разъясняют, что такой синтаксис работает только для последовательности узлов, а если нужен корневой элемент, то понадобятся дополнительные скобки: “(//div[@class=’post__prop’])[1]”

А лишние ячейки появляются из-за того, что внутри этого div есть еще теги. Чтобы почистить данные, понадобится применить функцию text(). Итоговая формула в гугл-таблицах получается такой: =IMPORTXML(D2;”(//div[@class=’post__prop’])[1]/text()”)

Остается только протянуть ее ниже — для всех строк с выгруженными URL статей.

Практикум по xPath: простой, быстрый и бесплатный способ парсить сайтыДоработанная таблица с выгрузкой просмотров

Читайте также: Где вести блог, если нет своего сайта: 10 платформ для личного и корпоративного блогинга

Подытожим

xPath в гугл-таблицах — мощная штука, однако подходит только для решения относительно простых задач.

Так, при наличии большого количества формул типа IMPORTHTML, IMPORTDATA, IMPORTFEED и IMPORTXML результаты могут грузиться очень долго — а польза парсинга как раз в том, что можно быстро добывать свежие данные. К тому же, например, статистику Яндекс.Вордстат не получится извлечь через xPath — для работы нужна авторизация, да и даже при ручном сборе сервис может замучать капчей. 

Поэтому для более серьезных задач по продвижению/оптимизации нужны профессиональные инструменты, например, Promopult. Там большой выбор решений для SEO- и PPC-специалистов: парсинг Wordstat и метатегов, сбор поисковых подсказок и кластеризация запросов, поиск и генерация объявлений и др. Один запрос стоит от 0.01 руб.

Добавить комментарий