Сообщить об ошибке.

Основной объект Tag() модуля BeautifulSoup4 в Python

Модуль BeautifulSoup4 превращает HTML-документ в дерево объектов Python, которое будет состоять в основном из четырех видов - это Tag, NavigableString, BeautifulSoup и Comment.

Основной объект Tag HTML-дерева BeautifulSoup4.


Объект Tag соответствует тегу XML или HTML в исходном документе:

>>> from bs4 import BeautifulSoup
# создаем HTML-дерево
>>> root = BeautifulSoup('<b class="boldest">Extremely bold</b>', 'html.parser')
# извлекаем тег `<b>`
>>> tag = root.b
# смотрим тип
>>> type(tag)
# <class 'bs4.element.Tag'>

У объекта Tag много атрибутов и методов, которые рассматриваются ниже, но на данный момент наиболее важными особенностями тега являются его имя и атрибуты.

Замена имени HTML-тега.

У каждого тега есть имя, доступное как атрибут Tag.name:

>>> html = '<p><b class="boldest">Extremely bold</b></p>'
>>> root = BeautifulSoup(, 'html.parser')
>>> tag = root.b
>>> tag.name
# 'b'

Если изменить имя тега, то это изменение будет отражено в любой HTML-разметке, созданной BeautifulSoup4.

# меняем имя тега с `<b>` на `<blockquote>`
>>> tag.name = "blockquote"
>>> tag
# <p><blockquote class="boldest">Extremely bold</blockquote></p>

Доступ к атрибутам HTML-тега.

У объекта Tag может быть любое количество атрибутов. Тег <b id = "boldest"> имеет атрибут 'id', значение которого равно 'boldest'. Доступ к атрибутам тега можно получить, обращаясь с тегом как со словарем:

>>> from bs4 import BeautifulSoup
>>> root = BeautifulSoup('<p><b id="boldest">bold</b></p>', 'html.parser').
>>> tag = root.b
>>> tag['id']
# 'boldest'

Доступ к словарю с атрибутами тега можно получить напрямую, обращаясь к Tag.attrs:

>>> tag.attrs
# {'id': 'boldest'}

Можно добавлять, удалять и изменять атрибуты тега. Опять же, это делается путем обращения с тегом как со словарем dict:

>>> tag['id'] = 'verybold'
>>> tag['class'] = 'another'
>>> tag
# <b class="class" id="verybold"></b>

# удаляем атрибуты
>>> del tag['id']
>>> del tag['another-attribute']
>>> tag
# <b>bold</b>

# смотрим
>>> tag['id']
# KeyError: 'id'
>>> tag.get('id')
# None

В HTML 5 есть атрибуты, которые могут иметь несколько значений. Самый распространённый из многозначных атрибутов - это class, который может иметь более одного класса CSS. Среди прочих rel, rev, accept-charset, headers и accesskey. Модуль BeautifulSoup4 представляет значения многозначного атрибута в виде списка:

>>> root = BeautifulSoup('<p class="body"></p>', 'html.parser')
>>> root.p['class']
# ['body']

>>> root = BeautifulSoup('<p class="body strikeout"></p>', 'html.parser')
>>> root.p['class']
# ['body', 'strikeout']

Если атрибут выглядит так, будто он имеет более одного значения, но это не многозначный атрибут, определенный какой-либо версией HTML- стандарта, то BeautifulSoup4 оставит атрибут как есть:

>>> root = BeautifulSoup('<p id="my id"></p>', 'html.parser')
>>> root.p['id']
# 'my id'

Когда тег преобразовывается обратно в строку, несколько значений атрибута объединяются:

>>> html = '<p>Back to the <a rel="index">homepage</a></p>'
>>> root = BeautifulSoup(html, 'html.parser')
>>> root.a['rel']
# ['index']
>>> root.a['rel'] = ['index', 'contents']
>>> root
# <p>Back to the <a rel="index contents">homepage</a></p>

Можно отключить объединение, передав в конструктор BeautifulSoup аргумент multi_valued_attributes=None:

html = '<p class="body strikeout"></p>'
>>> root = BeautifulSoup(html, 'html.parser', multi_valued_attributes=None)
>>> root.p['class']
# 'body strikeout'

Если BeautifulSoup4 разбирает документ как XML, то многозначных атрибутов не будет:

# вторым аргументом указываем XML парсер (требуется установка)
xml_soup = BeautifulSoup('<p class="body strikeout"></p>', 'xml')
xml_soup.p['class']
# 'body strikeout'

Родительские элементы HTML-тега.

Каждый тег и каждая строка имеет родителя - тег, который его содержит. Получить доступ к родительскому элементу можно с помощью атрибута Tag.parent. Например, тег <head> является родительским для тега <title>:

html = """
<html>
  <head>
    <title>The Dormouse's story</title>
  </head>
  <body>
    <div>
      <p class="title"><b>The Dormouse's story</b></p>
    </div>
  </body>
</html>"""
>>> root = BeautifulSoup(html, 'html.parser')
>>> title_tag = root.title
>>> title_tag
# <title>The Dormouse's story</title>
>>> title_tag.parent
# <head>
# <title>The Dormouse's story</title>
# </head>

