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

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

Два языка с первого взгляда

CSS-селекторы: это паттерны, которые вы уже пишете в таблицах стилей: .price, #header, div > span. Браузеры вычисляют их непрерывно, все библиотеки для скрапинга поддерживают их, и они легко читаются в распространённых случаях. Это кратчайший путь к «взять этот элемент», когда на странице есть разумные классы и идентификаторы.

XPath («XML Path Language»): полноценный язык запросов для навигации по дереву документа. Он представляет страницу как узлы, по которым можно перемещаться в любом направлении: вниз к дочерним, вверх к предкам, в стороны к соседям. Он умеет сопоставлять по текстовому содержимому, фильтровать с булевыми условиями и комбинировать предикаты способами, недоступными CSS. Эта мощь обходится некоторой многословностью, но окупается на запутанных или глубоко вложенных страницах.

Оба языка работают с одним и тем же DOM. Различие в охвате и эргономике: CSS лаконичен и знаком, XPath точен и выразителен. Знать, где каждый из них заканчивается,: это вся суть.

CSS Selectors, поле за полем

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

Тег, класс и идентификатор. Три наиболее распространённые отправные точки. Голое имя тега соответствует каждому элементу этого типа, ведущая точка соответствует классу, а ведущий решётки соответствует идентификатору.

css
a                  /* every anchor on the page */
.product-title     /* any element with class product-title */
#product-price     /* the element with id product-price */
span.price-label   /* span elements that also have class price-label */

Потомок и дочерний. Пробел означает «где угодно внутри», как бы глубоко. > означает «только прямой дочерний», на один уровень вниз. Различие важно, когда в макете один и тот же тег вложен на нескольких уровнях, а нужен только непосредственный.

css
div.price-container span      /* any span inside, at any depth */
div.price-container > span    /* only spans that are direct children */

Атрибуты. Квадратные скобки сопоставляют по любому атрибуту, а не только по классу и идентификатору. Точное совпадение с [attr=val], подстрока с [attr*=val], префикс с [attr^=val], суффикс с [attr$=val]. Селекторы атрибутов часто дают наиболее стабильную точку привязки, так как data-атрибуты меняются реже, чем визуальные классы.

css
a[role='link']            /* anchors with role exactly "link" */
[data-testid='price']     /* any element with that test id */
a[href^='/product/']      /* anchors whose href starts with /product/ */

Позиция. Псевдоклассы выбирают по месту в порядке сиблингов. :first-child, :last-child и рабочая лошадка :nth-of-type(n) позволяют брать n-й элемент заданного тега: именно так вы вытаскиваете «вторую строку» или «четвёртый элемент списка» из повторяющегося блока.

css
.product-list li:first-child       /* first item in the list */
ul.specs li:nth-of-type(3)         /* the third li */
table tr:nth-of-type(2) td         /* cells in the second row */

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

XPath, поле за полем

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

Тег и потомок. Двойная косая черта: повседневное начало; она находит совпадающие элементы на любой глубине.

xpath
//div                         (: every div on the page :)
//div[@class='price-container']/span   (: direct span children :)

Предикаты по атрибутам. Внутри скобок атрибуты проверяются через @. Точное совпадение: [@class='x']; для классов с несколькими разделёнными пробелами значениями безопаснее contains(@class, 'x'), потому что он срабатывает, когда x: одно из нескольких.

xpath
//*[@id='product-price']                  (: by id :)
//*[contains(@class, 'product-title')]    (: class among many :)
//a[@href]                                (: any anchor that has an href :)

Сопоставление по тексту. Это флагманская возможность XPath. Можно выбрать элемент по тексту, который он содержит: точно с text()='...' или нестрого с contains(text(), '...'). В CSS нет ничего эквивалентного.

xpath
//button[text()='Add to Cart']
//span[contains(text(), 'In stock')]
//label[normalize-space()='Email address']

Позиция. Индексы XPath отсчитываются с 1 и задаются в предикатах. Также можно использовать функции last() и position() для выбора с конца или диапазона.

