一. Beautiful Soup库简介
简单来说, Beautiful Soup 就是 Python 的一个 HTML 或者 XML 的解析库,可以用它来方便地从网页中提取数据。官方解释如下:
Beautiful Soup 提供一些简单的、Python 式的函数来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据。因为简单,所以不需要多少代码就可以写出一个完整的应用程序。
Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 UTF-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时你仅仅需要说明一下原始编码方式就可以了。
Beautiful Soup 已成为和 lxml、html6lib 一样出色的 Python 解释器,为用户灵活地提供不同的解析策略或强劲的速度。
所以说,利用它可以省去很多烦琐的提取工作,提高了解析效率。
Beautiful Soup 库的详析使用方法可以参考其中文文档:https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/
二. 安装beautifulsoup库
在 Ubuntu 虚拟机中,beautifulsoup4 能通过 pip 来安装:
$ python -m pip install beautifulsoup4
或者
$ pip install beautifulsoup4
其他平台下的安装可参考其他相应的安装方式,此处不做概述。
三. Beautiful Soup库的四个对象类
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:'Tag'、'NavigableString'、'BeautifulSoup'、'Comment'
1. Tag
Tag 是什么?通俗点讲就是 HTML 中的一个个标签,例如:
<title>The Dormouse's story</title>
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
上面的 'title'、'a'
等 HTML 标签加上里面包括的内容就是 Tag
,下面我们来了解一下怎样用 Beautiful Soup 来方便地获取 Tags:
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.title)
print(soup.a)
# 运行结果
<title>The Example of Beautiful Soup</title>
<a href="../news/index.html">Home Page</a>
我们可以利用 soup 加标签名轻松地获取这些标签的内容,这比正则表达式要方便很多。不过有一点是,它查找的是在所有内容中的第一个符合要求的标签,如果要查询所有的标签,我们在后面进行介绍。查看上述输出内容的类型,可以发现是bs4.element.Tag
:
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(type(soup.a))
# 运行结果
<class 'bs4.element.Tag'>
对于 Tag
,它有两个重要的属性,是 'name'和'attrs'
,下面我们分别来看看:
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.name)
print(soup.div.name)
# 运行结果
[document]
div
soup 对象本身比较特殊,它的 name
即为 [document]
,对于其他内部标签,输出的值便为标签本身的名称。
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.div.attrs)
print(soup.li.attrs)
# 运行结果
{
'class': ['header']}
{
'class': ['list'], 'id': 'li-1'}
通过attrs
属性打印出某一个标签的所有属性时,无论标签的属性有几个,都以字典数据类型输出结果。
如果我们想要单独获取某个属性,可以这样:例如我们想获取 p
的 class
属性,有如下四种写法:
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
# 写法1
print(soup.p.attrs['class'])
# 写法2
print(soup.p.attrs.get('class'))
# 写法3
print(soup.p['class'])
# 写法4
print(soup.p.get('class'))
# 运行结果
['title']
['title']
['title']
['title']
2. NavigableString
既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?用 .string 即可获取标签内容。例如
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.p.string)
print(type(soup.p.string))
# 运行结果
Python
<class 'bs4.element.NavigableString'>
检查一下输出内容的类型,可以发现是NavigableString(可遍历的字符串)
。
3. BeautifulSoup
BeautifulSoup 对象表示的是一个文档的全部内容。大部分时候,可以把它当作 Tag
对象,是一个特殊的 Tag
。例如下面示例,我们可以分别获取它的类型,名称,以及属性:
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(type(soup.name))
print(soup.name)
print(soup.attrs)
# 运行结果
<class 'str'>
[document]
{
}
4. Comment
Comment 对象是一个特殊类型的 NavigableString 对象,其输出的内容不包括注释符号,但是如果不好好处理它,可能会对我们的文本处理造成意想不到的麻烦。例如:
from bs4 import BeautifulSoup
html = """<p class="title" style="color: red"><!--Python--></p>"""
soup = BeautifulSoup(html, 'lxml')
print(soup.p)
print(soup.p.string)
print(type(soup.p.string))
# 运行结果
<p class="title" style="color: red"><!--Python--></p>
Python
<class 'bs4.element.Comment'>
p
标签里的内容实际上是注释,但是如果我们利用 .string
来输出它的内容,我们发现它已经把注释符号去掉了,所以这可能会给我们带来不必要的麻烦。另外我们打印输出它的类型,发现它是一个 Comment
类型,所以,我们在使用前最好做一下判断,判断代码如下:
if type(soup.a.string)==bs4.element.Comment:
print soup.a.string
四. Beautiful Soup库详析
Beautiful Soup 借助网页的结构和属性等特性来解析网页。有了它,我们不用再去写一些复杂的正则表达式,只需要简单的几条语句,就可以完成网页中某个元素的提取。接下来我们就来感受一下 Beautiful Soup 的强大之处。
(一)解析器
Beautiful Soup 在解析时实际上依赖解析器,它除了支持 Python 标准库中的 HTML 解析器外,还支持一些第三方解析器(比如 lxml)。下表列出了 Beautiful Soup 支持的解析器:
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python 标准库 | BeautifulSoup(markup, “html.parser”) | Python 的内置标准库、执行速度适中、文档容错能力强 | 较旧版本容错能力差 |
lxml HTML 解析器 | BeautifulSoup(markup, “lxml”) | 执行速度快、文档容错能力强 | 需要安装 C 语言库 |
lxml XML 解析器 | BeautifulSoup(markup, “xml”) | 执行速度快、唯一支持 XML 的解析器 | 需要安装 C 语言库 |
html5lib | BeautifulSoup(markup, “html5lib”) | 最好的容错性、以浏览器的方式解析文档、生成 HTML5格式的文档 | 速度慢、不依赖外部扩展 |
通过以上对比可以看出,lxml 解析器有解析 HTML 和 XML 的功能,而且速度快,容错能力强,所以推荐使用它。
(二)创建Beautiful Soup对象
-
step1:导入 bs4 库:
from bs4 import BeautifulSoup
-
step2:创建一个字符串,后面的例子我们便会用它来演示:
html = ''' <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>The Example of Beautiful Soup</title> </head> <body> <div class="header"> <p class="title">Python</p> <p class="profile"> Life is short,you need Python. <i>Beautiful is better than ugly.</i> Explicit is better than implicit. <i>Simple is better than complex.</i> </p> <ul class="menu"> <li class="li1" id="home-page"><a href="../news/index.html">Home Page</a></li> <li class="li2"><a href="../course/course.html">Online Class</a></li> <li class="li3"><a href="../doc/docDownload.html">Download Document</a></li> <li class="li4"><a href="../news/search.html">Search</a></li> </ul> <div class="login"> <span class="span Admin"><a href="../user/admin.html"><strong>USER ADMIN:</strong></a></span> <span class="item"><a href="../user/login.html">SIGN IN | </a></span> <span class="item"><a href="../user/register.html">SIGN UP | </a></span> <span class="item" id="logout">SIGN OUT</span> </div> </div> </body> </html> '''
-
step3:创建 beautifulsoup 对象:
soup = BeautifulSoup(html, 'lxml')
-
step4:指定编码方式:当 html 为其他类型编码(非 utf-8 和 ascii),比如 GB2312 的时候,需要指定相应的字符编码,BeautifulSoup才能正确解析:
htmlcharset = "GB2312" soup = BeautifulSoup(open('test_bs4.html'), 'lxml', from_encoding=htmlcharset)
另外,除了上述方式,我们还可以用本地 HTML 文件来创建对象,例如:
soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
前面所创建的 html 字符串就是 test_bs4.html 文件的内容。
(三)节点选择器
1. 选择元素
直接调用节点的名称就可以选择节点元素,再调用 string 属性就可以得到节点内的文本了,这种选择方式速度非常快。如果单个节点结构层次非常清晰,可以选用这种方式来解析。如下示例:
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.title)
print(type(soup.title))
print(soup.head)
print(soup.li)
# 运行结果:
<title>The Example of Beautiful Soup</title>
<class 'bs4.element.Tag'>
<head>
<meta charset="utf-8"/>
<title>The Example of Beautiful Soup</title>
</head>
<li class="li1" id="home-page"><a href="../news/index.html">Home Page</a></li>
观察上述代码运行结果,可以发现,调用节点名称选择的节点元素是 bs4.element.Tag
类型,这是Beautiful Soup中一个重要的数据结构。经过选择器选择后,选择结果都是这种 Tag 类型。
另外我们可以发现,当我们要选取 li 节点时,结果只返回第一个 li 节点的内容,后面的几个 li 节点并没有选到。也就是说,当选择多个节点时,这种选择方式只会选择到第一个匹配的节点,其他后面的节点都会被忽略。
2. 提取信息
在我们创建了 BeautifulSoup 对象之后,我们不仅可以提取节点的文本内容,而且可以提取节点属性和节点名称。下表总结了提取节点信息的各种属性:
属性 | 功能 |
---|---|
Tag.name | 获取节点名称 |
Tag.attrs | 获取节点属性 |
Tag.string | 获取节点的内容 |
Tag.strings | 获取节点的多个内容 |
Tag.stripped_strings | 获取节点的多个内容,其中不包括空格与空行 |
-
获取节点名称
from bs4 import BeautifulSoup soup = BeautifulSoup(open('test_bs4.html'), 'lxml') print(soup.div.name) # 运行结果: div
-
获取节点属性
from bs4 import BeautifulSoup soup = BeautifulSoup(open('test_bs4.html'), 'lxml') print(soup.li.attrs) print(soup.span.attrs) print(soup.li.attrs['id']) # 运行结果: { 'class': ['li1'], 'id': 'home-page'} { 'class': ['span', 'Admin']} home-page
可以看到,attrs 的返回结果是字典形式,它把选择的节点的所有属性和属性值组合成一个字典。另外,需要注意的是,有的返回结果是字符串,有的返回结果是字符串组成的列表,这是因为有的节点元素属性值是唯一的,所以返回结果为字符串,比如 id 、name等;而有的节点元素属性值是不唯一的,所以返回结果为列表,比如 class。
如果我们要获取 li 节点的 id 属性,就相当于从字典中获取某个键值,只需要用中括号加属性名就可以。
-
获取节点单个内容
from bs4 import BeautifulSoup soup = BeautifulSoup(open('test_bs4.html'), 'lxml') print(soup.li.string) # 运行结果: Home Page
可以看到,当我们想要获取 li 节点的文本内容时,只能返回第一个节点的文本内容,其他后面的节点都会被忽略选择。
-
获取节点多个内容
from bs4 import BeautifulSoup soup = BeautifulSoup(open('test_bs4.html'), 'lxml') for string in soup.ul.strings: print(string) # 运行结果: Home Page Online Class Download Document Search
不难发现,上述运行结果可以输出所有的 li 节点的文本内容,但是同时也将空行输出了,这并不是我们想要的结果。那么如何可以输出所有的 li 节点的文本内容,又可以过滤掉其中的空行和空格呢?我们可以试试下面的方法:
from bs4 import BeautifulSoup soup = BeautifulSoup(open('test_bs4.html'), 'lxml') for string in soup.ul.stripped_strings: print(string) # 运行结果: Home Page Online Class Download Document Search
可见,上述代码中的
.stripped_strings
可以获取多个节点的内容,并且能过滤文本内容之间的空行。
3. 嵌套选择
在上面的例子中,我们知道每一个返回结果都是 bs4 element.Tag 类型,它同样可以继续调用节点进行下一步的选择。我们看看下面的示例:
from bs4 import BeautifulSoup
soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.ul.li.a)
print(type(soup.ul.li.a))
print(soup.ul.li.a.string)
# 运行结果:
<a href="../news/index.html">Home Page</a>
<class 'bs4.element.Tag'>
Home Page
可以发现,当我们想要获取某一个节点的子节点时,可以进行嵌套选择。而我们在 Tag 类型的基础上再次选择得到的依然是 Tag 类型。
4. 关联选择
在做选择的时候,有时候不能做到一步就选到想要的节点元素,需要先选中某一个节点元素,然后以它为基准再选择它的子节点、父节点、 兄弟节点等,下面就来介绍如何选择这些节点元素。在关联选择中可调用的属性如下表所示:
属性 | 功能 | 类型 |
---|---|---|
Tag.contents | 以列表形式返回所有子节点 | list |
Tag.children | 获取所有的子节点,返回一个 list 生成器对象,可通过遍历获取其中的内容 | list_iterator |
Tag.descendants | 对所有 tag 的子孙节点进行递归循环 | generator |
Tag.parent | 获取该节点的父节点 | bs4.element.Tag |
Tag.parents | 获取父到根的所有节点,即祖先节点 | generator |
Tag.next_sibling | 获取该节点的下一个兄弟节点 | bs4.element.NavigableString |
Tag.previous_sibling | 获取该节点的上一个兄弟节点 | bs4.element.NavigableString |
Tag.next_siblings | 获取该节点的所有前面的兄弟节点的生成器 | generator |
Tag.previous_siblings | 获取该节点的所有后面的兄弟节点的生成器 | generator |
Tag.next_element | 获取该节点前面的一个节点,并不只是针对兄弟节点 | bs4.element.NavigableString |
Tag.previous_element | 获取该节点后面的一个节点,并不只是针对兄弟节点 | bs4.element.NavigableString |
Tag.next_elements | 获取该节点的所有前面的节点,并不只是针对兄弟节点 | generator |
Tag.previous_elements | 获取该节点的所有后面的节点,并不只是针对兄弟节点 | generator |
上述的属性中,返回结果的类型各不相同,有列表(list)、列表迭代器(list_iterator)、生成器(generator)、标签对象(bs4.element.Tag)和可遍历的字符串对象(bs4.element.NavigableString)等。其中的列表迭代器和生成器可以通过遍历进行获取,或者将其转换为 list 对象进行输出。下面我们通过实例进行演示:
-
获取子节点
from bs4 import BeautifulSoup soup = BeautifulSoup(open('test_bs4.html'), 'lxml') print(soup.ul.contents) # 运行结果: ['\n', <li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>, '\n', <li class="list" id="li-2"><a href="../course/course.html">Online Class</a></li>, '\n', <li class="list" id="li-3"><a href="../doc/docDownload.html">Download Document</a></li>, '\n', <li class="list" id="li-4"><a href="../news/search.html">Search</a></li>, '\n']
可以看到,该方法返回的是一个列表,列表元素包括了 ul 节点内的空行与子节点 li 。
from bs4 import BeautifulSoup soup = BeautifulSoup(open('test_bs4.html'), 'lxml') print(soup.ul.children) for i, child in enumerate(soup.ul.children): print("第%d个元素:%s"%(i,child)) # 运行结果: <list_iterator object at 0x7f95bf6d2978> 第0个元素: 第1个元素:<li class="li1" id="home-page"><a href="../news/index.html">Home Page</a></li> 第2个元素: 第3个元素:<li class="li2"><a href="../course/course.html">Online Class</a></li> 第4个元素: 第5个元素:<li class="li3"><a href="../doc/docDownload.html">Download Document</a></li> 第6个元素: 第7个元素:<li class="li4"><a href="../news/search.html">Search</a></li> 第8个元素:
可以看到,返回结果是一个
list_iterator
对象,要输出其内容,需要以遍历的方法循环输出。当然,此列表生成器也可以转换成一个列表对象直接输出:print(list(enumerate(soup.ul.children))) # 运行结果: [(0, '\n'), (1, <li class="li1" id="home-page"><a href="../news/index.html">Home Page</a></li>), (2, '\n'), (3, <li class="li2"><a href="../course/course.html">Online Class</a></li>), (4, '\n'), (5, <li class="li3"><a href="../doc/docDownload.html">Download Document</a></li>), (6, '\n'), (7, <li class="li4"><a href="../news/search.html">Search</a></li>), (8, '\n')]
转换成列表输出后,每一个列表元素都是一个元组,其中元组的第一个元素是索引,第二个元素是节点内容。
-
获取子孙节点
from bs4 import BeautifulSoup soup = BeautifulSoup(open('test_bs4.html'), 'lxml') print(soup.ul.descendants) for i, child in enumerate(soup.ul.descendants): print("第%d个元素:%s"%(i,child)) # 运行结果: <generator object descendants at 0x7f95bce07ca8> 第0个元素: 第1个元素:<li class="li1" id="home-page"><a href="../news/index.html">Home Page</a></li> 第2个元素:<a href