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格式的文档 | 速度慢 不依赖外部扩展 |
我们可以通过 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)
HTML 中的标签在 BeautifulSoup 中我们称之为 Tag,在 Tag 众多属性中最常用也最重要的属性即 name 和 attribute。
name 顾名思义他是 Tag 的名称,比如 <pclass='title'></p>
这段 HTML 中 Tag 的 name 即为 p。
attribute 是 tag 的属性,比如 <pclass='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 部分。
我们可以通过 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 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag,我们可以分别获取它的类型,名称等属性。
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 和 strippedstrings 三个属性来获取 Tag 的内容。如果一个 Tag 仅有一个子节点有内容「NavigableString 类型子节点」或其只有一个子节点可以使用 string 属性来获取节点内容。若 Tag 包含多个子节点,且不止一个子节点含有内容,此时需要用到 strings 和 strippedstrings 属性,使用 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 和 strippedstrings 属性的应用,需要注意的是当 Tag 不止一个子节点含有内容时,使用 strings 属性将返回 None。strings 和 strippedstrings 返回的是生成器,需要通过迭代获取内容。
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 通过 nextsibling、previoussibling、nextsiblings 和 previoussiblings 四个属性类获取兄弟节点,nextsibling 和 previoussibling 属性用来获取上一个兄弟节点和下一个兄弟节点,若节点不存在则返回 None。nextsiblings 和 previoussiblings 属性用于对当前节点的兄弟节点机型迭代,通过这两个属性可以获取当前节点的所有兄弟节点。
.nextsibling 和 .previoussibling 属性通常是字符串或空白,因为空白或者换行也可以被视作一个节点,所以得到的结果可能是空白或者换行。
# -*- 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 '
共有 nextelement、previouselement、nextelements 和 nextelements 四个属性来操作前后节点,和兄弟节点不同的是并不是针对同一层级的节点,而是所有节点不分层级。使用方法和兄弟节点类似,这里不再单独举例说明了。
BeautifulSoup 提供一下方法用于文档内容的搜索:
以上方法的参数及用法均相同,原理类似,这里以 findall 方法为例进行介绍,其他方法不再一一举例说明。findall 方法的定义如下:
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 时,标签名不加任何修饰,类名前加点,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 的名称直接对 name 属性重新赋值即可,修改属性的使用字典的方式进行重新赋值。
# 修改 Tag 的名称
tag.name = block
# 修改 Tag 的 class 属性值
tag['class'] = 'verybold'
对 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 可以大大节省编程的效率。