从先前的几篇文章中,我们已经能够使用Requests库构造请求并获得正确的响应,但是在样例中我们也发现了仅使用Requests库的缺陷。在网络爬虫的设计中,仅能构造请求并收到响应是远远不够的。想要获取响应中有意义的信息并能够由此采取下一步行动,是整个流程中的重要一段。所以本篇文章,我们就来一起学习较为流行的HTML/XML数据分析提取库:Beautiful Soup 4
Beautiful Soup 4库是一个在Python爬虫设计中非常流行的数据分析提取库。它能够解析HTML/XML语言并生成文档树。由于其简单易用的特性被许多小型爬虫所采用。
HTML语言作为一种标记语言,每一个元素都由一对标签包含,层层嵌套形成树状结构。当人去阅读这段代码时,实际内容会被一大串描述元素样式的代码淹没,观察较为繁琐;但程序阅读恰恰相反,标签化、结构化且严谨的语法非常适合程序(浏览器等)理解分析,并按照当初设计的方式渲染重构网页。
我们获得的来自服务器的响应一般为HTML格式的一个页面,由于HTML语言特性,人类难以直观理解实际效果,这就需要一个程序来解析这个响应。浏览器核心也包含一个HTML解析器,而在爬虫中,Beautiful Soup 4库就起到了这样的效果。
至于这个Python库的名称为什么是BeautifulSoup4
(即bs4
)而不是BeautifulSoup
,是因为后者已经用来表示BeautifulSoup3
库(为了兼容性原因仍然保留),为避免名称冲突,才使用这个名字。
关于这个库的更多信息以及使用指南,请参阅Beautiful Soup 4官方手册(中文)Beautiful Soup 4.4.0 文档
pip install beautifulsoup4
等待安装完成即可。
bs4
包中,使用时只需要从包中单独引用BeautifulSoup类即可。使用from-import语句实现from bs4 import BeautifulSoup
注意:BeautifulSoup
中的B
与S
为大写字母,Python对大小写敏感
示例实现步骤
Requests
库下载了一个样例页面BeautifulSoup
实例soup
prettify()
方法将主体内容进行了优化并输出import requests
from bs4 import BeautifulSoup
r = requests.get("http://python123.io/ws/demo.html")
# 提交请求抓取响应
# print(r.text)
soup = BeautifulSoup(r.text, features="html.parser")
# 声明实例并将响应主体作为参数传入
print(soup.prettify())
#使用prettify()方法输出优化后的内容
在了解BeautifulSoup
类的原理前,先来补充一点HTML语言的相关知识。
HTML语言的各个元素均由标签构成,一对一对标签以及中间包括的内容构成了元素,例如:
<p class="title">Hello Worldp>
其中,
p
:标签名称,p表示段落(paragraph)
class="title"
:标签的属性,属性键为class
,值为"title"
,这个属性与CSS样式表相关
Hello World
:标签主体内容
/p
:标签结束符号,与p
标签开始符号一一对应
标签开始部分(标签名称和属性)与标签结束都由尖括号包含,两个尖括号中间的内容为标签主体内容
这样的标签嵌套叠加,形成了一个树状结构,被称为文档树
附表:HTML常用标签
标签 名称 | 说明 |
---|---|
|
链接 |
|
普通文本 |
|
文档主体内容标签 |
|
HTML文档中的分隔区块 |
|
创建表单用于用户输入 |
|
标题,n为等级1-6 |
|
图片 |
|
对一部分文本进行着色 |
|
JavaScript脚本 |
BeautifulSoup
类中的一个实例正与一个文档树一一对应(类本身也是)。**声明一个新的实例时,将HTML内容作为参数传递给实例初始化函数,这个HTML内容所对应的文档树就按照BeautifulSoup
类的结构被对应到了实例中。**通过Beautiful Soup库,我们能够对文档树进行解析、遍历、维护。
在初始化一个BeautifulSoup
实例时,不仅需要提供HTML文档内容作为参数,还需要选择一个适合的解释器
解析器 | 名称 | 优点 | 缺点 |
---|---|---|---|
Python标准库解析器 | html.parser |
是Python内置标准库 执行速度适中 文档容错能力强 | 较早版本Python中的解析器容错能力弱且不稳定 |
lxml HTML解析器 | lxml |
速度快 文档容错能力强 | 依赖C语言库 |
lxml XML解析器 | lxml-xml |
速度快 唯一一个XML解析器 | 依赖C语言库 |
html5lib | html5lib |
容错性最佳 以浏览器方式解析文档 生成html5格式文档 | 速度慢 不依赖外部拓展 |
其中,Python标准库解析器在Python标准库中,不需要另外安装;其他的解析器需要自行安装第三方库(lxml的两个解析器安装lxml
包,html5lib解析器安装html5lib
包)
声明一个新实例的语法(尖括号仅表示名称,不在语法中)
<Name> = BeautifulSoup(<HTML Document>, features="" )
其中,
Name
:新实例的名称HTML Document
:需要解析的HTML文档,以字符串形式,也可以为打开的文件Parser Name
:解析器名称,各个解析器的名称见上表基本元素 | 说明 |
---|---|
Tag | 标签,为文档的最基本组成元素,上面已经介绍 |
Name | 标签的名称,例如标签名称为p,调用格式为
|
Attributes | 标签的属性,以字典形式组织,调用格式为
|
NavigableString | 可以遍历的字符串,为标签内非属性字符串,即为上面提到的标签主体内容,格式
|
Comment | 标签内字符串的注释部分,是一种特殊的NavigableString ,格式同上 |
Text | 标签中的主体内容(不含注释),格式
|
Beautiful Soup | 整个文档对应的类的实例,这个元素的名称为[document] |
bs4.element.Tag
bs4.element.NavigableString
BeautifulSoup标签的属性
基本元素中,除了Tag
为独立的标签元素外,其余所有都可以视作Tag
的属性,那么,如何从BeautifulSoup类中提取一个标签呢?我们以实例soup
为例
tag_a = soup.a
tag_p = soup.p
实际上,使用soup.就可以获得实例对应文档树中第一个名称为tag name
的标签,如果文档树中不存在这个标签,不会报错,而是返回空,且这些标签名称由于不固定,IDE不提供代码补全,所以要小心标签名称错误
注意,使用这个方法只能获得文档中的一个标签,如果需要其他标签,请使用后面讲到的文档遍历
标签的属性在上面的基本元素中已经基本给出,下面挑选必要的属性逐个介绍
Attributes
:标签属性域中的属性,为字典形式,对于HTML中已经定义的多值属性,将返回一个List
(即使只有一个也返回List)
tag.attrs['class']
NavigableString
:标签中的主要内容(可遍历字符串),即两对尖括号中间的内容
NavigableString
不是一个str
普通字符串型数据,而是Beautiful Soup 4中另行定义的类型bs4.element.NavigableString
Text
Comment
:标签中的注释内容,注意:Comment
为一种特殊的NavigableString
,其使用方法与返回形式完全相同,即使用string,两个返回格式相同,无法从这里区分Comment
与NavigableString
,需要用类型或Text判断
NavigableString
,Comment
有自己独有的类型bs4.element.Comment
Text
Text
:标签中的主要内容(不包含注释),也是两对尖括号中的内容
Text
的类型为str
,为Python标准类型Beautiful Soup 支持HTML文档树的上行遍历
、下行遍历
、平行遍历
三种遍历方法。所有遍历均通过查询当前标签的属性实现,即
<tag>.<attribute>
从根节点元素开始向文档叶节点遍历(寻找子节点)
Beautiful Soup 为下行遍历提供了三个属性
属性 | 说明 |
---|---|
.contents | 子节点列表,将这个标签的所有子节点(下一层)以列表形式给出 |
.children | 子节点的可迭代类型对象,生成这个标签所有子节点(下一层)的可迭代列表对象 |
.descendants | 子孙节点的可迭代类型对象,包含这个标签所有子孙节点(下面所有) |
\n
换行符或纯文本,这些字符串的类型为NavigableString
list_iterator object
.descendants
的遍历顺序为先序遍历从叶节点元素开始向文档根节点遍历(寻找父节点)
Beautiful Soup 为上行遍历提供了两个属性
属性 | 说明 |
---|---|
.parent | 父节点,这个标签的父节点,仅一个 |
.parents | 先辈节点、这个节点的所有父节点的迭代(父节点、父节点的父节点……) |
[document]
节点(参见上文HTML基础知识中文档树结构图)的类型为BeautifulSoup
,即为BeautifulSoup本身,其他节点的类型均为bs4.element.Tag
在平行节点间横向遍历,平行节点,即兄弟节点,指父节点为同一个节点的一系列节点
Beautiful Soup 为平行遍历提供了四个属性
属性 | 说明 |
---|---|
.next_sibling | 按照HTML文本序的下一个平行节点 |
.previous_sibling | 按照HTML文本序的上一个平行节点 |
.next_siblings | 按照HTML文本序的下面所有平行节点 |
.previous_sibling | 按照HTML文本序的上面所有平行节点 |
注意:平行节点不仅有标签类型,也有其他字符串,如\n
换行符或纯文本等,这些字符串的类型为NavigableString
,使用时请注意区分
可以通过判断该元素的name
属性是否为None来确定该元素是否为NavigableString
(若使用类型判断,则需要导入整个bs4)
if not tag.name is None:
do_something()
使用Requests
库得到的响应文档未经过处理(换行、缩进等),且由于HTML的特性,没有渲染情况下可读性较差,需要通过优化提升可读性,Beautiful Soup
类的Prettify()
方法提供了这个功能。需要将返回值打印才能看到效果(只是在其中添加换行符)
soup = BeautifulSoup(HTML_text, "html.parser")
print(soup.prettify())
Beautiful Soup提供了一个专用的方法用来查找指定名称的标签
soup.find_all('name')
#soup 为搜索范围的Tag或BeautifulSoup对象,name 为要查找的标签名称
函数返回一个列表,包含所有符合名称的标签
由于列表可遍历(可迭代),可以使用for语句遍历整个搜索结果列表,列表中的元素类型仍然为bs4.element.Tag
当想要获取多个类型的标签时,可以将一个包含有欲查找标签名称(字符串类型)的列表作为参数传入标签
当想要获得这个soup中的所有标签时(相当于树遍历),可以将参数变为True
如果还想要进一步限制查找内容,find_all()
函数还提供了class
属性参数
soup.find_all('name', 'attr')
这将搜索所有名称为name
且class
类属性为attr
的标签
如果将name
设为空''
,则将返回所有class
符合的标签,而不限制标签名称
还可以限制id属性
soup.find_all(id = 'link1')
这将搜索所有id
属性为"link1"的标签
这个函数的另一个参数recursive
递归,默认为True
,表示将遍历该标签对象下的所有子孙节点,否则只遍历该节点的子节点
又一个参数string
,可以搜索标签主体内容(一对尖括号中间的内容),返回类型为NagivableString
必须精确匹配内容才能搜索到,实际没有太大意义,以后将使用正则表达式标准库re
来完成正则表达式匹配搜索
除了find_all()外,Beautiful Soup还提供了其他特性的搜索函数,这些函数的参数与find_all()
相同,有对应需求时,直接使用函数即可
函数 | 说明 |
---|---|
.find() | 搜索且只返回第一个结果 |
.find_parents() | 在先辈节点中搜索,返回列表 |
.find_parent() | 在先辈节点中搜索,且只返回一个结果 |
.find_next_siblings() | 在后续平行节点中搜索,返回一个列表 |
.find_next_sibling() | 在后续平行节点中搜索,且只返回一个结果 |
.find_previous_siblings() | 在之前的平行节点中搜索,返回一个列表 |
.find_previous_sibling() | 在之前的平行节点中搜索,且只返回一个结果 |