Модуль BeautifulSoup4 превращает HTML-документ в дерево объектов Python, которое будет состоять в основном из четырех видов - это Tag
, NavigableString
, BeautifulSoup
и Comment
.
Tag
HTML-дерева BeautifulSoup4.Tag.string
возвращает/заменяет содержимое тега.Tag.append
добавляет содержимое тега.Tag.extend()
добавляет каждый элемент списка в Tag
по порядку.Tag.clear()
удаляет содержимое тега.Tag.extract()
удаляет тег и возвращает, то что удалено.Tag.decompose()
уничтожает тег вместе с его содержимым.Tag.wrap()
оборачивает тег в указанный тег.Tag.unwrap()
противоположность методу Tag.wrap()
.Объект 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
много атрибутов и методов, которые рассматриваются ниже, но на данный момент наиболее важными особенностями тега являются его имя и атрибуты.
У каждого тега есть имя, доступное как атрибут 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>
У объекта 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'
Каждый тег и каждая строка имеет родителя - тег, который его содержит. Получить доступ к родительскому элементу можно с помощью атрибута 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]
Дочерние элементы доступны через атрибут под названием 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>
и его дочерний элемент - строка.
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
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()
возвращает тег, который был заменен.