xpath
(//div[@class='product'])[1]      (: first matching product :)
//ul[@class='specs']/li[3]        (: the third li :)
//ul/li[last()]                   (: the final li :)

Оси. Настоящая мощь. Оси позволяют перемещаться в направлениях, недоступных CSS: following-sibling, preceding-sibling, parent и ancestor. Классический случай: пара «метка-значение», когда вы знаете текст метки и хотите получить значение рядом с ней.

xpath
(: the value cell next to the "Founded" label :)
//th[text()='Founded']/following-sibling::td

(: walk up from a price to its product card :)
//span[@class='price']/ancestor::div[@class='card']

Именно эти два запроса не имеют чистого CSS-эквивалента, и именно поэтому XPath остаётся в инструментарии.

Бок о бок: один элемент, два способа

Сравнение языков рядом делает компромиссы наглядными. Для распространённых целей оба почти равнозначны, и CSS обычно короче.

text
Goal                      CSS                          XPath
all anchors               a                            //a
class match               .product-title               //*[contains(@class,'product-title')]
id match                  #product-price               //*[@id='product-price']
tag + class               span.price-label             //span[@class='price-label']
descendant                .box span                    //*[@class='box']//span
direct child              .box > span                  //*[@class='box']/span
attribute exact           a[role='link']               //a[@role='link']
nth of type               li:nth-of-type(3)            //li[3]
text match                (not possible)               //button[text()='Buy']
walk up the tree          (not possible)               //span/ancestor::div[@class='card']

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

Запуск обоих в Python

Теория имеет свои пределы; вот как каждый язык выглядит в коде. Мы используем parsel (библиотека селекторов, на которой построен Scrapy), потому что он говорит на CSS и XPath с одним и тем же разобранным документом, что позволяет сравнивать их строка за строкой. BeautifulSoup и lxml: другие распространённые варианты, упомянуты ниже.

bash
python -m venv selectors_env
source selectors_env/bin/activate

pip install parsel

Загрузите разметку один раз, затем запрашивайте её обоими способами. Обратите внимание, что .css() и .xpath() в parsel оба возвращают список селекторов, так что паттерн доступа одинаков вне зависимости от языка.

python
from parsel import Selector

html = """
<div class="card">
  <h2 class="product-title">Wireless Mouse</h2>
  <span class="price">$24.99</span>
  <a role="link" href="/product/mouse">Details</a>
</div>
"""

sel = Selector(text=html)

# CSS: concise and familiar
title = sel.css("h2.product-title::text").get()
price = sel.css("span.price::text").get()
link  = sel.css("a[role='link']::attr(href)").get()

# XPath: the same three fields
title = sel.xpath("//h2[@class='product-title']/text()").get()
price = sel.xpath("//span[@class='price']/text()").get()
link  = sel.xpath("//a[@role='link']/@href").get()

print(title, price, link)

Для BeautifulSoup путь CSS: soup.select_one("span.price") и soup.select(...) для нескольких; нативного XPath нет. Когда XPath нужен, стандартным инструментом является lxml: tree.xpath("//span[@class='price']/text()") на разобранном документе lxml.html. parsel: удобная «золотая середина», поскольку он предоставляет оба API в одном объекте.

CSS compiles to XPath under the hood

Такие библиотеки, как parsel и lxml, перед выполнением транслируют CSS-селектор в XPath (через пакет cssselect). Именно поэтому всё, что можно выразить в CSS, имеет эквивалент на XPath, но не наоборот: сопоставление по тексту и перемещение вверх по осям не имеют CSS-формы для трансляции. Когда CSS-селектор не может выразить задуманное, переход к XPath: естественный следующий шаг, а не обходной путь.

Когда выигрывает XPath

Переходите к XPath, когда страница сопротивляется. Три ситуации делают его явным выбором.

  • Нужно сопоставить по тексту. «Кнопка с надписью Add to Cart» или «строка, чья метка Founded» может быть выражена только через содержимое. //button[text()='Add to Cart'] и contains(text(), ...) не имеют CSS-эквивалента.
  • Нужно подняться по дереву. Когда можно надёжно найти листовой узел (например, уникальную цену), но нужен его контейнер, ancestor::div[@class='card'] поднимается обратно. CSS движется только вниз и в стороны, но не вверх.
  • Нужны составные условия. Предикаты XPath комбинируются через and и or: //div[@class='item' and @data-available='true'] или фильтрация сразу по позиции и атрибуту. Складывать условия таким образом в CSS неудобно или невозможно.

Паттерн «метка-значение»: тот, с которым вы столкнётесь чаще всего. В таблице характеристик или боковой панели профиля нужное поле находится в ячейке рядом со стабильной меткой, тогда как у самой ячейки нет класса или он неопределённый. Привязка к тексту метки и шаг в сторону через following-sibling куда устойчивее, чем подсчёт позиций :nth-of-type, которые сдвигаются при добавлении или удалении поля.

Когда выигрывает CSS

Для подавляющего большинства задач CSS: лучший выбор по умолчанию. Он короче, читабельнее, и синтаксис знаком большинству разработчиков из работы с фронтендом, так что коллега сможет проверить ваши селекторы, не осваивая второй язык. На хорошо структурированной странице с разумными классами и идентификаторами .product-card .price говорит всё необходимое более кратко, чем его XPath-аналог.

CSS также естественно сочетается с инструментами автоматизации браузера. Когда вы управляете headless-браузером и скрапите динамический контент, те же CSS-селекторы, которые вы писали бы в document.querySelector, переносятся напрямую, что сохраняет единый словарь селекторов для статического парсинга и работы с живым DOM. Для простого, быстрого, повторяющегося извлечения на опрятном макете CSS: правильный инструмент, и к XPath переходят только тогда, когда CSS действительно не может выразить цель.

Crawlbase Crawling API

Селекторы неизбежны, когда вы самостоятельно разбираете сырой HTML, но не всегда ваша задача их писать. Scraper API автоматически разбирает распространённые типы страниц: страницы товаров, результаты поиска, отзывы: в структурированный JSON, так что для поддерживаемых целей вы полностью пропускаете XPath и CSS и просто читаете поля из ответа. Там, где нужен собственный парсинг, совместите его с рендеренным HTML и применяйте приведённые выше селекторы. Начните с бесплатного тарифа.

Как писать селекторы, которые не ломаются

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

  • Предпочитайте стабильные атрибуты визуальным классам. Хешированные или утилитарные классы вроде css-1x7a9q или mt-4 генерируются и часто меняются. data-testid, id, itemprop или ARIA role с куда большей вероятностью переживут рестайл. Привязывайтесь к ним, когда они есть.
  • Избегайте длинных, глубоких цепочек. Селектор вроде body > div > div > section > div:nth-child(2) > ul > li кодирует весь макет, и любая обёртка, добавленная в любом месте этого пути, сломает его. Сопоставляйте ближайший значимый контейнер и одну стабильную точку привязки, а не всю родословную.
  • Не полагайтесь на хрупкие позиции. :nth-of-type(4) предполагает, что счётчик не меняется. Когда доступна стабильная метка или атрибут, привязывайтесь к ней и двигайтесь относительно неё (здесь и блистают оси XPath), а не жёстко кодируйте индекс.
  • Используйте contains для многозначных классов. Элемент с class="btn btn-primary active" не совпадёт с [@class='btn-primary'] точно. Используйте contains(@class, 'btn-primary') в XPath или простой селектор .btn-primary в CSS, который уже сопоставляет один класс среди многих.
  • Явно сигнализируйте об ошибках, не замалчивайте их. Оберните извлечение так, чтобы отсутствующее поле возвращало None вместо сбоя, и логируйте, какой селектор вернул пустое значение. Это превращает изменение сайта из загадочной пустой записи в чёткий сигнал о том, какой селектор требует обновления.

Воспринимайте селекторы как код, который нуждается в обслуживании. Разметка меняется, и скрапер, работавший чисто в прошлом квартале, в итоге начнёт возвращать пустые поля. Исправление почти всегда: повторная инспекция живого элемента в dev tools и уточнение селектора, а не перестройка скрапера. Для общей настройки руководство по скрапингу веб-сайтов на Python проходит весь путь от загрузки до парсинга и сохранения, и паттерны селекторов из этой статьи органично вписываются в этот поток.

Полный отказ от селекторов

Иногда лучший селектор: никакого селектора. Если ваша цель относится к распространённым типам страниц, Crawling API возвращает разобранный JSON напрямую, и выбирать нечего. Для всего остального вы всё равно загружаете и разбираете сами, и рендеренный HTML для разбора можно получить из Crawling API, когда страница рендерится на клиенте или защищена. В любом случае навыки работы с селекторами, описанные здесь, именно и превращают сырую разметку в чистые записи, а знание обоих языков означает, что вы никогда не застрянете из-за того, что один из них не может достать элемент.

Итоги

Ключевые выводы

  • Оба языка работают с одним DOM. CSS-селекторы лаконичны и знакомы; XPath многословен, но более выразителен. Большинство реальных скраперов использует их оба.
  • CSS покрывает тег, класс, идентификатор, атрибут и позицию с помощью кратких читаемых паттернов: .class, #id, div > span, [attr=val], :nth-of-type(n).
  • XPath делает то, что CSS не может: сопоставлять по тексту через text() и contains(), подниматься по дереву через ancestor, шагать в стороны через following-sibling и комбинировать условия через and/or.
  • Запускайте оба на Python через parsel (.css() и .xpath() на одном объекте); BeautifulSoup поддерживает только CSS, lxml: основной инструмент для XPath.
  • Устойчивость важнее остроумия. Предпочитайте стабильные атрибуты, избегайте глубоких цепочек и хрупких индексов, используйте contains для многозначных классов и явно сигнализируйте, когда поле пропадает.
  • Можно полностью обойтись без селекторов на поддерживаемых типах страниц с автопарсингом Crawling API, оставляя рукописные селекторы для кастомных целей.

Часто задаваемые вопросы

Что лучше для новичков, XPath или CSS Selectors?

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

Поддерживают ли XPath и CSS Selectors все библиотеки для скрапинга?

Большинство поддерживает хотя бы один, и многие: оба. parsel и Scrapy обрабатывают CSS и XPath на одном объекте, lxml создан для XPath, а Selenium и Playwright принимают оба. BeautifulSoup: заметное исключение: он поддерживает CSS через .select(), но нативного XPath нет. Проверяйте документацию вашей библиотеки, прежде чем делать ставку на конкретный стиль.

Может ли CSS Selector находить элемент по его тексту?

Нет. В CSS нет возможности выбрать элемент по содержащемуся в нём тексту; он сопоставляет только по тегам, классам, идентификаторам, атрибутам и позиции. Когда нужна «кнопка с надписью Add to Cart» или «ячейка рядом с меткой Founded», это именно тот случай для XPath: text() и contains(text(), ...) не имеют CSS-эквивалента.

Быстрее ли XPath, чем CSS Selectors?

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

Как писать селекторы, которые не ломаются при изменении сайта?

Привязывайтесь к стабильным точкам: id, data-testid, itemprop или ARIA-роли вместо сгенерированных визуальных классов. Держите селекторы краткими, сопоставляя ближайший значимый контейнер, а не отслеживая всю родословную; избегайте жёстко закодированных индексов позиций там, где есть стабильная метка; используйте contains для многозначных классов. Затем явно сигнализируйте об отсутствующих полях, чтобы изменение разметки проявлялось как чёткий сигнал, а не тихая пустота.

Когда стоит вовсе отказаться от селекторов?

Когда ваша цель относится к распространённым типам страниц, которые уже понимает сервис автопарсинга. Crawling API возвращает структурированный JSON для поддерживаемых целей: страниц товаров и результатов поиска,: поэтому HTML разбирать не нужно и селекторы поддерживать тоже. Оставляйте рукописные XPath и CSS для кастомных страниц или полей, которые автопарсер не охватывает.

Начать создавать

Обходите любой сайт в масштабе, без борьбы с инфраструктурой.

Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.

Самообслуживание · Звонок отдела продаж не требуется · Доступны корпоративные объёмы краулинга