BeautifulSoup 是一个使用灵活方便、执行速度快、支持多种解析器的网页解析库,可以让你无需编写正则表达式也能从 html 和 xml 中提取数据。BeautifulSoup 不仅支持 Python 内置的 Html 解析器,还支持 lxml、html5lib 等第三方解析器。
以下是对几个主要解析器的对比:
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python 标准库 | BeautifulSoup(markup, "html.parser") | Python的内置标准库 执行速度适中 文档容错能力强 |
Python 2.7.3 or 3.2.2)前的版本中文档容错能力差 |
lxml HTML 解析器 | BeautifulSoup(markup, "lxml") | 速度快 文档容错能力强 |
需要安装C语言库 |
lxml XML 解析器 | BeautifulSoup(markup, ["lxml", "xml"]) BeautifulSoup(markup, "xml") |
速度快 唯一支持XML的解析器 |
需要安装C语言库 |
html5lib | BeautifulSoup(markup, "html5lib") | 最好的容错性 以浏览器的方式解析文档 生成HTML5格式的文档 |
速度慢 不依赖外部扩展 |
安装
BeautifulSoup 安装
我们可以通过 pip 来安装 BeautifulSoup4。
pip install BeautifulSoup4
PyPi 中还有一个名字是 BeautifulSoup,它是 BeautifulSoup3 的发布版本,目前已停止维护,不建议使用该版本。
解析器安装
虽然 BeautifulSoup 支持多种解释器,但是综合来考虑的话还是推荐使用 lxml 解释器,因为 lxml 解释器的效率更高且支持所有的 python 版本,我们可以通过 pip 来安装 lxml 解释器。
pip install lxml
使用
BeautifulSoup 将 HTML 文档转化为一个树形结构,树形结构的每个节点都是一个 python 对象,节点的类型可以分为 Tag、NavigableString、BeautifulSoup 和 Comment 四类。
将 html 文本传入 BeautifulSoup 的构造方法即可得到一个文档对象,通过该对象下每一个节点的数据。
from bs4 import BeautifulSoup
html = "<html>data</html>"
soup = BeautifulSoup(html)
节点的访问
Tag
HTML 中的标签在 BeautifulSoup 中我们称之为 Tag,在 Tag 众多属性中最常用也最重要的属性即 name 和 attribute。
name 顾名思义他是 Tag 的名称,比如 <p class='title'></p> 这段 HTML 中 Tag 的 name 即为 p。
attribute 是 tag 的属性,比如 <p class='title'></p> 这段 HTML 中 Tag 的 class 属性的值即为 title。
attribute 的操作方法与字典相同,我们可以正常对 tag 的属性进行删除、修改等操作。
以下代码展示了 name 和 attribute 的使用方法。
# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup
html = """
<html>
<a class="sister" href="http://example.com/lacie" id="link2"></a>
<p class="story"></p>
</html>
"""
soup = BeautifulSoup(html,features="lxml")
tag1 = soup.a
tag2 = soup.p
print (type(tag1))
print (type(tag2))
print (tag1.name)
print (tag2.name)
print (tag1.attrs)
print (tag1['class'])
tag1['class'] = "brothers"
print (tag1.attrs)
print (tag2.attrs)
print (tag2['class'])
在运行以上代码之前,请先确认安装了 BeautifulSoup 和 lxml 库。以上代码在 python 3.7.0 版本测试,若要在 python 2.7 版本使用请修改 print 部分。
NavigableString
我们可以通过 name 和 attrs 来获取标签的属性等内容,但是在很多情况下我们想要获取的是标签所包含的内容,此时我们就需要使用 string 属性。先来看下以下代码
# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup
html = """<p name="dromouse"><b>The Dormouse's story</b></p>"""
soup = BeautifulSoup(html, features='lxml')
print (soup.p.name)
print (soup.p.string)
print (soup.b.string)
以上代码执行结果如下
p
The Dormouse's story
The Dormouse's story
在这个示例中仅仅通过一行代码 ==soup.p.string== 就获取了标签所包含的字符串,在 Python 爬虫第一篇(urllib+regex) 中使用的正则表达式来获取标签所包含的内容,有兴趣的话可以去看一下。
标签中所包含的字符串无法进行编辑,但是可以使用 replace_with 方法进行替换。
BeautifulSoup
BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag,我们可以分别获取它的类型,名称等属性。
Comment
Comment 是一个特殊的 NavigableString。在 html 文件中不可避免的会出现大量的注释部分,由于使用 string 属性会将注释部分作为正常内容输出,而我们往往不需要注释部分的内容,此时就引入了 Comment 对象,BeautifulSoup 将 html 文档中的注释部分自动设置为 Comment 对象,在使用过程中通过判断 string 的类型是否为 Comment 就可以过滤注释部分的内容。
# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup, element
html = """
<p name="dromouse">The Dormouse's story</p>
<b><!--Hey, buddy. Want to buy a used parser?--></b>
"""
soup = BeautifulSoup(html, features='lxml')
print (soup.p.string)
print (soup.b.string)
print (type(soup.p.string))
print (type(soup.b.string))
if type(soup.p.string) != element.Comment:
print (soup.p.string)
if type(soup.b.string) != element.Comment:
print (soup.b.string)
以上代码执行结果如下
The Dormouse's story
Hey, buddy. Want to buy a used parser?
<class 'bs4.element.NavigableString'>
<class 'bs4.element.Comment'>
The Dormouse's story
文档的遍历
BeautifulSoup 提供了子孙节点、内容属性「.string 属性」、父节点、兄弟节点、前后节点等多种方式来遍历整个文档。
子孙节点
BeautifulSoup 提供了 contents、children 和 descendants 三种属性来操作子孙节点。通过 contents 和 children 可以获取一个 Tag 的直接节点,contents 返回的是一个 list,children 返回的是一个 list 的生成器,可以通过遍历来获取所有内容。descendants 将获取一个 Tag 的说有子节点,以及子节点的子节点「孙节点」。它也是一个生成器,需要通过遍历来获取内容。
# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup
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 class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>and
<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>;
and they lived at the bottom of a well.
</p>
</body>
</html>
"""
soup = BeautifulSoup(html, features='lxml')
print ('---------- contents ----------')
print (soup.contents)
print ('---------- children ----------')
for index, value in enumerate(soup.children):
print ("%4d : %s" %(index, value))
print ('---------- descendants ----------')
for index, value in enumerate(soup.descendants):
print ("%4d : %s" %(index, value))
以上代码演示了 contents、children 和 descendants 属性的使用,执行结果这里就不再贴出来了,有兴趣的或可以自己运行一下获取结果并验证它。
ps: 以上代码均在 python 3.7.0 测试通过。
内容属性
BeautifulSoup 提供了 string、strings 和 stripped_strings 三个属性来获取 Tag 的内容。如果一个 Tag 仅有一个子节点有内容「NavigableString 类型子节点」或其只有一个子节点可以使用 string 属性来获取节点内容。若 Tag 包含多个子节点,且不止一个子节点含有内容,此时需要用到 strings 和 stripped_strings 属性,使用 strings 获取的内容会包含很多的空格和换行,使用 stripped_strings 可以过滤这些空格和换行。
# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup
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 class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>and
<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>;
and they lived at the bottom of a well.
</p>
</body>
</html>
"""
soup = BeautifulSoup(html, features='lxml')
print ('---------- single navigablestring string attributes ----------')
print (soup.title.string)
print (soup.head.string)
print ('---------- multiple navigablestring string attributes ----------')
print (soup.body.string)
print ('---------- multiple navigablestring strings attributes ----------')
for string in soup.body.strings:
print (string)
print ('---------- multiple navigablestring stripped_strings attributes ----------')
for string in soup.body.stripped_strings:
print (string)
以上代码展示了 string、strings 和 stripped_strings 属性的应用,需要注意的是当 Tag 不止一个子节点含有内容时,使用 strings 属性将返回 None。strings 和 stripped_strings 返回的是生成器,需要通过迭代获取内容。
父节点
BeautifulSoup 通过 parent 和 parents 来获取 Tag 的父节点。使用 parent 得到的是 Tag 的直接父节点,而 parents 将得到 Tag 的所有父节点,包括
父节点的父节点。
# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup
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 class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>and
<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>;
and they lived at the bottom of a well.
</p>
</body>
</html>
"""
soup = BeautifulSoup(html, features='lxml')
print ('---------- parent attributes ----------')
print (soup.title.parent.name)
print ('---------- parents attributes ----------')
for parent in soup.title.parents:
print(parent.name)
以上代码的执行结果如下:
---------- parent attributes ----------
head
---------- parents attributes ----------
head
html
[document]
兄弟节点
兄弟节点即和当前节点处在同一级上的节点,BeautifulSoup 通过 next_sibling、previous_sibling、next_siblings 和 previous_siblings 四个属性类获取兄弟节点,next_sibling 和 previous_sibling 属性用来获取上一个兄弟节点和下一个兄弟节点,若节点不存在则返回 None。next_siblings 和 previous_siblings 属性用于对当前节点的兄弟节点机型迭代,通过这两个属性可以获取当前节点的所有兄弟节点。
.next_sibling 和 .previous_sibling 属性通常是字符串或空白,因为空白或者换行也可以被视作一个节点,所以得到的结果可能是空白或者换行。
# -*- coding: utf-8 -*-
from bs4 import BeautifulSoup
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 class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>and
<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>;
and they lived at the bottom of a well.
</p>
</body>
</html>
"""
soup = BeautifulSoup(html, features='lxml')
print ('---------- next_sibling and previous_sibling ----------')
print (repr(soup.p.next_sibling))
print (repr(soup.p.previous_sibling))
print ('---------- next_siblings and previous_siblings ----------')
for sibling in soup.a.next_siblings:
print (repr(sibling))
if soup.a.previous_siblings is not None:
for sibling in soup.a.previous_siblings:
print (repr(sibling))
else:
print ('None')
运行结果如下
---------- next_sibling and previous_sibling ----------
'\n'
'\n'
---------- next_siblings and previous_siblings ----------
',\n '
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
'and\n '
<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>
';\n and they lived at the bottom of a well.\n '
'Once upon a time there were three little sisters; and their names were\n '
前后节点
共有 next_element、previous_element、next_elements 和 next_elements 四个属性来操作前后节点,和兄弟节点不同的是并不是针对同一层级的节点,而是所有节点不分层级。使用方法和兄弟节点类似,这里不再单独举例说明了。
内容的搜索
BeautifulSoup 提供一下方法用于文档内容的搜索:
- find 和 find_all:搜索当前 Tag 及其所有子节点,判断其是否符合过滤条件。
- find_parent 和 find_parents:用来搜索当前 Tag 的父节点,判断其是否符合过滤条件。
- find_next_siblings 和 find_next_sibling:用来搜索当前 Tag 前面的兄弟节点,判断其是否符合过滤条件。
- find_previous_siblings 和 find_previous_sibling:用来搜索当前 Tag 后面的兄弟节点,判断其是否符合过滤条件。
- find_all_next 和 find_next:通过 next_elements 属性对当前 Tag 的之后的节点和字符串进行迭代,并判断其是否符合过滤条件。
- find_all_previous 和 find_previous:通过previous_elements 属性对当前节点前面的节点和字符串进行迭代,并判断其是否符合过滤条件。
以上方法的参数及用法均相同,原理类似,这里以 find_all 方法为例进行介绍,其他方法不再一一举例说明。find_all 方法的定义如下:
find_all( name , attrs , recursive , text , **kwargs )
name参数
name 参数用于查找所有名字为 name 的 Tag,它会自动忽略掉字符串对象。name 参数不仅仅可以传入字符串,也可以传入正则表达式、列表、True「当需要匹配任何值时可以出入 True」、或者方法。
当 name 参数传入方法时,此方法仅接受一个参数「HTML 文档中的一个节点」,当该方法返回 True 时表示当前元素被找到,反之则返回 False。
以下代码简单介绍 name 参数的使用
# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup
import re
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 class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>and
<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>;
and they lived at the bottom of a well.
</p>
</body>
</html>
"""
soup = BeautifulSoup(html, features='lxml')
print ('---------- string ----------')
print (soup.find_all('title'))
print ('---------- regex ----------')
print (soup.find_all(re.compile('^b')))
print ('---------- list ----------')
print (soup.find_all(['b', 'a']))
print ('---------- True ----------')
print (soup.find_all(True))
print ('---------- function ----------')
def has_class_but_no_href(tag):
return tag.has_attr('class') and not tag.has_attr('href')
print (soup.find_all(has_class_but_no_href))
keyword 参数
如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性.
我们可以使用 keyword 参数来搜索指定名字的属性,可使用的参数值包括字符串、正则表达式、列表和 True。
# -*- coding:utf-8 -*-
from bs4 import BeautifulSoup
import re
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 class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>and
<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>;
and they lived at the bottom of a well.
</p>
</body>
</html>
"""
soup = BeautifulSoup(html, features='lxml')
print ('------------------------------')
print (soup.find_all(id='link1'))
print ('------------------------------')
print (soup.find_all(href=re.compile('tillie')))
print ('------------------------------')
print (soup.find_all(id=True))
print ('------------------------------')
print (soup.find_all(id='link2', href=re.compile('tillie')))
运行结果如下
------------------------------
[<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]
------------------------------
[<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>]
------------------------------
[<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="link2">Tillie</a>]
------------------------------
[<a class="sister" href="http://example.com/tillie" id="link2">Tillie</a>]
部分属性在搜索不能使用,比如 HTML5 中的 data-* 属性,此时可以通过 find_all 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的 Tag。
soup.find_all(attrs={"data-foo": "value"})
CSS 选择器
我们在写 CSS 时,标签名不加任何修饰,类名前加点,id名前加 #,在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list,BeautifulSoup 支持了大部分的 CSS 选择器。
# 通过标签名查找
print (soup.select('title'))
# 通过类名查找
print (soup.select('.sister'))
# 通过 id 名查找
print (soup.select('#link1'))
# 组合查找
print (soup.select('p #link1'))
# 属性查找
print (soup.select('a[class="sister"]'))
内容的修改
通过 BeautifulSoup 我们可以对 html 文档内容进行插入、删除、修改等等操作。
Tag 的名称和属性的修改
修改 Tag 的名称直接对 name 属性重新赋值即可,修改属性的使用字典的方式进行重新赋值。
# 修改 Tag 的名称
tag.name = block
# 修改 Tag 的 class 属性值
tag['class'] = 'verybold'
Tag 内容的修改
对 Tag 内容进行修改可以直接对 string 属性进行赋值「此时会覆盖掉原有的内容」,若要在当前内容后追加内容可以使用 append 方法,若需要在指定位置增加内容可以使 insert 方法。
新增节点
在 html 文档中新增节点使用 new_tag 方法,
new_tag = soup.new_tag("a", href="http://www.example.com")
tag.append(new_tag)
其他
若要清除 Tag 内容可以使用 clear 方法。使用 extract 方法 和 decompose 方法可以将当前节点从 html 文档中移除。replace_with 方法用来移除内容并使用新的节点替换被移除的内容。wrap 方法 和 unwrap 方法是一对相反的方法,wrap 对指定的节点进行包装,而 unwrap 对指定的节点进行解包。
BeautifulSoup 的功能较多,在本文中对大部分常用内容进行了解释并提供了示例,不过这仍然不算完全,希望可以帮到大家一点。BeautifulSoup 是一个非常优秀的网页解析库,使用 BeautifulSoup 可以大大节省编程的效率。