Навигация по DOM-дереву с помощью CSS-селекторов

Навигация по DOM-дереву с помощью CSS-селекторов Парсинг (BeautifulSoup, Selenium)

Важное предупреждение !!!

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

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

Перед началом прочтения статьи, ОБЯЗАТЕЛЬНО ознакомтесь со статьей «Что такое парсинг сайтов: законно ли это?«

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

Привет! Мы уже научились искать элементы по тегам, классам и id. Но HTML — это дерево. Иногда нужно двигаться вверх к родителю, вниз к детям или вбок к соседям. В этом помогают CSS-селекторы и навигация по DOM-дереву.

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

  • Навигацию по DOM-дереву
  • Родители, дети, соседи
  • CSS-селекторы в BeautifulSoup

Что нужно знать перед началом

Для этого урока тебе понадобится:

  • Установленный BeautifulSoup (статья 2)
  • Базовое понимание HTML-структуры

Совет: DOM (Document Object Model) — это представление HTML-документа в виде дерева. Понимание этого дерева — ключ к эффективному парсингу.

Основная часть

Структура DOM-дерева

HTML-документ можно представить как дерево:

html
├── head
│   └── title
└── body
    ├── h1
    ├── div
    │   ├── p
    │   └── p
    └── footer
        └── p
<!DOCTYPE html>
<html>
<head>
    <title>Заголовок</title>
</head>
<body>
    <h1>Привет</h1>
    <div class="content">
        <p>Абзац 1</p>
        <p>Абзац 2</p>
    </div>
    <footer>
        <p>Подвал</p>
    </footer>
</body>
</html>

Дети и потомки

Дети (children) — элементы, находящиеся непосредственно внутри родителя.

Потомки (descendants) — все элементы внутри родителя, включая вложенные.

from bs4 import BeautifulSoup

html = """
<div class="content">
    <p>Первый абзац</p>
    <p>Второй абзац</p>
    <div>
        <p>Вложенный абзац</p>
    </div>
</div>
"""

soup = BeautifulSoup(html, "html.parser")
div = soup.find("div", class_="content")

# Дети (только прямые)
children = list(div.children)
for child in children:
    if child.name:
        print(child.name)   # p, p, div

# Потомки (все, включая вложенные)
descendants = div.find_all("p")
for p in descendants:
    print(p.text)
# Первый абзац
# Второй абзац
# Вложенный абзац

Совет: children возвращает итератор, а не список. Используй list() для преобразования.

Родители

Движение вверх по дереву.

html = """
<div class="container">
    <div class="content">
        <p id="target">Целевой абзац</p>
    </div>
</div>
"""

soup = BeautifulSoup(html, "html.parser")
p = soup.find("p", id="target")

# Родитель (непосредственно выше)
parent = p.parent
print(parent.name)   # div
print(parent.get("class"))   # ['content']

# Все родители
for ancestor in p.parents:
    print(ancestor.name)   # div, div, body, html, [document]

Совет: parents возвращает итератор всех предков до корня документа.

Соседи (siblings)

Движение вбок по дереву.

html = """
<p>Первый абзац</p>
<p class="middle">Средний абзац</p>
<p>Третий абзац</p>
"""

soup = BeautifulSoup(html, "html.parser")
middle = soup.find("p", class_="middle")

# Предыдущий сосед
prev = middle.previous_sibling
print(prev.text)   # Первый абзац

# Следующий сосед
next_p = middle.next_sibling
print(next_p.text)   # Третий абзац

Важно: Между тегами могут быть текстовые узлы (переводы строк, пробелы). Иногда нужно использовать next_sibling несколько раз или find_next_sibling().

# Поиск следующего элемента-тега
next_p = middle.find_next_sibling("p")
print(next_p.text)   # Третий абзац

CSS-селекторы в BeautifulSoup

CSS-селекторы позволяют искать элементы так же, как в стилях.

html = """
<div id="main">
    <p class="title">Заголовок</p>
    <div class="content">
        <p>Абзац 1</p>
        <p class="highlight">Абзац 2</p>
    </div>
</div>
"""

soup = BeautifulSoup(html, "html.parser")

