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

Методы .find_all() и .find*() модуля BeautifulSoup4 в Python

Все виды поиска по HTML-документу в BeautifulSoup4

Внимание!!! Важно для понимания как работают методы поиска модуля 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-тега, может быть:
    • строкой с именем HTML-тега,
    • списком с несколькими именами 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.

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>]

Поиск по классу CSS.

Имя 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_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>