Beautiful Soup 是一个可以从HTML和XML文件中提取数据的Python。它可以实现文档的增删改查操作,我们侧重点是它的查询操作。
安装 Beautiful Soup
你可以根据自己的系统选择下面的安装代码进行安装操作:
$ apt-get install Python-bs4
$ easy_install beautifulsoup4
$ pip install beautifulsoup4
安装解析器
Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是 lxml。根据操作系统不同,你可以选择下面方法来安装 lxml:
$ apt-get install Python-lxml
$ easy_install lxml
$ pip install lxml
另外一个可供选择的解析器是纯Python实现的 html5lib,解析方式和浏览器相同,你可以选择下面的方法来安装 html5lib:
$ apt-get install Python-html5lib
$ easy_install html5lib
$ pip install html5lib
几种解析器的优缺点
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup, "html.parser") | 执行速度适中,文档容错能力强 | Python2.7.3 or 3.2.3前的版本文档容错能力差 |
lxml HTML 解析器 | BeautifulSoup(markup, "lxml") | 速度快,文档容错能力强 | 需要安装C语言库 |
lxml XML 解析器 | BeautifulSoup(markup, "xml") | 速度快,唯一支持XML的解析器 | 需要安装C语言库 |
html5lib | BeautifulSoup(markup, "html5lib") | ||
最高的容错性,以浏览器的方式解析文档,生成HTML5格式的文档 | 数独慢,不依赖外部扩展 |
推荐使用 lxml 作为解析器,因为效率更高。在Python2.7.3之前的版本和Python3.2.3之前的版本,必须安装 lxml 或 html5lib,因为Python版本的标准库中的内置的 HTML 解析方法不够稳定。
如何使用
将一段文档传入 Beautiful Soup 的构造方法,就能得到一个文档的对象,可以传入一段文字或一个文件句柄。
from bs4 import BeautifulSoup
soup = BeautifulSoup(open("index.html")) // 文件句柄
soup = BeautifulSoup("<html>data</html>") // 文档
文档被转换成 Unicode,并且HTML的实例都被转换成 Unicode 编码,然后,Beautiful Soup选择最合适的解析器来解析这段文档,如果手动指定解析器,那么 Beautiful Soup 会选择指定的解析器来解析文档。
对象的种类
Beautiful Soup 将复杂 HTML 文档转换成一个复杂的树形结构,每隔节点都是Python对象,多有对象可以归纳为4种:Tag
,NavigableString
,BeautifulSoup
,Comment
。
Tag对象
Tag 对象与 XML 或 HTML 原生文档中的 tag 相同。Tag 有很多方法和属性,其中最重要的属性:name 和 attributes。
from bs4 import BeautifulSoup
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>','lxml')
tag = soup.b // 一个 Tag 对象
print(type(tag)) // <class 'bs4.element.Tag'>
print(tag.name) // tag都有自己的名字:b
print(tag.attrs) // tag的属性字典:{'class': ['boldest']}
print(tag['class']) // 属性字典中的'class'值:['boldest']
// tag 的属性可以被添加,删除或修改,属性操作和字典一样
tag['class'] = 'myClsss'
print(tag) // <b class="myClsss">Extremely bold</b>
tag['id'] = 'custemId'
print(tag) // <b class="myClsss" id="custemId">Extremely bold</b>
NavigableString对象
字符串常被包含在 tag 内。Beautiful Soup 用 NavigableString 类来包装 tag 中的字符串。
print(type(tag.string)) // <class 'bs4.element.NavigableString'>
print(tag.string) // Extremely bold
字符串不支持 .contents 或 .string 属性或 find() 方法。
BeautifulSoup对象
该对象表示的是一个文档的全部内容,大部分的适合可以把它当作 Tag 对象。因为 BeautifulSoup 对象并不是真正的 HTML 或 XML 的tag,所以它没有name和attributes属性。但有时查看它时,.name 属性还是可以的。
print(type(soup)) // <class 'bs4.BeautifulSoup'>
print(soup.name) // [document]
Comment对象
Tag , NavigableString , BeautifulSoup 几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象.容易让人担心的内容是文档的注释部分。
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup,'lxml')
comment = soup.b.string
print(type(comment)) // <class 'bs4.element.Comment'>
Comment 对象是一个特殊类型的 NavigableString 对象:
print(comment) // Hey, buddy. Want to buy a used parser?
但是当它出现在HTML文档中时, Comment 对象会使用特殊的格式输出:
print(soup.b.prettify())
<b>
<!--Hey, buddy. Want to buy a used parser?-->
</b>
搜索文档树
说完 Beautiful Soup 中的四种对象,接下来介绍一下如何搜索内容。 Beautiful Soup 提供了很多搜索方法,这里着重介绍其中的两个:find()
和 find_all()
,其他的方法和参数以及用法都类似。
过滤器
过滤器作为搜索文档的参数,贯穿整个搜索的 API。过滤器可以被用在 tag 中的 name 中,节点的属性中,字符串中或者他们的混合中。过滤器可以是字符串、正则表达式、列表、True值甚至是方法。
- 字符串
soup.find_all('b') // 查找文档中所有的<b>标签
- 正则表达式
import re
// 查找 b 开头的标签
for tag in soup.find_all(re.compile("^b")):
print(tag.name)
// body
// b
- 列表
markup = '''
<b>Hey, buddy. Want to buy a used parser?</b>
<p class="title">The Dormouse's story</p>
<a>Once upon a time there were three little sisters</a>
'''
soup = BeautifulSoup(markup,'lxml')
for tag in soup.find_all(['a','p']):
print(tag)
// <p class="title">The Dormouse's story</p>
// <a>Once upon a time there were three little sisters</a>
- True
True 可以匹配任何值
for tag in soup.find_all(True):
print(tag.name)
// html
// body
// b
// p
// a
- 方法
如果没有合适的过滤器,那么还可以定一个方法,方法只接收一个参数,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则返回 False
def has_class(tag):
return tag.has_attr('class')
for tag in soup.find_all(has_class):
print(tag)
# <p class="title">The Dormouse's story</p>
find_all()
find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
- name 参数
name 参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉。其中,name 参数的值可以是任一类型的过滤器:字符串、正则表达式、列表、Ture或是方法。
markup = '''
<b>Hey, buddy. Want to buy a used parser?</b>
<p class="title">The Dormouse's story</p>
<a>Once upon a time there were three little sisters</a>
'''
soup = BeautifulSoup(markup,'lxml')
print(soup.find_all('p'))
# [<p class="title">The Dormouse's story</p>]
- keyword 参数
如果一个指定名字的参数不是搜索内置的参数名称,搜索时会把该参数当作指定名字tag的属性来搜索。
markup = '''
<b id='link'>Hey, buddy. Want to buy a used parser?</b>
<p class="title">The Dormouse's story</p>
<a>Once upon a time there were three little sisters</a>
<a class="sister" href="http://example.com/elsie" id="link1">three</a>
<div data-foo="value">foo!</div>
'''
soup = BeautifulSoup(markup,'lxml')
print(soup.find_all(id='link'))
# [<b id="link">Hey, buddy. Want to buy a used parser?</b>]
# class是Python的保留关键字,这里是 class_
print(soup.find_all(class_='title'))
# [<p class="title">The Dormouse's story</p>]
# 可以使用多个指定名字的参数来过滤多个tag的属性
import re
print(soup.find_all(id='link1', href=re.compile('example')))
# [<a class="sister" href="http://example.com/elsie" id="link1">three</a>]
- attrs 参数
有些tag属性在搜索中不能使用,如HTML5中的 data-* 属性,我们可以使用 attrs 参数定一个字典参数来搜索包含特殊属性的tag。
print(soup.find_all(attrs={'data-foo':'value'}))
# [<div data-foo="value">foo!</div>]
- text参数
通过 text 参数可以搜索文档中的字符串内容,和 name 参数一样,接受参数有:字符串、正则表达式、列表、True。
print(soup.find_all(text='three'))
# ['three']
import re
print(soup.find_all(text=re.compile('^H.*?$')))
# ['Hey, buddy. Want to buy a used parser?']
- recursive 参数
Beautiful Soup 会检索当前tag的所有子孙节点,如果你只想搜索tag的直接子节点,可以使用参数 recursive = False。
markup = '''
<html>
<head>
<title>
The Dormouse's story
</title>
</head>
</html>
'''
soup = BeautifulSoup(markup, 'lxml')
print(soup.html.find_all('title'))
# [<title>
# The Dormouse's story
# </title>]
print(soup.html.find_all('title', recursive=False))
# []
- limit 参数
markup = '''
<b id='link'>Hey, buddy. Want to buy a used parser?</b>
<p class="title">The Dormouse's story</p>
<a>Once upon a time there were three little sisters</a>
<a class="sister" href="http://example.com/elsie" id="link1">three</a>
<div data-foo="value">foo!</div>
'''
soup = BeautifulSoup(markup,'lxml')
import re
for str in soup.find_all(text=re.compile('o')):
print(str)
# Hey, buddy. Want to buy a used parser?
# The Dormouse's story
# Once upon a time there were three little sisters
# foo!
for str in soup.find_all(text=re.compile('o'), limit=1):
print(str)
# Hey, buddy. Want to buy a used parser?
- 说明
find_all()
是 Beautiful Soup中最常用的搜索方法,该库还提供了它的简写方法。BeautifulSoup 对象和 tag 对象都可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同,下面代码是等价的:
soup.find_all("a")
soup("a")
soup.title.find_all(text=True)
soup.title(text=True)
find()
find_all()
方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果。比如文档中只有一个<body>
标签,那么使用 find_all()
方法来查找显然不太合适,如果使用 limit=1 参数不如使用 find()
方法。
soup.find_all('title', limit=1)
# [<title>The Dormouse's story</title>]
soup.find('title')
# <title>The Dormouse's story</title>
唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果。
find_all() 方法没有找到目标是返回空列表,find() 方法找不到目标时,返回 None 。
soup.head.title 是 tag的名字 方法的简写。这个简写的原理就是多次调用当前tag的 find() 方法:
soup.head.title
# <title>The Dormouse's story</title>
soup.find("head").find("title")
# <title>The Dormouse's story</title>
输出
prettify()
方法将Beautiful Soup的文档树格式化后以Unicode编码输出,每个XML/HTML标签都独占一行。
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup, 'lxml')
print(soup.prettify())
# <html>
# <body>
# <a href="http://example.com/">
# I linked to
# <i>
# example.com
# </i>
# </a>
# </body>
# </html>
Beautiful Soup 功能强大,除了上面提到的 搜索文档树 功能,更有遍历文档树,修改文档树等等功能。更多详细介绍请查阅官方文档 4.2.0。