Beautiful Soup库简介

Beautiful Soup库是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式. Beautiful Soup库会帮你节省数小时甚至数天的工作时间.

安装Beautiful Soup库

安装Beautiful Soup库的最快也是最简单的方法是在shell上使用以下命令:
pip install beautifulsoup4
一般使用时直接import即可
from bs import BeautifulSoup

安装解析器

Beautiful Soup支持Python标准库中的HTML解析器, 还支持一些第三方的解析器, 其中一个是 lxml .可以使用pip install lxml来安装lxml.

另一个可供选择的解析器是纯Python实现的 html5lib , html5lib的解析方式与浏览器相同, 可以使用pip install html5lib来安装html5lib.

下表列出了主要的解析器,以及它们的优缺点:

解析器 使用方法 优势 劣势
Python标准库 BeautifulSoup(markup, “html.parser”) Python的内置标准库 执行速度适中 文档容错能力强 Python 2.7.3 或 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作为解析器, 因为效率更高.

Beautiful Suop库的理解

使用以下代码可以获取HTML文档的BeautifulSuop类.

1
2
from bs4 import BeautifulSoup
soup = BeautifulSoup(open("index.html"), 'lxml')

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种: BeautifulSoup, Tag, NavigableString, Comment.

BeautifulSoup

BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,它支持 遍历文档树搜索文档树 中描述的大部分的方法.
BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,它支持 遍历文档树搜索文档树 中描述的大部分的方法.

因为 BeautifulSoup 对象并不是真正的HTML或XML的tag,所以它没有name和attribute属性.但有时查看它的 .name 属性是很方便的,所以 BeautifulSoup 对象包含了一个值为 “[document]” 的特殊属性 .name
soup.name # '[document]'

Tag

Tag对象与XML或HTML原生文档中的tag相同, 是最基本的信息组织单元.
Tag对象有两个重要的属性: name和attributes

Name

每个tag都有自己的名字,通过 .name 来获取:

1
2
3
4
5
6
7
>>> from bs4 import BeautifulSoup
>>> soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
>>> tag = soup.b
>>> tag
<b class="boldest">Extremely bold</b>
>>> type(tag)
<class 'bs4.element.Tag'>

如果改变了tag的name,那将影响所有通过当前Beautiful Soup对象生成的HTML文档:

1
2
3
>>>tag.name = "blockquote"
>>>tag
<blockquote class="boldest">Extremely bold</blockquote>

Attributes

一个tag可能有很多个属性. tag <b class="boldest"> 有一个 “class” 的属性,值为 “boldest” . tag的属性的操作方法与字典相同:
tag['class'] # 'boldest'
也可以直接用’.’取属性, 比如: .attrs:
tag.attrs # {'class': ['boldest']}
tag的属性可以被添加,删除或修改.

1
2
3
4
5
6
7
8
9
10
11
12
>>>tag['class'] = 'verybold'
>>>tag['id'] = 1
>>>tag
<blockquote class="verybold" id="1">Extremely bold</blockquote>
>>>del tag['class']
>>>del tag['id']
>>>tag
<blockquote>Extremely bold</blockquote>
>>>tag['class']
KeyError: 'class'
>>>print(tag.get('class'))
None

字符串常被包含在tag内.Beautiful Soup用 NavigableString 类来包装tag中的字符串:

1
2
3
4
>>> tag.string
'Extremely bold'
>>> type(tag.string)
<class 'bs4.element.NavigableString'>

tag中包含的字符串不能编辑,但是可以被替换成其它的字符串,用 replace_with() 方法:

1
2
3
4
>>> tag.string.replace_with("No longer bold")
'Extremely bold'
>>> tag
<blockquote class="boldest">No longer bold</blockquote>

NavigableString 对象支持 遍历文档树搜索文档树 中定义的大部分属性, 并非全部.尤其是,一个字符串不能包含其它内容(tag能够包含字符串或是其它tag),字符串不支持 .contents 或 .string 属性或 find() 方法.

Comment

BeautifulSoup, Tag, NavigableString几乎覆盖了html和xml中的所有内容, 但是还有一些特殊对象. 容易让人担心的内容是文档的注释部分:

1
2
3
4
5
>>>markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
>>>soup = BeautifulSoup(markup)
>>>comment = soup.b.string
>>>type(comment)
<class 'bs4.element.Comment'>

Comment 对象是一个特殊类型的 NavigableString 对象:
但是当它出现在HTML文档中时, Comment 对象会使用特殊的格式输出:

1
2
3
4
>>>print(soup.b.prettify())
<b>
<!--Hey, buddy. Want to buy a used parser?-->
</b>

Beautiful Soup中定义的其它类型都可能会出现在XML的文档中: CData, ProcessingInstruction, Declaration, Doctype. 与 Comment 对象类似, 这些类都是 NavigableString 的子类, 只是添加了一些额外的方法的字符串独享. 下面是用CDATA来替代注释的例子:

