В последнем уроке вы изучили основы библиотеки Beautiful Soup . Помимо навигации по дереву DOM, вы также можете искать элементы с заданным class
или id
. Вы также можете изменить дерево DOM, используя эту библиотеку.
В этом уроке вы узнаете о различных методах, которые помогут вам с поиском и модификациями. Мы будем чистить ту же страницу Википедии о Python из нашего последнего урока.
Фильтры для поиска по дереву
В Beautiful Soup есть много методов поиска по дереву DOM. Эти методы очень похожи и используют те же фильтры, что и аргументы. Поэтому имеет смысл правильно понять различные фильтры, прежде чем читать о методах. Я буду использовать тот же find_all()
чтобы объяснить разницу между разными фильтрами.
Простейший фильтр, который вы можете передать любому методу поиска, — это строка. Затем Beautiful Soup будет искать в документе тег, который точно соответствует строке.
01
02
03
04
05
06
07
08
09
10
|
for heading in soup.find_all(‘h2’):
print(heading.text)
# Contents
# History[edit]
# Features and philosophy[edit]
# Syntax and semantics[edit]
# Libraries[edit]
# Development environments[edit]
# … and so on.
|
Вы также можете передать объект регулярного выражения в метод find_all()
. На этот раз Beautiful Soup отфильтрует дерево, сопоставив все теги с данным регулярным выражением .
01
02
03
04
05
06
07
08
09
10
11
12
13
|
import re
for heading in soup.find_all(re.compile(«^h[1-6]»)):
print(heading.name + ‘ ‘ + heading.text.strip())
# h1 Python (programming language)
# h2 Contents
# h2 History[edit]
# h2 Features and philosophy[edit]
# h2 Syntax and semantics[edit]
# h3 Indentation[edit]
# h3 Statements and control flow[edit]
# … an so on.
|
Код будет искать все теги, которые начинаются с «h» и сопровождаются цифрой от 1 до 6. Другими словами, он будет искать все теги заголовков в документе.
Вместо использования регулярных выражений вы можете достичь того же результата, передав список всех тегов, которые нужно, чтобы Beautiful Soup соответствовал документу.
1
2
|
for heading in soup.find_all([«h1», «h2», «h3», «h4», «h5», «h6»]):
print(heading.name + ‘ ‘ + heading.text.strip())
|
Вы также можете передать True
в качестве параметра find_all()
. Затем код вернет все теги в документе. Вывод ниже означает, что в данный момент на странице Википедии есть 4,339 тегов, которые мы анализируем.
1
2
|
len(soup.find_all(True))
# 4339
|
Если вы все еще не можете найти то, что ищете, с помощью любого из приведенных выше фильтров, вы можете определить свою собственную функцию, которая принимает элемент в качестве единственного аргумента. Функция также должна возвращать True
если есть совпадение, и False
противном случае. В зависимости от того, что вам нужно, вы можете сделать функцию настолько сложной, насколько это необходимо для выполнения работы. Вот очень простой пример:
1
2
3
4
5
|
def big_lists(tag):
return len(tag.contents) > 20 and tag.name == ‘ul’
len(soup.find_all(big_lists))
# 13
|
Вышеупомянутая функция просматривает ту же страницу Python из Википедии и ищет неупорядоченные списки, которые имеют более 20 дочерних элементов.
Поиск в дереве DOM с использованием встроенных функций
Один из самых популярных методов поиска в DOM — find_all()
. Он пройдет через всех потомков тега и вернет список всех потомков, соответствующих вашим критериям поиска. Этот метод имеет следующую подпись:
1
|
find_all(name, attrs, recursive, string, limit, **kwargs)
|
Аргумент name
— это имя тега, который вы хотите, чтобы эта функция искала при прохождении по дереву. Вы можете предоставить строку, список, регулярное выражение, функцию или значение True
в качестве имени.
Вы также можете фильтровать элементы в дереве DOM на основе различных атрибутов, таких как id
, href
и т. Д. Вы также можете получить все элементы с определенным атрибутом независимо от его значения, используя attribute=True
. Поиск элементов с определенным классом отличается от поиска обычных атрибутов. Поскольку class
является зарезервированным ключевым словом в Python, вам придется использовать class_
ключевого слова class_
при поиске элементов с определенным классом.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
import re
len(soup.find_all(id=True))
# 425
len(soup.find_all(class_=True))
# 1734
len(soup.find_all(class_=»mw-headline»))
# 20
len(soup.find_all(href=True))
# 1410
len(soup.find_all(href=re.compile(«python»)))
# 102
|
Вы можете видеть, что документ содержит 1734 тега с атрибутом class
и 425 тегов с атрибутом id
. Если вам нужны только первые несколько из этих результатов, вы можете передать в метод число в качестве значения limit
. Передача этого значения заставит Beautiful Soup прекратить поиск дополнительных элементов, как только он достигнет определенного числа. Вот пример:
1
2
3
4
5
6
|
soup.find_all(class_=»mw-headline», limit=4)
# <span class=»mw-headline» id=»History»>History
# <span class=»mw-headline» id=»Features_and_philosophy»>Features and philosophy
# <span class=»mw-headline» id=»Syntax_and_semantics»>Syntax and semantics
# <span class=»mw-headline» id=»Indentation»>Indentation
|
Когда вы используете метод find_all()
, вы говорите Beautiful Soup пройти через всех потомков данного тега, чтобы найти то, что вы ищете. Иногда вам нужно искать элемент только в прямых дочерних элементах тега. Это может быть достигнуто путем передачи recursive=False
find_all()
.
1
2
3
4
5
6
7
8
|
len(soup.html.find_all(«meta»))
# 6
len(soup.html.find_all(«meta», recursive=False))
# 0
len(soup.head.find_all(«meta», recursive=False))
# 6
|
Если вы заинтересованы в поиске только одного результата для определенного поискового запроса, вы можете использовать метод find()
чтобы найти его, вместо передачи limit=1
для find_all()
. Единственная разница между результатами, возвращаемыми этими двумя методами, состоит в том, что find_all()
возвращает список только с одним элементом, а find()
просто возвращает результат.
1
2
3
4
5
|
soup.find_all(«h2», limit=1)
# [<h2>Contents</h2>]
soup.find(«h2»)
# <h2>Contents</h2>
|
Методы find()
и find_all()
поиск по всем потомкам данного тега для поиска элемента. Есть десять других очень похожих методов, которые вы можете использовать для перебора дерева DOM в разных направлениях.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
find_parents(name, attrs, string, limit, **kwargs)
find_parent(name, attrs, string, **kwargs)
find_next_siblings(name, attrs, string, limit, **kwargs)
find_next_sibling(name, attrs, string, **kwargs)
find_previous_siblings(name, attrs, string, limit, **kwargs)
find_previous_sibling(name, attrs, string, **kwargs)
find_all_next(name, attrs, string, limit, **kwargs)
find_next(name, attrs, string, **kwargs)
find_all_previous(name, attrs, string, limit, **kwargs)
find_previous(name, attrs, string, **kwargs)
|
find_parent()
и find_parents()
дерево DOM, чтобы найти данный элемент. find_next_sibling()
и find_next_siblings()
будут перебирать все элементы одного и того же элемента, которые идут после текущего. Точно так же find_previous_sibling()
и find_previous_siblings()
будут перебирать все элементы одного и того же элемента, которые находятся перед текущим.
find_next()
и find_all_next()
будут перебирать все теги и строки, которые идут после текущего элемента. Точно так же find_previous()
и find_all_previous()
будут перебирать все теги и строки, предшествующие текущему элементу.
Вы также можете искать элементы с помощью селекторов CSS с помощью метода select()
. Вот несколько примеров:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
len(soup.select(«pa»))
# 411
len(soup.select(«p > a»))
# 291
soup.select(«h2:nth-of-type(1)»)
# [<h2>Contents</h2>]
len(soup.select(«p > a:nth-of-type(2)»))
# 46
len(soup.select(«p > a:nth-of-type(10)»))
# 6
len(soup.select(«[class*=section]»))
# 80
len(soup.select(«[class$=section]»))
# 20
|
Модификация Дерева
Вы можете не только искать в дереве DOM, чтобы найти элемент, но и изменять его. Очень легко переименовать тег и изменить его атрибуты.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
heading_tag = soup.select(«h2:nth-of-type(2)»)[0]
heading_tag.name = «h3»
print(heading_tag)
# <h3><span class=»mw-headline» id=»Features_and_philosophy»>Feat…
heading_tag[‘class’] = ‘headingChanged’
print(heading_tag)
# <h3 class=»headingChanged»><span class=»mw-headline» id=»Feat…
heading_tag[‘id’] = ‘newHeadingId’
print(heading_tag)
# <h3 class=»headingChanged» id=»newHeadingId»><span class=»mw….
del heading_tag[‘id’]
print(heading_tag)
# <h3 class=»headingChanged»><span class=»mw-headline»…
|
Продолжая наш последний пример, вы можете заменить содержимое тега на заданную строку, используя атрибут .string
. Если вы не хотите заменять содержимое, но добавляете что-то лишнее в конце тега, вы можете использовать метод append()
.
Точно так же, если вы хотите вставить что-то внутри тега в определенном месте, вы можете использовать метод insert()
. Первый параметр для этого метода — это позиция или индекс, в который вы хотите вставить контент, а второй параметр — это сам контент. Вы можете удалить все содержимое внутри тега, используя метод clear()
. Это просто оставит вас с самим тегом и его атрибутами.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
heading_tag.string = «Features and Philosophy»
print(heading_tag)
# <h3 class=»headingChanged»>Features and Philosophy</h3>
heading_tag.append(» [Appended This Part].»)
print(heading_tag)
# <h3 class=»headingChanged»>Features and Philosophy [Appended This Part].</h3>
print(heading_tag.contents)
# [‘Features and Philosophy’, ‘ [Appended This Part].’]
heading_tag.insert(1, ‘ Inserted this part ‘)
print(heading_tag)
# <h3 class=»headingChanged»>Features and Philosophy Inserted this part [Appended This Part].</h3>
heading_tag.clear()
print(heading_tag)
# <h3 class=»headingChanged»></h3>
|
В начале этого раздела вы выбрали заголовок второго уровня из документа и изменили его на заголовок третьего уровня. Повторное использование того же селектора покажет вам заголовок следующего второго уровня, который следует за оригиналом. Это имеет смысл, поскольку исходный заголовок больше не является заголовком второго уровня.
Исходный заголовок теперь можно выбрать с помощью h3:nth-of-type(2)
. Если вы полностью хотите удалить элемент или тег и все содержимое внутри него из дерева, вы можете использовать метод decompose()
.
1
2
3
4
5
6
7
8
9
|
soup.select(«h3:nth-of-type(2)»)[0]
# <h3 class=»headingChanged»></h3>
soup.select(«h3:nth-of-type(3)»)[0]
# <h3><span class=»mw-headline» id=»Indentation»>Indentation
soup.select(«h3:nth-of-type(2)»)[0].decompose()
soup.select(«h3:nth-of-type(2)»)[0]
# <h3><span class=»mw-headline» id=»Indentation»>Indentation
|
Как только вы разложили или удалили исходный заголовок, его место займет третье место.
Если вы хотите удалить тег и его содержимое из дерева, но не хотите полностью уничтожить тег, вы можете использовать метод extract()
. Этот метод вернет извлеченный тег. Теперь у вас будет два разных дерева, которые вы сможете разобрать. Корнем нового дерева будет тег, который вы только что извлекли.
1
2
3
4
|
heading_tree = soup.select(«h3:nth-of-type(2)»)[0].extract()
len(heading_tree.contents)
# 2
|
Вы также можете заменить тег внутри дерева чем-то другим по вашему выбору, используя метод replace_with()
. Этот метод вернет тег или строку, которые он заменил. Это может быть полезно, если вы хотите поместить замененное содержимое в другое место документа.
01
02
03
04
05
06
07
08
09
10
11
12
|
soup.h1
# <h1 class=»firstHeading»>Python (programming language)</h1>
bold_tag = soup.new_tag(«b»)
bold_tag.string = «Python»
soup.h1.replace_with(bold_tag)
print(soup.h1)
# None
print(soup.b)
# <b>Python</b>
|
В приведенном выше коде основной заголовок документа был заменен тегом b
. В документе больше нет тега h1
, и поэтому print(soup.h1)
теперь печатает None
.
Последние мысли
После прочтения двух руководств в этой серии вы сможете проанализировать разные веб-страницы и извлечь важные данные из документа. Вы также должны иметь возможность восстановить исходную веб-страницу, изменить ее в соответствии со своими потребностями и сохранить измененную версию локально.
Если у вас есть какие-либо вопросы относительно этого урока, пожалуйста, дайте мне знать в комментариях.