Навигация по дереву и таблицы
HTML-документ — дерево: у каждого тега есть родитель (тег, внутри которого он лежит), могут быть дети (теги и текст внутри) и соседи (другие дети того же родителя). По этому дереву можно ходить во все стороны.
<div class="card">
<h3>Чайник</h3>
<p>Цена: <span>1500</span></p>
</div>
Здесь у <span> родитель — <p>, у <p> родитель — <div>. <h3> и <p> — соседи.
Вверх: родители
span = soup.find("span")
print(span.parent.name) # p
print([t.name for t in span.parents]) # ['p', 'div', '[document]']
.name— имя тега;.parent— непосредственный родитель;.parents— все предки снизу вверх. Последний из них — сам объектBeautifulSoupс именем[document].
Подниматься вверх удобно, когда нашли что-то внутри и нужен весь блок: нашли цену — поднялись до карточки товара и взяли из неё название. Для этого есть и find_parent("div") — ближайший предок нужного вида.
Вниз: дети
div = soup.find("div")
for child in div.children: # непосредственные дети — теги и текст
print(repr(child.name)) # None для текста, 'h3', 'p'
print(div.find_all(recursive=False)) # только теги-дети, без текста
.children перебирает непосредственных детей, включая текстовые узлы — переводы строк и пробелы между тегами. Если нужны только теги, удобнее find_all(recursive=False). Всех потомков на любой глубине даёт .descendants или обычный find_all().
Вбок: соседи
h3 = soup.find("h3")
print(h3.find_next_sibling()) # <p>Цена: ...</p> — следующий сосед-тег
print(h3.find_next_sibling("p")) # ближайший следующий <p>
print(h3.find_next_siblings()) # все следующие соседи
print(h3.find_previous_sibling()) # предыдущий (здесь None)
Есть и свойства .next_sibling / .previous_sibling, но они возвращают и текстовые узлы, поэтому часто дают перевод строки вместо тега. Методы find_next_sibling(...) пропускают текст.
Соседи помогают, когда в разметке нет вложенности, а смысл задаётся порядком: заголовок, а за ним абзацы до следующего заголовка; термин <dt>, а за ним его определения <dd>.
Таблицы
Таблица в HTML — <table>, строки — <tr>, ячейки — <th> (заголовочная) и <td> (обычная). Строки часто лежат в <thead> и <tbody>, но find_all("tr") находит их на любой глубине:
table = soup.find("table")
rows = []
for tr in table.find_all("tr"):
cells = [cell.get_text().strip() for cell in tr.find_all(["th", "td"])]
rows.append(cells)
header, data = rows[0], rows[1:]
find_all(["th", "td"]) возвращает ячейки обоих видов в порядке документа. Готовые таблицы так легко перенести в pandas: pd.DataFrame(data, columns=header).
Итог
.parent,.parents,find_parent(...)— вверх;.children,find_all(recursive=False),.descendants— вниз;find_next_sibling(...),find_next_siblings(...),find_previous_sibling(...)— вбок;- таблица:
tr→find_all(["th", "td"])→get_text().strip().