Beautiful Soup(BS4)
Beautiful Soup(BS4)
BeautifulSoup
(简称 bs4
)是 Python 用于解析 HTML 和 XML 的强大库,广泛用于网页数据爬取。本文将从 BeautifulSoup
的安装、基本用法到所有操作函数与属性进行分类讲解。
1. 安装与解析器选择
# 安装核心库+常用解析器
pip install beautifulsoup4 lxml html5lib
解析器 | 使用场景 | 示例代码 |
---|---|---|
lxml | 高性能解析标准HTML/XML | BeautifulSoup(html, 'lxml') |
html5lib | 修复极端错误格式(如旧网页) | BeautifulSoup(html, 'html5lib') |
html.parser | 无依赖基础解析 | BeautifulSoup(html, 'html.parser') |
2. Beautiful Soup核心对象与结构
BeautifulSoup
对象:整个文档树的根容器Tag
对象:HTML/XML标签(如<div>
,<a>
)NavigableString
对象:标签内的文本内容(非注释)Comment
对象:HTML注释内容
2.1 BeautifulSoup
对象
BeautifulSoup
对象代表的是整个HTML或XML文档的解析结果,可以视为文档树的根节点。它支持多种方法来遍历和搜索文档结构。
2.1.1 创建 BeautifulSoup
对象
from bs4 import BeautifulSouphtml_doc = '''<html>
<head><title>Example</title>
</head>
<body><p>Hello, World!</p>
</body>
</html>
'''soup = BeautifulSoup(html_doc, "html.parser")
2.1.2 BeautifulSoup
的主要特点
- 可以像
Tag
一样使用各种遍历和搜索方法。 - 其
name
属性为"[document]"
,因为它并不是真正的 HTML/XML 标签。
print(soup.name)
输出:
[document]
2.2 Tag
对象
Tag
对象表示 HTML/XML 标签,是 Beautiful Soup 最常用的对象类型。可以通过 name
获取标签名,使用 attrs
访问标签属性。
2.2.1 获取 Tag
对象
soup = BeautifulSoup('<b class="boldest">粗体文本</b>', "html.parser")
tag = soup.bprint(tag)
print(tag.name)
输出:
<b class="boldest">粗体文本</b>
b
2.2.2 修改 Tag
名称
tag.name = "strong"
print(tag)
输出:
<strong class="boldest">粗体文本</strong>
2.2.3 操作 Tag
属性
print(tag['class']) # 输出:['boldest']tag['id'] = "text1"
print(tag)
输出:
['boldest']
<strong class="boldest" id="text1">粗体文本</strong>
2.2.4 删除属性
del tag['class']
print(tag)
输出:
<strong id="text1">粗体文本</strong>
2.3 NavigableString
对象
NavigableString
表示标签内部的文本内容,它是 Unicode
字符串的子类。
2.3.1 获取 NavigableString
对象
soup = BeautifulSoup("<p>Hello, World!</p>", "html.parser")
text = soup.p.string
print(text)
print(type(text))
输出:
Hello, World!
<class 'bs4.element.NavigableString'>
2.3.2 替换 NavigableString
对象
soup.p.string.replace_with("新文本")
print(soup.p)
输出:
<p>新文本</p>
2.4 Comment
对象
Comment
是 NavigableString
的子类,专门用于存储 HTML 注释。
2.4.1 获取 Comment
对象
markup = "<b><!--这是一行注释--></b>"
soup = BeautifulSoup(markup, "html.parser")
comment = soup.b.string
print(comment)
print(type(comment))
输出:
这是一行注释
<class 'bs4.element.Comment'>
2.4.2 特殊格式化输出
print(soup.b.prettify())
输出:
<b><!--这是一行注释--> </b>
3. 文档树遍历
示例 HTML 文档:
html_doc = """<html>
<head><title>网页标题</title>
</head>
<body><p class="title"><b>第一段文本</b></p><p class="story">第二段段首<a href="http://example.com/a" class="friend" id="link1">链接1</a><a href="http://example.com/b" class="friend" id="link2">链接2</a><a href="http://example.com/c" class="friend" id="link3">链接3</a>第二段段尾</p><p class="story">...</p></body>
</html>
"""from bs4 import BeautifulSoupsoup = BeautifulSoup(html_doc, 'html.parser')
3.1 子节点操作
BeautifulSoup
提供了多种方法来访问 HTML 文档中某个标签的子节点。
3.1.1 通过标签名称获取
获取文档中的标签非常简单,可以通过标签名称直接访问:
soup.head # 获取 <head> 标签
输出:
<head> <title>网页标题</title> </head>
soup.title # 获取 <title> 标签
输出:
<title>网页标题</title>
如果要获取 <body>
标签中的第一个 <b>
标签:
soup.body.b # 获取 <body> 标签中的第一个 <b> 标签
输出:
<b>第一段文本</b>
3.1.2 获取多个标签
如果需要获取所有的 <a>
标签,可以使用 find_all
:
soup.find_all('a')
输出:
[<a class="friend" href="http://example.com/a" id="link1">链接1</a>,<a class="friend" href="http://example.com/b" id="link2">链接2</a>,<a class="friend" href="http://example.com/c" id="link3">链接3</a>]
3.1.3 返回所有子节点
tag.contents
返回标签的所有子节点(包括字符串和其他标签),它是一个列表。
head_tag = soup.headhead_tag.contents # 返回 <head> 标签的所有子节点
输出:
['\n', <title>网页标题</title>, '\n']
如果希望遍历子节点,可以使用 .children
:
for child in head_tag.children:print(child)
输出:
<title>网页标题</title>
3.1.4 递归所有子孙节点
.descendants
递归地获取标签的所有子孙节点:
for child in head_tag.descendants:print(child)
输出:
<title>网页标题</title> 网页标题
3.1.5 返回标签中字符串
如果一个标签只有一个子节点(如字符串),可以直接使用 .string
:
title_tag = soup.titletitle_tag.string
输出:
'网页标题'
如果标签内有多个字符串,可以使用 .strings
来遍历:
for string in soup.strings:print(repr(string))
输出:
'\n' '\n' '网页标题' '\n' '\n' '\n' '\n' '第一段文本' '\n' '\n' '\n 第二段段首\n ' '链接1' '\n' '链接2' '\n' '链接3' '\n 第二段段尾\n ' '\n' '...' '\n' '\n' '\n'
.stripped_strings
会去除多余的空白字符:
for string in soup.stripped_strings:print(repr(string))
输出:
'网页标题' '第一段文本' '第二段段首' '链接1' '链接2' '链接3' '第二段段尾' '...'
3.2 父节点操作
3.2.1 返回直接父节点
通过 .parent
可以获取某个元素的直接父节点:
title_tag = soup.title
title_tag.parent # 获取 <title> 标签的父节点 <head> 标签
输出:
<head> <title>网页标题</title> </head>
3.2.2 递归所有父节点
.parents
可以递归地获取元素的所有父节点:
link = soup.a
for parent in link.parents:print(parent.name)
输出:
p body html [document]
3.3 兄弟节点操作
3.3.1 获取相邻节点
.next_sibling
获取当前节点的下一个兄弟节点:
sibling_soup = BeautifulSoup("""<a><b>text1</b><c>text2</c>
</a>""")sibling_soup.b.next_sibling
输出:
<c>text2</c>
.previous_sibling
获取上一个兄弟节点:
sibling_soup.c.previous_sibling # 获取 <c> 标签的上一个兄弟节点 <b> 标签
输出:
<b>text1</b>
3.3.2 返回所有之前/之后节点
.next_siblings
返回当前节点的所有下一个兄弟节点:
# 遍历第一个超链接之后所有同级节点
for sibling in soup.select('a')[0].next_siblings:print(repr(sibling))
输出:
'\n' <a class="friend" href="http://example.com/b" id="link2">链接2</a> '\n' <a class="friend" href="http://example.com/c" id="link3">链接3</a> '\n 第二段段尾\n '
.previous_siblings
返回当前节点的所有上一个兄弟节点:
# 遍历最后一个超链接之前所有同级节点
for sibling in soup.select('a')[-1].previous_siblings:print(repr(sibling))
输出:
'\n' <a class="friend" href="http://example.com/b" id="link2">链接2</a> '\n' <a class="friend" href="http://example.com/a" id="link1">链接1</a> '\n 第二段段首\n '
3.4 回退和前进
3.4.1 获取相邻对象
last_a_tag = soup.find("a", id="link3")
last_a_tag
输出:
<a class="friend" href="http://example.com/c" id="link3">链接3</a>
.next_element
获取当前节点解析后的下一个对象:
last_a_tag.next_element
输出:
'链接3'
.previous_element
获取前一个对象:
last_a_tag.previous_element
输出:
'\n'
3.4.2 迭代获取上下解析内容
.next_elements
用于迭代获取文档中的下一个解析内容:
for element in last_a_tag.next_elements:print(repr(element))
输出:
'链接3' '\n 第二段段尾\n ' '\n' <p class="story">...</p> '...' '\n' '\n' '\n'
.previous_elements
用于迭代获取文档中的上一个解析内容:
for element in last_a_tag.previous_elements:print(repr(element))
输出:
'\n' '链接2' <a class="friend" href="http://example.com/b" id="link2">链接2</a> '\n' '链接1' <a class="friend" href="http://example.com/a" id="link1">链接1</a> '\n 第二段段首\n ' <p class="story">第二段段首<a class="friend" href="http://example.com/a" id="link1">链接1</a> <a class="friend" href="http://example.com/b" id="link2">链接2</a> <a class="friend" href="http://example.com/c" id="link3">链接3</a>第二段段尾</p> '\n' '\n' '第一段文本' <b>第一段文本</b> '\n' <p class="title"> <b>第一段文本</b> </p> '\n' <body> <p class="title"> <b>第一段文本</b> </p> <p class="story">第二段段首<a class="friend" href="http://example.com/a" id="link1">链接1</a> <a class="friend" href="http://example.com/b" id="link2">链接2</a> <a class="friend" href="http://example.com/c" id="link3">链接3</a>第二段段尾</p> <p class="story">...</p> </body> '\n' '\n' '网页标题' <title>网页标题</title> '\n' <head> <title>网页标题</title> </head> '\n' <html> <head> <title>网页标题</title> </head> <body> <p class="title"> <b>第一段文本</b> </p> <p class="story">第二段段首<a class="friend" href="http://example.com/a" id="link1">链接1</a> <a class="friend" href="http://example.com/b" id="link2">链接2</a> <a class="friend" href="http://example.com/c" id="link3">链接3</a>第二段段尾</p> <p class="story">...</p> </body> </html>
4. 文档树搜索
Beautiful Soup 提供了多种方法来搜索 HTML 文档中的元素,最常用的两种方法是 find()
和 find_all()
。接下来将介绍这两种方法的用法以及如何通过过滤器来精确定位元素。
4.1 find()
与 find_all()
这两个方法用于查找匹配特定条件的标签。
find()
:返回第一个符合条件的标签。
# 使用 find() 查找第一个 <a> 标签
soup.find("a")
输出:
<a class="friend" href="http://example.com/a" id="link1">链接1</a>
find_all()
:返回所有符合条件的标签列表。
# 使用 find_all() 查找所有 <a> 标签
soup.find_all("a")
输出:
[<a class="friend" href="http://example.com/a" id="link1">链接1</a>,<a class="friend" href="http://example.com/b" id="link2">链接2</a>,<a class="friend" href="http://example.com/c" id="link3">链接3</a>]
4.2 过滤器类型
在 find()
和 find_all()
方法中,可以使用多种过滤器来精确筛选标签:
- 字符串:直接查找与字符串完全匹配的标签。
- 正则表达式:通过正则表达式匹配标签名称。
- 列表:查找多个标签名称中的任意一个标签。
- True:匹配所有标签。
- 方法:通过自定义方法筛选标签。
4.2.1 字符串
# 查找所有 <b> 标签
soup.find_all("b")
输出:
[<b>第一段文本</b>]
4.2.2 正则表达式
import re
# 查找以字母 'b' 开头的标签
for tag in soup.find_all(re.compile("^b")):print(tag.name)
输出:
body b
4.2.3 列表
# 查找 <a> 和 <b> 标签
soup.find_all(["a", "b"])
输出:
[<b>第一段文本</b>,<a class="friend" href="http://example.com/a" id="link1">链接1</a>,<a class="friend" href="http://example.com/b" id="link2">链接2</a>,<a class="friend" href="http://example.com/c" id="link3">链接3</a>]
4.2.4 True
# 查找所有的标签,但不返回字符串节点
for tag in soup.find_all(True):print(tag.name)
输出:
html head title body p b p a a a p
4.2.5 方法
自定义方法来过滤标签,例如查找所有含有 class
属性但不含 id
属性的标签:
def has_class_but_no_id(tag):return tag.has_attr('class') and not tag.has_attr('id')soup.find_all(has_class_but_no_id)
输出:
[<p class="title"><b>第一段文本</b></p>,<p class="story">第二段段首<a class="friend" href="http://example.com/a" id="link1">链接1</a><a class="friend" href="http://example.com/b" id="link2">链接2</a><a class="friend" href="http://example.com/c" id="link3">链接3</a>第二段段尾</p>,<p class="story">...</p>]
4.3 搜索参数详细说明
4.3.1 name
参数
name
用来查找特定名称的标签,可以是字符串、正则表达式、列表等。
# 查找所有 <title> 标签
soup.find_all("title")
输出:
[<title>网页标题</title>]
4.3.2 attrs
参数
- 用于根据标签的属性值进行过滤。
# 查找具有特定 id 的标签
soup.find_all(id="link2")
输出:
[<a class="friend" href="http://example.com/b" id="link2">链接2</a>]
4.3.3 string
参数
- 用于查找包含特定字符串的标签。
# 查找文本内容为 "链接3" 的标签
soup.find_all(string="链接3")
输出:
['链接3']
4.3.4 limit
参数
- 限制返回结果的数量,默认情况下返回所有符合条件的标签。
# 只返回前两个 <a> 标签
soup.find_all("a", limit=2)
输出:
[<a class="friend" href="http://example.com/a" id="link1">链接1</a>,<a class="friend" href="http://example.com/b" id="link2">链接2</a>]
4.3.5 recursive
参数
- 默认为
True
,表示查找所有子孙节点。
# 查找所有子节点
soup.html.find_all("title")
输出:
[<title>网页标题</title>]
- 如果只想查找直接子节点,可以将其设置为
False
。
# 只查找直接子节点
soup.html.find_all("title", recursive=False)
输出:
[]
4.4 使用 CSS 类名进行查找
- 使用
class_
参数来查找具有指定 CSS 类名的标签。由于class
是 Python 保留字,因此需要使用class_
。
# 查找具有 class="sister" 的所有 <a> 标签
soup.find_all("a", class_="friend")
输出:
[<a class="friend" href="http://example.com/a" id="link1">链接1</a>,<a class="friend" href="http://example.com/b" id="link2">链接2</a>,<a class="friend" href="http://example.com/c" id="link3">链接3</a>]
4.5 嵌套使用 string
和其他参数
可以将 string
参数与其他参数结合使用,例如查找某个标签中包含特定文本的标签。
# 查找文本为 "Elsie" 的 <a> 标签
soup.find_all("a", string="链接2")
输出:
[<a class="friend" href="http://example.com/b" id="link2">链接2</a>]
4.6 find()
与 find_all()
简写方式
find()
和find_all()
可以简写为标签对象的调用方式,效果相同。
# 等价于 soup.find_all("a")
soup("a")
输出:
[<a class="friend" href="http://example.com/a" id="link1">链接1</a>,<a class="friend" href="http://example.com/b" id="link2">链接2</a>,<a class="friend" href="http://example.com/c" id="link3">链接3</a>]
# 等价于 soup.find("a")
soup("a")[0]
输出:
<a class="friend" href="http://example.com/a" id="link1">链接1</a>
5. 文档树修改
Beautiful Soup 的强项在于文档树的搜索,但它也提供了非常便捷的修改文档树的方法。
5.1 修改标签的名称和属性
通过直接修改标签的 name
属性或使用 []
操作符,可以改变标签的名称、属性值,或者添加/删除属性。
5.1.1 修改标签名称和属性值
soup = BeautifulSoup('<b class="boldest">加粗文本</b>')
tag = soup.b
tag.name = "blockquote" # 修改标签名称
tag['class'] = 'verybold' # 修改属性值
tag['id'] = 1 # 添加新属性print(tag)
输出:
<blockquote class="verybold" id="1">加粗文本</blockquote>
5.1.2 删除标签的属性
del tag['class'] # 删除class属性
del tag['id'] # 删除id属性print(tag)
输出:
<blockquote>加粗文本</blockquote>
5.2 修改文本内容
通过 .string
属性,可以修改标签内的文本内容。注意,如果标签内包含子标签,修改 .string
会覆盖原有内容。
markup = '<a href="http://example.com/">跳转到<i>example.com</i></a>'
soup = BeautifulSoup(markup)
tag = soup.a
tag.string = "新的链接" # 替换标签内容print(tag)
输出:
<a href="http://example.com/">新的链接</a>
5.3 添加内容
5.3.1 append()
方法
append()
方法可以向标签内部添加内容。相当于 Python 列表中的 append()
,该方法将内容添加到标签的末尾。
soup = BeautifulSoup("<a>Foo</a>")
soup.a.append("Bar") # 在标签内部添加内容print(soup.a)
输出:
<a>FooBar</a>
5.3.2 NavigableString
NavigableString
可以用于向标签内添加文本内容。
from bs4 import NavigableStringsoup = BeautifulSoup("<b></b>")
tag = soup.b
tag.append("Hello")
new_string = NavigableString(" there")
tag.append(new_string) # 使用 NavigableString 添加文本print(tag)
输出:
<b>Hello there</b>
5.3.3 new_tag()
方法
new_tag()
方法可以用来创建新的标签,并将其插入到文档中。
new_tag = soup.new_tag("a", href="http://www.example.com")
tag.append(new_tag)
new_tag.string = "新文本"print(tag)
输出:
<b>Hello there<a href="http://www.example.com">新文本</a></b>
5.4 插入内容
5.4.1 insert()
方法
insert()
方法用于将元素插入到指定的位置,而不是总是添加到最后。此方法接收一个索引,表示元素插入的位置。
markup = '<a href="http://example.com/">我链接到<i>example.com</i></a>'
soup = BeautifulSoup(markup)
tag = soup.a
tag.insert(1, "但未背书") # 插入到指定位置print(tag)
输出:
<a href="http://example.com/">我链接到但未背书<i>example.com</i></a>
5.4.2 insert_before()
方法
insert_before()
在当前标签或文本节点之前插入内容。
soup = BeautifulSoup("<b>stop</b>")
tag = soup.new_tag("i")
tag.string = "Don't"
soup.b.string.insert_before(tag) # 在b标签前插入内容print(soup.b)
输出:
<b><i>Don't</i>stop</b>
5.4.3 insert_after()
方法
insert_after()
在当前标签或文本节点之后插入内容。
soup.b.i.insert_after(soup.new_string(" ever ")) # 在i标签后插入内容
输出:
<b><i>Don't</i> ever stop</b>
5.5 移除内容
5.5.1 clear()
方法
clear()
方法移除当前标签内的所有内容,但保留标签本身。
markup = '<a href="http://example.com/">链接到 <i>example.com</i></a>'
soup = BeautifulSoup(markup)
tag = soup.a
tag.clear() # 清空标签内容print(tag)
输出:
<a href="http://example.com/"></a>
5.5.2 extract()
方法
extract()
方法将标签从文档树中移除,并返回被移除的标签。这使得被移除的内容可以进一步处理或操作。
markup = '<a href="http://example.com/">链接到 <i>example.com</i></a>'
soup = BeautifulSoup(markup)
i_tag = soup.i.extract() # 移除并返回i标签print(soup)
print(i_tag)
输出:
<html><body><a href="http://example.com/">I linked to </a></body></html>
<i>example.com</i>
5.5.3 decompose()
方法
decompose()
方法与 extract()
类似,但它不仅移除标签,还销毁该标签,从而完全删除该元素。
markup = '<a href="http://example.com/">链接到 <i>example.com</i></a>'
soup = BeautifulSoup(markup)
soup.i.decompose() # 从文档树中移除并销毁i标签print(soup.a)
输出:
<a href="http://example.com/">链接到 </a>
5.5.4 replace_with()
方法
replace_with()
方法会移除标签中的某个内容,并用新的标签或文本节点替代它。
markup = '<a href="http://example.com/">链接到 <i>example.com</i></a>'
soup = BeautifulSoup(markup)
new_tag = soup.new_tag("b")
new_tag.string = "example.net"
soup.a.i.replace_with(new_tag) # 用新标签替换print(soup.a)
输出:
<a href="http://example.com/">链接到 <b>example.net</b></a>
5.6 包装与解包装
5.6.1 wrap()
方法
wrap()
方法将指定的标签包裹起来,返回包裹后的标签。这通常用于将现有元素放入一个新的标签中。
soup = BeautifulSoup("<p>这是一句话。</p>")
soup.p.string.wrap(soup.new_tag("b")) # 将p标签中的内容包裹在b标签中print(soup.p)
输出:
<p><b>这是一句话。</b></p>
5.6.2 unwrap()
方法
unwrap()
方法的作用是将标签解包,即移除标签本身,保留标签内的内容。该方法常用于去除无关标签,但保留其文本内容。
soup.p.b.unwrap() # 解包i标签print(soup.p)
输出:
<p>这是一句话。</p>
6. 输出处理
6.1 格式化输出
prettify()
方法用于格式化 Beautiful Soup 解析后的 HTML/XML 文档,使其更加易读。该方法会为每个标签独占一行,返回 Unicode 编码的字符串。
from bs4 import BeautifulSoupmarkup = '<a href="http://example.com/">跳转到 <i>example.com</i></a>'
soup = BeautifulSoup(markup)print(soup.prettify())
输出:
<html><body><a href="http://example.com/">跳转到<i>example.com</i></a></body> </html>
prettify()
也可用于某个特定标签,如:
print(soup.a.prettify())
输出:
<a href="http://example.com/">跳转到<i>example.com</i> </a>
6.2 压缩输出
如果不需要格式化输出,只想获取 HTML 字符串,可以使用 str()
方法:
str(soup)
输出:
'<html><body><a href="http://example.com/">跳转到 <i>example.com</i></a></body></html>'
此外,可以使用 encode()
获取字节码,或 decode()
获得 Unicode 字符串。
6.3 获取纯文本内容
get_text()
方法可以提取标签中的所有文本内容,包括子标签中的文本,并返回 Unicode 字符串。
markup = '<a href="http://example.com/">\n跳转到 <i>example.com</i>\n</a>'
soup = BeautifulSoup(markup, 'html.parser')print(soup.get_text())
输出:
跳转到 example.com
可以指定分隔符:
soup.get_text("|")
输出:
'\n跳转到 |example.com|\n'
指定去除前后空白:
soup.get_text("|", strip=True)
输出:
'跳转到|example.com'
还可以使用 .stripped_strings
生成器获取去除空白的文本列表:
[text for text in soup.stripped_strings]
输出:
['跳转到', 'example.com']