BeautifulSoup4 也是一个 XML/HTML 的解析器,能够解析和提取 XML/HTML 数据。
与基于 lxml 的局部遍历不同,BeautifulSoup4 则是基于 DOM(Document Object Model),一般会载入整个文档,解析整个 DOM 树,因此与 lxml 相比,BeautifulSoup4 解析时的时间和内存开销都会大的多。
BeautifulSoup4 在解析 XML 之外,还支持 CSS 选择器,Python 中的 HTML 解析器和 lxml 中的 XML 解析器。
from bs4 import BeautifulSoup
text = """
"""
html = BeautifulSoup(text,features='lxml')
print(html.prettify())
结果为:
在之前的代码段中,存在 BeautifulSoup(text,features='lxml'),其中的参数 features 说明的就是解析器。
BeautifulSoup 不仅支持 python 标准库中的 HTML 解析器,还支持一些第三方的解析器,比如 lxml。不同的解析器的优缺点为:
解析器 | 使用 | 优点 | 缺点 |
python 标准库 | BeautifulSoup(markup,"html.parser") | python 的内置标准库 执行速度适中 文档容错能力强 |
python2.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 格式的文档 |
速度慢 不依赖外部扩展 |
比较推荐的是使用 lxml 作为解析器,因为效率会比较高。
该对象可以认为是 HTML 中的标签。
from bs4 import BeautifulSoup
text = """
"""
html = BeautifulSoup(text,features='lxml')
# print title
print("html.title is:",html.title)
print(type(html.title))
# print head
print("html.head is:",html.head)
print(type(html.head))
# print body
print("html.body is:",html.body)
print(type(html.body))
# print div
print("html.div is:",html.div)
print(type(html.div))
# print ul
print("html.ul is:",html.ul)
print(type(html.ul))
# print li
print("html.li is:",html.li)
print(type(html.li))
# print a
print("html.a is:",html.a)
print(type(html.a))
结果为:
html.title is: None
html.head is: None
html.body is:
html.div is:
html.ul is:
html.li is: aaa
html.a is: aaa
也就是说,使用 BeautifulSoup4 可以借用实际的标签来实现输出,从最后输出的结果来看,text 中不存在的标签并不会输出,也不会创建 Tag 对象,而存在的标签都会创建 Tag 对象。需要注意的话,这种利用标签直接输出的情况,只会输出当前查找到的第一个对应的标签。
Tag 对象根据官方文档的介绍,存在两个比较重要的属性,name 和 attrs。
from bs4 import BeautifulSoup
text = """
"""
html = BeautifulSoup(text,features='lxml')
print(type(html))
print(html.name)
print(html.attrs)
# print body
print(html.body.name)
print(html.body.attrs)
# print div
print(html.div.name)
print(html.div.attrs)
# print ul
print(html.ul.name)
print(html.ul.attrs)
# print li
print(html.li.name)
print(html.li.attrs)
# print a
print(html.a.name)
print(html.a.attrs)
结果为:
[document]
{}
body
{}
div
{}
ul
{}
li
{'class': ['a']}
a
{'href': 'aaa.html'}
从上边的结果可以看出:
因为标签的属性采用的是字典的形式,因此也可以使用键值的形式进行访问:
# print li
print(html.li['class'])
# print a
print(html.a['href'])
结果为:
['a']
aaa.html
只是不能将 name 和 attrs 当作 key 来输入。
如果想要知道 Tag 中的内容,可以使用 Tag.string 来获取标签中的文字,此时的 Tag.string 的类型就是 NavigatableString。
# print li
print(type(html.li.string))
print(html.li.string)
# print a
print(type(html.a.string))
print(html.a.string)
结果为:
aaa
aaa
在之前使用 Tag 的 name 和 attrs 属性的时候,提到对于 BeautifulSoup 对象来说,该对象的 name 属性为 document,因此可知 BeautifulSoup 也是一种对象。
之前也提到过 BeautifulSoup 是基于 DOM(Document Object Model),一般会载入整个文档,解析整个 DOM 树。因此 BeautifulSoup 对象表示的整个文档的全部内容,该对象是一种树状结构,因此该对象支持文档树遍历和搜索的大部分方法,也可以认为是大型的 Tag 对象,因为毕竟存在 name 和 attrs 属性。
在 xml/html 中还有一些特殊对象,比如文档的注释部分:
比如上边的语句中,xml/html 就会将 中的部分解释为注释。
from bs4 import BeautifulSoup
text = ""
soup = BeautifulSoup(text,'lxml')
print(soup.b.string)
print(type(soup.b.string))
结果为:
Hey, buddy. Want to buy a used parser?
此时虽然使用了 Tag.string 的形式来输出数据,但是类型却不是 NavigatableString,而是 Comment。因此也可以认为 Comment 是一种比较特殊的 NavigatableString。
from bs4 import BeautifulSoup
text = """
"""
html = BeautifulSoup(text,features='lxml')
results = html.ul
print(results.contents)
for result in results.children:
print(result)
结果为:
['\n', aaa , '\n', bbb , '\n', ccc , '\n', ddd , '\n', eee , '\n']
aaa
bbb
ccc
ddd
eee
从上面的结果可以看出,使用 children 能够对文档树进行遍历,但是只会遍历子节点,而不会遍历孙子节点。如果将上边的 ul 更改为 div 会有不同的结果。
上边输出的结果中,存在很多的空行,这是由于解析器解析造成的。
from bs4 import BeautifulSoup
text = """
"""
html = BeautifulSoup(text,features='lxml')
for string in html.strings:
print(repr(string))
print('******************')
for string in html.stripped_strings:
print(repr(string))
结果为:
'\n'
'\n'
'aaa'
'\n'
'bbb'
'\n'
'ccc'
'\n'
'ddd'
'\n'
'eee'
'\n'
'\n'
'\n'
******************
'aaa'
'bbb'
'ccc'
'ddd'
'eee'
使用 strings 和 stripped_strings 能够循环获取 Tag 中的多个字符串,如上边代码段中显示的那样。在第一次输出中,存在很多空行(以 '\n' 显式标明),这是解析器解析的结果,可以使用 stripped_strings 来去除多余的空白内容。
使用 find 方法可以返回第一个满足条件的标签,而 find_all 方法则可以返回所有满足条件的标签。
在这两种方法使用过程中,可以使用 name 或者 attrs 参数来找出符合要求的标签,或者直接传入属性的名字作为关键字参数进行查找。
from bs4 import BeautifulSoup
text = """
"""
html = BeautifulSoup(text,features='lxml')
print(html.find('li'))
print(html.find_all('li'))
print(html.find('li',attrs = {'class':['a']}))
print(html.find_all('li',attrs = {'class':['a']}))
print(html.find('a',attrs = {'href':['aaa.html']}))
print(html.find_all('a',href = ['aaa.html']))
结果为:
aaa
[aaa , bbb , ccc , ddd , eee ]
aaa
[aaa , eee ]
aaa
[aaa]
从结果可以看出,如果使用 find_all 方法,则会返回返回所有满足条件的标签列表。
不过比较特殊的是,不能使用 ‘class’ 作为关键字参数进行查找。
使用 select 方法可以通过 css 选择器来进行查找。
from bs4 import BeautifulSoup
text = """
"""
html = BeautifulSoup(text,features='lxml')
# 通过标签名查找
print(html.select('li'))
# 通过类名查找
# 使用类名进行查找的话,应该在类名的前边加上一个.
print(html.select('.a'))
# 通过 id 查找
# 使用 id 进行查找的话,应该在 id 的前边加上一个#
print(html.select('#b'))
# 组合查找
# 使用组合查找的话,要注意各自的查找方式不变,方式之间用空格分隔
# 直接子标签查找,则使用 > 分隔
print(html.select('li a'))
# 通过属性查找
# 使用属性进行查找的话,应该将属性用中括号括起来
# 只是属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到
print(html.select('li[class="b"]'))
# 获取内容
# 对返回的列表先遍历内容,然后使用 get_text 方法,可以获取内容
for item in html.select('li'):
print(item.get_text())
结果为:
[aaa , bbb , ccc , ddd , eee ]
[aaa , eee ]
[bbb ]
[aaa, bbb, ccc, ddd, eee]
[ddd ]
aaa
bbb
ccc
ddd
eee