CSS-селекторы и навигация по дереву

CSS-селекторы — компактный язык адресации элементов, знакомый по вёрстке.
Метод select в BeautifulSoup принимает CSS-селектор: div.product > span.price. Это часто короче и выразительнее, чем цепочки find.

Если ты знаком с CSS, селекторы покажутся естественными. .price — элемент с классом price, #main — с id main, div a — все ссылки внутри div, ul > li — прямые потомки. BeautifulSoup поддерживает их через select (все) и select_one (первый).

from bs4 import BeautifulSoup

soup = BeautifulSoup(html, 'lxml')

# все цены
prices = soup.select('div.product span.price')

# первый заголовок внутри карточки
title = soup.select_one('.product .title')

# ссылки только прямых потомков списка
items = soup.select('ul.menu > li > a')

Навигация по дереву

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

row = soup.find('td', string='Цена')
value = row.find_next_sibling('td').text   # соседняя ячейка

card = price_tag.find_parent('div', class_='product')
title = card.find('h2').text

Как работает под капотом

BeautifulSoup транслирует CSS-селектор в обход дерева через библиотеку soupsieve. Комбинатор > означает «прямой потомок», пробел — «любой потомок на любой глубине». Навигационные методы (find_parent, find_next_sibling) двигаются по связям узлов, не делая повторного поиска по всему документу, — это быстро и предсказуемо. Извлечение списка значений — типовой приём:

Попробуй сам ▶

# демонстрация логики «собрать тексты» на чистом Python
rows = [
    {'title': 'Python с нуля', 'price': '0'},
    {'title': 'SQL продвинутый', 'price': '1990'},
    {'title': 'Скрейпинг', 'price': '2490'},
]

# так выглядит результат после .select(...) -> список словарей
total = 0
for r in rows:
    print(f"{r['title']:20} {r['price']:>6} руб")
    total += int(r['price'])
print('-' * 30)
print(f"{'Итого':20} {total:>6} руб")

find или select: что выбрать

BeautifulSoup даёт два стиля поиска, и оба легитимны. Методы find/find_all — «питоний» способ: ищешь по имени тега и именованным аргументам (class_, id, attrs). Методы select/select_one принимают CSS-селектор строкой. CSS часто короче для вложенных условий: div.product a.buy против цепочки из нескольких find. На практике их свободно смешивают: нашёл карточку через select_one, а внутри неё дёргаешь поля через find.

Отдельно стоит освоить регистрозависимость и комбинаторы. Пробел между селекторами означает «потомок на любой глубине», > — «прямой ребёнок», , — «или то, или другое». Псевдоклассы вроде :nth-of-type мощны, но делают селектор хрупким — их применяют осторожно. Хорошая привычка — сначала подобрать селектор прямо в консоли браузера через document.querySelectorAll('...'), убедиться, что он ловит ровно нужные элементы, и только потом переносить его в код. Так ты экономишь время на отладке и не гоняешь лишние запросы к сайту ради проверки.

Частые ошибки

  • Слишком хрупкие селекторы. div > div > div:nth-child(3) ломается от любой перестановки. Цепляйся за классы и осмысленные атрибуты.
  • Путать select и find. select всегда возвращает список (даже из одного элемента), а find — один элемент.
  • Забывать про точку перед классом. select('product') ищет тег, а select('.product') — класс.

Best practices

  • Предпочитай селекторы по классу и атрибутам, а не по позиции (nth-child).
  • Используй навигацию по соседям, когда у значения нет своего класса, но есть подпись рядом.
  • Проверяй селектор прямо в консоли браузера через document.querySelectorAll.

Помни о компромиссе между точностью и устойчивостью селектора. Слишком общий селектор (div) поймает лишнее; слишком конкретный и завязанный на позицию (div:nth-child(4) > span) развалится при малейшем изменении вёрстки. Золотая середина — цепляться за смысловые признаки: семантические классы, id, атрибуты data-* и текстовые «якоря» рядом с нужным значением. Хороший селектор переживает мелкие правки дизайна и ломается только при действительно серьёзных изменениях структуры страницы.

На практике селекторы удобно подбирать итеративно: открыл инспектор, скопировал предлагаемый браузером селектор, упростил его до самой устойчивой части, проверил через document.querySelectorAll, что он ловит ровно нужные элементы и ничего лишнего, и только потом перенёс в код BeautifulSoup. Этот цикл «инспектор → упрощение → проверка → код» превращает подбор селектора из гадания в инженерную процедуру и резко снижает число правок, когда скрейпер запускается на полном объёме страниц.

Итог: CSS-селекторы делают код короче и читаемее. select + навигация по дереву (find_parent, find_next_sibling) покрывают практически любой случай извлечения.

Проверьте себя
1. Что вернёт метод select() в BeautifulSoup?
AОдин элемент или None
BСписок всех элементов, подходящих под CSS-селектор
CТекст первого элемента
DКоличество элементов
2. Когда полезна навигация по соседним элементам (find_next_sibling)?
AКогда у нужного значения нет своего класса, но рядом есть подпись-якорь
BТолько для картинок
CНикогда не нужна
DТолько в Scrapy