1
2
3
4
5
6
7
>>>from bs4 import CData
>>>cdata = CData("A CDATA block")
>>>comment.replace_with(cdata)
>>>print(soup.b.prettify())
<b>
<![CDATA[A CDATA block]]>
</b>

基于bs4库的HTML内容遍历方法

每一个HTML都是一颗完整的标签树, 同样也就有了下行遍历, 上行遍历和平行遍历.

下行遍历

标签数的下行遍历属性:

属性 描述
.contens 子节点的列表, 将\所有儿子节点存入列表
.children 子节点的迭代类型, 与.contens相似, 用于遍历儿子节点
.descnedants 子孙节点的迭代类型, 包含所有子孙节点, 用于循环遍历

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
>>> import requests
>>> from bs4 import BeautifulSoup
>>> r = requests.request('GET', 'https://zzz5.xyz')
>>> soup = BeautifulSoup(r.text)
>>> tag = soup.head
>>> tag.contents
[<meta content="Hexo 3.8.0" name="generator"/>, '\n', <meta charset="utf-8"/>, '\n', <meta content="webkit" name="renderer"/>, '\n', <meta content="IE=edge" http-equiv="X-UA-Compatible"/>, '\n', <link href="http://www.zzz5.xyz" rel="dns-prefetch"/>, '\n', <title>zzZ5的个人博客</title>, '\n', <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport"/>, '\n', <meta content="个人博客" name="description"/>, '\n', <meta content="日记" name="keywords"/>, '\n', <meta content="website" property="og:type"/>, '\n', <meta content="zzZ5的个人博客" property="og:title"/>, '\n', <meta content="http://www.zzz5.xyz/index.html" property="og:url"/>, '\n', <meta content="zzZ5的个人博客" property="og:site_name"/>, '\n', <meta content="个人博客" property="og:description"/>, '\n', <meta content="zh-CN" property="og:locale"/>, '\n', <meta content="summary" name="twitter:card"/>, '\n', <meta content="zzZ5的个人博客" name="twitter:title"/>, '\n', <meta content="个人博客" name="twitter:description"/>, '\n', <link href="/atom.xml" rel="alternative" title="zzZ5的个人博客" type="application/atom+xml"/>, '\n', <link href="/assets/img/zzz5.ico" rel="icon"/>, '\n', <link href="/./main.0cf68a.css" rel="stylesheet" type="text/css"/>, '\n', <style type="text/css">
#container.show {
background: linear-gradient(200deg,#a0cfe4,#e8c37e);
}]
>>> for i in tag.children:
print(i)
<meta content="Hexo 3.8.0" name="generator"/>
<meta charset="utf-8"/>
<meta content="webkit" name="renderer"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<link href="http://www.zzz5.xyz" rel="dns-prefetch"/>
<title>zzZ5的个人博客</title>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport"/>
<meta content="个人博客" name="description"/>
<meta content="日记" name="keywords"/>
<meta content="website" property="og:type"/>
<meta content="zzZ5的个人博客" property="og:title"/>
<meta content="http://www.zzz5.xyz/index.html" property="og:url"/>
<meta content="zzZ5的个人博客" property="og:site_name"/>
<meta content="个人博客" property="og:description"/>
<meta content="zh-CN" property="og:locale"/>
<meta content="summary" name="twitter:card"/>
<meta content="zzZ5的个人博客" name="twitter:title"/>
<meta content="个人博客" name="twitter:description"/>
<link href="/atom.xml" rel="alternative" title="zzZ5的个人博客" type="application/atom+xml"/>
<link href="/assets/img/zzz5.ico" rel="icon"/>
<link href="/./main.0cf68a.css" rel="stylesheet" type="text/css"/>
<style type="text/css">
#container.show {
background: linear-gradient(200deg,#a0cfe4,#e8c37e);
}
</style>

上行遍历

标签数的上行遍历属性:

属性 描述
.parent 节点的父亲标签
.parents 节点的先辈的迭代标签, 用于循环遍历先辈节点

方法和下行遍历相同, 此处就不举例子了. 需要注意一点, soup 的 parent 为空.

平行遍历

标签数的平行遍历属性:

属性 描述
.next_sibling 返回按照HTML文本顺序的下一个平行节点标签
.previous_sibling 返回按照HTML文本顺序的上一个平行节点标签
.next_siblings 迭代类型, 返回按照HTML文本顺序的后续所有平行节点标签
.previous_sibling 迭代类型, 返回按照HTML文本顺序的前序所有平行节点标签

使用方法和上行下行遍历相同, 此处就不举例子了.

基于bs4库的HTML格式化