Строка заголовка сама имеет родителя: тег <title>, содержащий ее:

>>> title_tag.string
# "The Dormouse's story"
>>> title_tag.string.parent
# <title>The Dormouse's story</title>

Родительским элементом тега верхнего уровня, такого как <html>, является сам объект BeautifulSoup:

html_tag = root.html
type(html_tag.parent)
# <class 'bs4.BeautifulSoup'>

А BeautifulSoup.parent определяется как None:

>>> print(soup.parent)
# None

Можно перебрать всех родителей элемента с помощью атрибута Tag.parents. В следующем примере Tag.parents используется для перемещения от тега <b>, расположенного внутри документа, до самого верха:

>>> b_tag = root.b
>>> b_tag
# <b>The Dormouse's story</b>
>>> for parent in b_tag.parents:
...     print(parent.name)

# p
# div
# body
# html
# [document]

Дочерние элементы HTML-тега.

Дочерние элементы доступны через атрибут под названием Tag.contents.

Внимание! Дочерними элементами так же считаются текст (или пробелы), расположенный между HTML-тегов и переносы строк \n. Например:

>>> head_tag = root.head
>>> head_tag.contents
# ['\n', <title>The Dormouse's story</title>, '\n']

Так что лучше заранее привести HTML-документ к "нормальному виду", например с помощью регулярных выражений, командой re.sub(r'>\s+<', '><', html.replace('\n', '')).

Уберем переносы строк и пробелы между тегами в HTML при помощи простого регулярного выражения и заново создадим дерево BeautifulSoup().

>>> import re
>>> html = re.sub(r'>\s+<', '><', html.replace('\n', ''))
>>> root = BeautifulSoup(html, 'html.parser')
>>> head_tag = root.head
>>> head_tag.contents
# [<title>The Dormouse's story</title>]
>>> title_tag = head_tag.contents[0]
>>> title_tag
# <title>The Dormouse's story</title>
>>> title_tag.contents
# ['The Dormouse's story']

Можно перебирать дочерние элементы с помощью генератора Tag.children:

for child in title_tag.children:
    print(child)
# The Dormouse's story

Атрибуты Tag.contents и Tag.children применяются только в отношении непосредственных дочерних элементов тега. Например, тег <head> имеет только один непосредственный дочерний тег <title> (смотрите выше).

Но у самого тега <title> есть дочерний элемент: строка 'The Dormouse’s story'. В некотором смысле, эта строка также является дочерним элементом тега <head>. Атрибут Tag.descendants позволяет перебирать все дочерние элементы тега рекурсивно: его непосредственные дочерние элементы, дочерние элементы дочерних элементов и так далее:

for child in head_tag.descendants:
    print(child)
# <title>The Dormouse's story</title>
# The Dormouse's story

У тега <head> есть только один дочерний элемент, но при этом у него два потомка: тег <title> и его дочерний элемент - строка.

Перемещение/навигация по HTML-документу.

Tag.next_sibling:
Tag.previous_sibling:

Перемещаться по одному уровню можно при помощи атрибутов Tag.next_sibling и Tag.previous_sibling.

Например

html = """
<body>
  <div>
    <p class="title"><b>The story</b></p>
    <p class="new"><i>New story</i></p>
    <p class="other"><b>Other story</b></p>
  </div>
</body>"""
# очищаем HTML от переносов строк 
# и пробелов между тегами
>>> html = re.sub(r'>\s+<', '><', html.replace('\n', ''))
>>> root = BeautifulSoup(html, 'html.parser')
# выбираем первый тег `<p>`
>>> p_tag = root.p
>>> p_new_tag = p_tag.next_sibling
>>> p_new_tag
# <p class="new"><i>New story</i></p>
>>> p_new_tag.previous_sibling
# <p class="title"><b>The story</b></p>
>>> p_new_tag.next_sibling
# <p class="other"><b>Other story</b></p>

Tag.next_siblings:
Tag.previous_siblings:

Название методы Tag.next_siblings() и Tag.previous_siblings() отличаются от предыдущих на одну последнюю букву "s" (означающую множественное число).

Эти методы позволяют перебрать одноуровневые элементы данного тега и представляют собой генераторы.

>>> p_tag
# <p class="title"><b>The story</b></p>
>>> for sibling in p_tag.next_siblings:
...     print(sibling)

# <p class="new"><i>New story</i></p>
# <p class="other"><b>Other story</b></p>

Tag.next_element:
Tag.previous_element:

Атрибут .next_element строки или HTML-тега указывает на то, что было разобрано непосредственно после него. Это может быть то же, что и .next_sibling, но обычно результат резко отличается.

>>> p_tag.next_element
# <b>The story</b>
>>> p_tag.next_element.next_element
# The story

Почему последний результат - это строка, расположенная внутри тега <b>, а не следующий тег <p>? Это связано с тем как работает HTML-парсер. Парсер берет строку символов и превращает ее в серию событий: например, открыть тег <p>, открыть тег <b>, добавить строку The story, закрыть тег <b>, закрыть тег <p>, добавить перевод строки \n, открыть тег <p> и так далее.

