Внимание!!! Важно для понимания как работают методы поиска модуля BeautifulSoup4:
Объект BeautifulSoup()
представляет HTML-документ как единое целое. В большинстве случаев его можно рассматривать как корневой объект Tag
с соответствующим поведением. В то же время объект BeautifulSoup
это не совсем HTML- или XML-тег, и он не может иметь атрибутов, но имеет специальный атрибут .name
, который используется, как отправная точка при поиске.
>>> from bs4 import BeautifulSoup >>> root = BeautifulSoup ('<p><b>Hello</b></p>', 'html.parser') >>> root.name # '[document]'
from bs4 import BeautifulSoup root = BeautifulSoup (html, 'html.parser') # возвращает первого потомка el = root.find(name, attrs, recursive, string, **kwargs) # возвращает всех потомков # ищет первого потомка el = root.find_all(name, attrs, recursive, string, limit, **kwargs) new_el = el.find(...) ...
name
- фильтр по имени HTML-тега, может быть:bool
,attrs
- словарь, с атрибутами HTML-тега в виде {"class": "sister"}
,recursive
- отвечает за рекурсивный просмотр потомков, по умолчанию True
,string
- ищет совпадение в тексте HTML-документа, а не в тегах. Принимаемые значения такие же, как у аргумента name
.limit
- целое число, ограничивает максимальное количество совпадений. **kwargs
- фильтр по атрибутам HTML-тега. Принимаемые значения такие же, как у аргумента name
..find_all()
- список объектов HTML-документа или пустой список []
..find()
- объект элемента HTML-документа или None
..find_all()
:Метод .find_all()
модуля BeautifulSoup4 просматривает и извлекает ВСЕХ потомков тега, которые соответствуют переданным фильтрующим аргументам.
Метод .find()
похож на метод .find_all()
, но извлекает только первый найденный HTML-тег.
HTML-документ, на котором будут рассматриваться примеры:
html = """<html><head><title>The Dormouse's story</title></head> <body> <p class="title"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p>"""
name
.**kwargs
.attrs
.string
.recursive
.name
.name
, как строка:Если аргумент name
- это строка, то BeautifulSoup4 будет искать только теги с именами, соответствующие этой строке. Текст HTML-документа будет игнорироваться, так же как и теги, имена которых не соответствуют заданным.
>>> from bs4 import BeautifulSoup >>> root = BeautifulSoup (html, 'html.parser') >>> root.find_all('title') # [<title>The Dormouse's story</title>] >>> root.find_all('b') # [<b>The Dormouse's story</b>]
name
, как объект регулярного выражения:Если аргумент name
- это объект регулярного выражения, то метод .find_all
отфильтрует результаты в соответствии с этим регулярным выражением, используя его метод re.search()
.
Пример ищет все теги, имена которых начинаются с буквы 'b'
:
import re for tag in root.find_all(re.compile("^b")): print(tag.name) # body # b
Пример ищет все теги, имена которых содержат букву 't'
:
for tag in root.find_all(re.compile("t")): print(tag.name) # html # title
name
, как список:Если аргумент name
- это список строк, то метод .find_all
будет искать теги с именами, которые совпадут со строками из этого списка.
Пример ищет все теги <a>
и все теги <b>
в HTML-документе:
>>> root.find_all(["a", "b"]) # [<b>The Dormouse's story</b>, # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
name
, как объект bool
:Если аргумент name
- это значение True
, то BeautifulSoup4 найдет ВСЕ теги HTML-документа.
Пример ищет ВСЕ теги в документе, но не текстовые строки:
for tag in root.find_all(True): print(tag.name) # html # head # title # body # p # b # p # a # a # a # p
name
, как функция:Если аргумент name
- это функция, то она должна принимать в качестве единственного аргумента элемент HTML-документа и возвращать True
, если переданный элемент подходит по параметрам.
Пример функции, которая возвращает True
, если в теге определен атрибут 'class'
, но нет атрибута 'id'
:
def class_no_id(tag): return tag.has_attr('class') and not tag.has_attr('id')
Пример использования функции в качестве фильтра HTML-тегов:
>>> root.find_all(class_no_id) # [<p class="title"><b>The Dormouse's story</b></p>, # <p class="story">Once upon a time there were…bottom of a well.</p>, # <p class="story">...</p>]
Пример сложной функции, которая будет искать теги, окруженные строковыми объектами:
from bs4 import NavigableString def surrounded_by_strings(tag): return (isinstance(tag.next_element, NavigableString) and isinstance(tag.previous_element, NavigableString)) for tag in root.find_all(surrounded_by_strings): print(tag.name) # body # p # a # a # a # p
**kwargs
.Ключевые аргументы **kwargs
предназначен для дополнительной фильтрации по атрибуту тега. Если в метод .find_all()
передать только один аргумент с именем id
, то BeautifulSoup4 будет искать по атрибуту id
каждого тега:
>>> root.find_all(id='link2') # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
Обратите внимание, что аргументы **kwargs
могут принимать значения, такие же, как аргумент name
, а именно: строка, объект регулярного выражения, список значений атрибутов, значение bool
и функция, которая будет принимать значение этого атрибута.
Если передать значение для href
, то BeautifulSoup4 будет искать по атрибуту href
каждого тега:
>>> root.find_all(href=re.compile("elsie")) # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
Следующий пример находит все теги, в которых присутствует атрибут id
, независимо от того, что это за значение:
>>> root.find_all(id=True) # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
Можно искать сразу по нескольким атрибутам одновременно:
>>> root.find_all(href=re.compile("elsie"), id='link1') # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
Если передать функцию для фильтрации по атрибуту, такому как 'href'
, то единственным аргументом, переданным в функцию, будет значение атрибута, а не весь тег.
Пример функции, которая находит все теги <a>
, у которых атрибут 'href'
НЕ соответствует регулярному выражению:
>>> import re >>> def not_lacie(href): ... return href and not re.compile("lacie").search(href) >>> root.find_all(href=not_lacie) # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
Имя class
, является зарезервированным словом в Python. Использование ключевого аргумента class
в методах .find_*()
приведет к синтаксической ошибке. Начиная с BeautifulSoup4.1.2, можно выполнять поиск по классу CSS, используя ключевой аргумент class_
:
Как и с любым **kwargs
аргументом, можно передать в качестве значения class_
- строку, регулярное выражение, функцию или True
:
# использование строки >>> root.find_all('p', class_='title') # [<p class="title"><b>The Dormouse's story</b></p>] >>> import re # использование регулярки >>> root.find_all(class_=re.compile('tit')) # [<p class="title"><b>The Dormouse's story</b></p>] # использование функции >>> def five_characters(css_class): ... return css_class is not None and len(css_class) == 5 >>> root.find_all(class_=five_characters, string='...') [<p class="story">...</p>]
Один атрибут class
может иметь несколько значений. Смотрим нюансы такого поиска:
soup = BeautifulSoup('<p class="one two three"></p>', 'html.parser') soup.find_all('p', class_='one') # [<p class="one two three"></p>] soup.find_all('p', class_='three') # [<p class="one two three"></p>] >>> soup.find_all('p', class_='one two') # [] >>> soup.find_all('p', class_=['one', 'two']) # [<p class="one two three"></p>] # или для старых версий BeautifulSoup4 >>> soup.find_all('p', attrs={'class': ['one', 'three']}) # [<p class="one two three"></p>]
attrs
.Некоторые атрибуты, например data-foo
в HTML <div data-foo="value">foo!</div>
, имеют имена, которые запрещены для использования в качестве имен аргументов в Python. Что бы искать по таким атрибутам, необходимо поместив их в словарь и передать как аргумент attrs
>>> soup = BeautifulSoup('<div data-foo="value">foo!</div>', 'html.parser') >>> soup.find_all(data-foo="value") # SyntaxError: expression cannot contain assignment... >>> soup.find_all(attrs={"data-foo": "value"}) # [<div data-foo="value">foo!</div>]
Так же нельзя использовать в **kwargs
именованный аргумент name
, т.к. BeautifulSoup4 использует его для имени самого тега. Но можно передать атрибут name
вместе с его значением в составе аргумента attrs
:
>>> soup = BeautifulSoup('<input name="email"/>', 'html.parser') >>> soup.find_all(name="email") # [] >>> soup.find_all(attrs={"name": "email"}) # [<input name="email"/>]
string
.Обратите внимание, что до версии BeautifulSoup4.4.0., аргумент string
назывался text
.
С помощью аргумента string
можно искать по тексту HTML-документа вместо тегов. Как и в случае с аргументами name
и **kwargs
, аргумент string
, в качестве значений принимает строку, регулярное выражение, список, функцию или значения True
.
Несколько примеров:
>>> root.find_all(string='Elsie') # ['Elsie'] >>> root.find_all(string=['Tillie', 'Elsie', 'Lacie']) # ['Elsie', 'Lacie', 'Tillie'] >>> root.find_all(string=re.compile(r'Dormouse')) # ["The Dormouse's story", "The Dormouse's story"] >>> def only_parent_string(s): ... """Возвращает значение True, если эта строка `s` ... является единственным дочерним элементом ... родительского тега.""" ... return (s == s.parent.string) >>> root.find_all(string=only_parent_string) # ["The Dormouse's story", # "The Dormouse's story", # 'Elsie', 'Lacie', 'Tillie', '...']
Хотя аргумент string
предназначен для поиска текста в HTML, его можно комбинировать с аргументами, которые находят теги:
>>> root.find_all("a", string="Elsie") # [<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>]
recursive
.Если вызвать element.find_all()
, то BeautifulSoup4 проверит всех потомков mytag
: его дочерние элементы, дочерние элементы дочерних элементов, и так далее. Если необходимо, чтобы BeautifulSoup4 рассматривал только непосредственных потомков (дочерние элементы), можно передать recursive=False
.
Смотрим разницу:
>>> root.html.find_all("title") # [<title>The Dormouse's story</title>] >>> root.html.find_all("title", recursive=False) # []
Вот эта часть HTML:
<html> <head> <title> The Dormouse's story </title> </head> ...
Tag.find_all()
и Tag.find()
- это единственные методы, которые его поддерживают.
limit
.Метод Tag.find()
, это почти то же самое, что и метод Tag.find_all()
с аргументом limit=1
>>> root.find_all('title', limit=1) # [<title>The Dormouse's story</title>] >>> root.find('title') # <title>The Dormouse's story</title>
Разница в том, что .find_all()
возвращает список, содержащий единственный результат, а .find()
возвращает сам результат. Если .find_all()
ничего не находит, то он возвращает пустой список []
. Если .find()
не может ничего найти, то возвращается None
:
>>> print(root.find('notag')) # None
BeautifulSoup4 API определяет десяток других методов для поиска по дереву. Пять из этих методов в целом похожи на Tag.find_all()
, а другие пять в целом похожи на Tag.find()
. Единственное различие в том, по каким частям дерева они ищут.
.find_parents()
и .find_parent()
ищут совпадения в родительских тегах..find_next_siblings()
ищет все совпавшие последующие одноуровневые элементы..find_next_sibling()
ищет первый совпавший последующий одноуровневый элемент..find_previous_siblings
ищет все совпавшие предшествующие одноуровневые элементы..find_previous_sibling
ищет первый совпавший предыдущий одноуровневый элемент..find_all_next()
ищет все совпавшие последующие элементы..find_next()
ищет следующий совпавший элемент..find_all_previous
ищет все совпавшие предшествующие элементы..find_previous()
ищет предыдущий совпавший элемент..find_parents()
:.find_parent()
:Методы .find_parents()
и .find_parent()
ищут теги снизу вверх, просматривая родительские элементы тега или строки. Отличие этих методов состоит в типе возвращаемых методов: список и строка соответственно.
# найдем текст "Lacie", расположенный # внутри тега `<a>` >>> a_text = root.find(string="Lacie") # теперь посмотрим, есть ли у текста # `a_text` родительский тег `<a>`? >>> a_text.parents('a') # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>] # а есть ли родительский тег `<div>`? >>> a_text.find_parents('div') []
По сути, эти методы поиска используют метод Tag.parents
, чтобы перебрать все родительские элементы и проверить каждый из них на соответствие заданному фильтру.
.find_next_siblings()
:.find_next_sibling()
:Методы .find_next_siblings()
и .find_next_sibling()
используют метод Tag.next_siblings
для перебора одноуровневых элементов для данного элемента в дереве. Метод .find_next_siblings()
возвращает все подходящие одноуровневые элементы, а .find_next_sibling()
возвращает только первый из них:
# выберем первый тег `<a>` >>> first_a = root.a >>> first_a # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a> # найдем все последующие одноуровневые теги `<a>` >>> first_a.find_next_siblings('a') # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] # найдем первый `<p>` который имеет `class="story"` >>> first_story = root.find('p', 'story') # найдем все последующие одноуровневые теги `<p>` >>> first_story.find_next_siblings('p') # <p class="story">...</p>
.find_previous_siblings()
:.find_previous_sibling()
:Методы .find_previous_siblings()
и .find_previous_sibling()
используют метод Tag.find_previous_siblings
для перебора тех одноуровневых элементов, которые предшествуют данному элементу в HTML-дереве. Метод .find_previous_siblings()
возвращает все подходящие одноуровневые элементы,, а .find_next_sibling()
только первый из них:
>>> last_a = root.find('a', id='link3') >>> last_a # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a> >>> last_a.find_previous_siblings('a') # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>] >>> first_story = root.find('p', 'story') >>> first_story.find_previous_sibling('p') # <p class="title"><b>The Dormouse's story</b></p>
.find_all_next()
:.find_next()
:Методы .find_all_next()
и .find_next()
используют метод Tag.next_elements
для перебора любых тегов и строк, которые встречаются в документе после элемента. Метод .find_all_next()
возвращает все совпадения, а .find_next()
только первое:
>>> first_a = root.a >>> first_a # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a> >>> first_a.find_all_next(string=True) # ['Elsie', ',\n', 'Lacie', ' and\n', 'Tillie', # ';\nand they lived at the bottom of a well.', '\n', '...', '\n'] >>> first_a.find_next("p") # <p class="story">...</p>
Для этих методов имеет значение только то, что элемент соответствует фильтру и появляется в документе позже, чем тот элемент, с которого начали поиск.
.find_all_previous()
:.find_previous()
:Методы .find_all_previous()
и .find_previous()
используют метод Tag.previous_elements
для перебора любых тегов и строк, которые встречаются в документе до элемента. Метод .find_all_previous()
возвращает все совпадения, а .find_previous()
только первое:
>>> first_a = root.a >>> first_a # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a> >>> first_a.find_all_previous('p') # [<p class="story">Once upon a time there were three little sisters; ...</p>, # <p class="title"><b>The Dormouse's story</b></p>] >>> first_a.find_previous('title') # <title>The Dormouse's story</title>