使用 pretiffy() 方法将HTML格式化.
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
>>> soup.prettify
<bound method Tag.prettify of <!DOCTYPE html>
<html>
<head><meta content="Hexo 3.8.0" name="generator"/>
<meta charset="utf-8"/>
<meta content="webkit" name="renderer"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<link href="http://www.zzz5.xyz" rel="dns-prefetch"/>
<title>zzZ5的个人博客</title>
<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport"/>
<meta content="个人博客" name="description"/>
<meta content="日记" name="keywords"/>
<meta content="website" property="og:type"/>
<meta content="zzZ5的个人博客" property="og:title"/>
<meta content="http://www.zzz5.xyz/index.html" property="og:url"/>
<meta content="zzZ5的个人博客" property="og:site_name"/>
<meta content="个人博客" property="og:description"/>
<meta content="zh-CN" property="og:locale"/>
<meta content="summary" name="twitter:card"/>
<meta content="zzZ5的个人博客" name="twitter:title"/>
<meta content="个人博客" name="twitter:description"/>
<link href="/atom.xml" rel="alternative" title="zzZ5的个人博客" type="application/atom+xml"/>
<link href="/assets/img/zzz5.ico" rel="icon"/>
<link href="/./main.0cf68a.css" rel="stylesheet" type="text/css"/>
<style type="text/css">
#container.show {
background: linear-gradient(200deg,#a0cfe4,#e8c37e);
}
</style>
</head></html>
>

基于bs4库的HTML内容查找方法

find_all(name, attrs, recursive, text, **kwargs)

  • name : 查找所有名字为 name 的tag,
  • keyword : 如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的’id’属性.
  • recursive : 调用tag的 find_all() 方法时, Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点, 可以使用参数 recursive=False.
  • attrs : 参数定义一个字典参数来搜索包含特殊属性的tag: findall(attrs={“data-foo”: “value”})
  • text : 参数可以搜索文档中的字符串内容.与 name 参数的可选值一样, text 参数接受字符串, 正则表达式, 列表, True.
  • **kwargs : 可选参数.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
>>> for tag in soup.find_all(True):    # 内容为True时打印所有标签
print(tag.name)
html
head
meta
meta
meta
meta
link
title
meta
meta
meta
meta
meta
meta
meta
meta
meta
meta
meta
meta
link
link
link
style
>>> soup.find_all('title')
[<title>zzZ5的个人博客</title>]
>>> soup.find_all(content='个人博客')
[<meta content="个人博客" name="description"/>, <meta content="个人博客" property="og:description"/>, <meta content="个人博客" name="twitter:description"/>]
>>>import re
>>> soup.find_all(content=re.compile('个人'))
[<meta content="个人博客" name="description"/>, <meta content="zzZ5的个人博客" property="og:title"/>, <meta content="zzZ5的个人博客" property="og:site_name"/>, <meta content="个人博客" property="og:description"/>, <meta content="zzZ5的个人博客" name="twitter:title"/>, <meta content="个人博客" name="twitter:description"/>]
>>> soup.find_all(text=re.compile('zzZ5'))
['zzZ5的个人博客']

值得一提的是:
<tag>(…) 等价于 <tag>.find_all(…)
soup(…) 等价于 soup.find_all(…)

按照CSS类名搜索tag的功能非常实用, 但标识CSS类名的关键字 class 在Python中是保留字,使用 class 做参数会导致语法错误.从Beautiful Soup的4.1.1版本开始,可以通过 class_ 参数搜索有指定CSS类名的tag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>>oup.find_all("p", class_="etitle")

# class_ 参数同样接受不同类型的 过滤器 ,字符串,正则表达式,方法或 True :
>>>soup.find_all(class_=re.compile("eti"))

>>>def has_six_characters(css_class):
>>> return css_class is not None and len(css_class) == 6
>>>soup.find_all(class_=has_six_characters)

# tag的 class 属性是 多值属性 .按照CSS类名搜索tag时,可以分别搜索tag中的每个CSS类名:
>>>css_soup = BeautifulSoup('<p class="body strikeout"></p>')
>>>css_soup.find_all("p", class_="strikeout")
[<p class="body strikeout"></p>]

>>>css_soup.find_all("p", class_="body")
[<p class="body strikeout"></p>]

# 搜索 class 属性时也可以通过CSS值完全匹配:
>>>css_soup.find_all("p", class_="body strikeout")
[<p class="body strikeout"></p>]
# 完全匹配 class 的值时,如果CSS类名的顺序与实际不符,将搜索不到结果:

soup.find_all("P", attrs={"class": "body strikeout"})
[<p class="body strikeout"></p>]

find_all() 方法还有七个扩展方法:

方法 描述
find() 搜索且只返回一个结果, 字符串类型, 同find_all()参数
find_parents() 在先辈节点中搜索, 返回列表类型, 同find_all()参数
find_parent() 在先辈节点中搜索且只返回一个结果, 字符串类型, 同find_all()参数
find_next_siblings() 在后续平行节点中搜索, 返回列表类型, 同find_all()参数
find_next_sibling() 在后续平行节点中搜索且只返回一个结果, 字符串类型, 同find_all()参数
find_previous_siblings() 在前序平行节点中搜索, 返回列表类型, 同find_all()参数
find_previous_sibling() 在前续平行节点中搜索且只返回一个结果, 字符串类型, 同find_all()参数