Атрибут .previous_element является полной противоположностью .next_element. Он указывает на элемент, который был обнаружен при разборе непосредственно перед текущим:

>>> p_tag.previous_element
# <div>
#   <p class="title"><b>The story</b></p>
#   <p class="new"><i>New story</i></p>
#   <p class="other"><b>Other story</b></p>
# </div>

Tag.next_elements:
Tag.previous_elements:

При помощи атрибутов Tag.next_elements и Tag.previous_elements можно получить список элементов, в том порядке, в каком он был разобран парсером.

for el in p_tag.next_elements:
    print(el)

# <b>The Dormouse's story</b>
# The Dormouse's story
# <p class="new"><i>New story</i></p>
# <i>New story</i>
# New story
# <p class="other"><b>Other story</b></p>
# <b>Other story</b>
# Other story

Изменение HTML-документа.

Tag.string.

Метод Tag.string возвращает содержимое, расположенное внутри тега, но если заменить значение Tag.string новой строкой, то содержимое тега будет заменено на эту строку:

>>> html = '<a href="http://example.com/"><i>example.com</i></a>'
>>> root = BeautifulSoup(markup, 'html.parser')
>>> tag = root.a
>>> tag.string = "New link text."
>>> tag
# <a href="http://example.com/">New link text.</a>

Будьте осторожны: если тег содержит другие теги, то все их содержимое будет уничтожено.

Tag.append().

Можно добавить содержимое тега с помощью Tag.append(). Метод работает точно так же, как list.append() для списка в Python:

>>> root = BeautifulSoup("<i>Foo</i>", 'html.parser')
>>> root.i.append("Bar")
>>> root
# <a>FooBar</a>
>>> root.i.contents
# ['Foo', 'Bar']

Tag.extend().

Объект Tag также поддерживает метод .extend(), который добавляет каждый элемент списка в Tag по порядку:

>>> root = BeautifulSoup("<em>Soup</em>", 'html.parser')
>>> root.em.extend(["'s", " ", "on"])
>>> root
# <em>Soup's on</em>
>>> root.em.contents
# ['Soup', ''s', ' ', 'on']

Tag.clear().

Удаляет содержимое тега:

>>> html = '<a href="http://example.com/"><i>example.com</i></a>'
>>> root = BeautifulSoup(html, 'html.parser')
>>> tag = root.a
>>> tag.clear()
>>> tag
# <a href="http://example.com/"></a>

Tag.extract().

Метод .extract() удаляет тег Tag..extract() или строку Tag.string.extract() из дерева, но при этом возвращает то, что было удалено:

>>> html = '<a href="http://example.com/">linked to <i>example.com</i></a>'
>>> root = BeautifulSoup(html, 'html.parser')
>>> a_tag = root.a
# удаляем тег `<i>`
>>> i_tag = root.i.extract()
>>> a_tag
# <a href="http://example.com/">linked to </a>

# смотрим, на то, что было удалено
>>> i_tag
# <i>example.com</i>
>>> print(i_tag.parent)
# None

Tag.decompose().

Метод Tag.decompose() удаляет тег из дерева, а затем полностью уничтожает его вместе с его содержимым:

>>> html = '<a href="http://example.com/">link to <i>example.com</i></a>'
>>> root = BeautifulSoup(html, 'html.parser')
>>> a_tag = root.a
>>> root.i.decompose()
>>> a_tag
# <a href="http://example.com/">link to </a>

Tag.replace_with()

Метод .replace_with()' удаляет тегTag.replacewith()или строкуTag.string.replacewith()` из дерева и заменяет его другим тегом или строкой:

Так же метод возвращает тег или строку, которая была заменена, следовательно ее можно сохранить в переменной, а потом добавить обратно в другую часть HTML-дерева

>>> html = '<a href="http://example.com/">link to <i>example.com</i></a>'
>>> root = BeautifulSoup(html, 'html.parser')
>>> a_tag = root.a
>>> new_tag = root.new_tag("b")
>>> new_tag.string = "example.net"
>>> a_tag.i.replace_with(new_tag)
# <i>example.com</i>
>>> a_tag
# <a href="http://example.com/">link to <b>example.net</b></a>

Tag.wrap()

Метод .wrap() обертывает тег Tag.wrap() или строку Tag.string.wrap() в указанный тег. Он возвращает новую HTML-разметку:

root = BeautifulSoup("<p>I wish I was bold.</p>", 'html.parser')
root.p.string.wrap(root.new_tag("b"))
# <b>I wish I was bold.</b>
root.p.wrap(root.new_tag("div"))
# <div><p><b>I wish I was bold.</b></p></div>

Tag.unwrap()

Метод Tag.unwrap() противоположен методу Tag.wrap(). Он заменяет весь тег на его содержимое. Этим методом удобно очищать разметку:

>>> html = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
>>> root = BeautifulSoup(html, 'html.parser')
>>> a_tag = root.a
>>> a_tag.i.unwrap()
>>> a_tag
# <a href="http://example.com/">I linked to example.com</a>

Как и Tag.replace_with(), метод Tag.unwrap() возвращает тег, который был заменен.