# По id
element = soup.select_one("#main")
print(element.name)   # div

# По классу
elements = soup.select(".content p")
for el in elements:
    print(el.text)
# Абзац 1
# Абзац 2

# Вложенные селекторы
title = soup.select_one("#main .title")
print(title.text)   # Заголовок

# Атрибуты
links = soup.select("a[href]")

Совет: select_one() возвращает первый найденный элемент, select() — список .

Получение атрибутов

html = """
<a href="https://example.com" target="_blank">Ссылка</a>
<img src="image.jpg" alt="Картинка">
"""

soup = BeautifulSoup(html, "html.parser")

# Получение атрибута
link = soup.find("a")
print(link.get("href"))   # https://example.com
print(link["href"])       # https://example.com
print(link.attrs)         # {'href': 'https://example.com', 'target': '_blank'}

img = soup.find("img")
print(img.get("src"))     # image.jpg
print(img.get("alt"))     # Картинка

# Безопасное получение
href = link.get("href", "")
print(href)               # https://example.com

Важно: Используй .get() для безопасного получения атрибута — если атрибута нет, вернётся None (или значение по умолчанию).

Пример из реальной жизни

Парсинг карточек товаров:

import requests
from bs4 import BeautifulSoup

url = "https://example-shop.com/products"
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")

# Находим все карточки товаров
products = soup.select(".product-card")

for product in products:
    # Название товара
    name = product.select_one(".product-name")
    # Цена
    price = product.select_one(".product-price")
    # Ссылка
    link = product.select_one("a")

    print(name.text.strip())
    print(price.text.strip())
    print(link.get("href"))
    print("-" * 40)

Задачи для закрепления

Задача 1. Найди всех прямых потомков тега <div>.

Задача 2. Найди родителя элемента с классом «target».

Задача 3. Найди предыдущий и следующий абзац относительно выбранного.

Задача 4. Используй CSS-селектор для поиска всех ссылок внутри <div> с классом «menu».

Задача 5. Получи атрибут src у изображения.

Ответы:

Задача 1.

div = soup.find("div")
children = div.find_all(recursive=False)

Задача 2.

element = soup.find(class_="target")
parent = element.parent

Задача 3.

p = soup.find("p")
prev = p.find_previous_sibling("p")
next_p = p.find_next_sibling("p")

Задача 4.

links = soup.select(".menu a")

Задача 5.

img = soup.find("img")
src = img.get("src")

Нюансы и подводные камни

Текстовые узлы между тегами

html = "<p>Абзац</p>\n<p>Следующий</p>"
soup = BeautifulSoup(html, "html.parser")
p = soup.find("p")
print(p.next_sibling)   # '\n' (текстовый узел)

Используй find_next_sibling() для поиска следующего тега.

parents vs parent

  • parent — один элемент (непосредственный родитель)
  • parents — итератор всех предков

.attrs возвращает словарь

print(element.attrs)   # {'class': ['content'], 'id': 'main'}

Частые ошибки и как их избежать

Ошибка 1: previous_sibling вместо find_previous_sibling

Для поиска именно тега используй find_previous_sibling("tag").

Ошибка 2: Путаница между .select() и .find_all()

select() — CSS-селекторы, find_all() — поиск по тегам/классам.

Ошибка 3: Неправильный CSS-селектор

Проверяй синтаксис: .class#idtagtag.class.

Шпаргалка

Что нужноКак пишется
Прямые детиelement.children
Все потомкиelement.find_all()
Родительelement.parent
Все предкиelement.parents
Следующий сосед-тегelement.find_next_sibling("tag")
Предыдущий сосед-тегelement.find_previous_sibling("tag")
CSS-селектор (первый)soup.select_one(".class")
CSS-селектор (все)soup.select(".class")
Получить атрибутelement.get("href")

Заключение / Выводы

Сегодня мы:

  • Научились перемещаться по DOM-дереву
  • Работали с родителями, детьми и соседями
  • Использовали CSS-селекторы для гибкого поиска
  • Получали атрибуты элементов

КВИЗ

Что дальше?

Оцените статью
IMI-DS - PYTHON LERNEN
Содержание
Оглавление ×