Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间.
这篇文档介绍了BeautifulSoup4中所有主要特性,并且有小例子.让我来向你展示它适合做什么,如何工作,怎样使用,如何达到你想要的效果,和处理异常情况.
文档中出现的例子在Python2.7和Python3.2中的执行结果相同
你可能在寻找 Beautiful Soup3 的文档,Beautiful Soup 3 目前已经停止开发,我们推荐在现在的项目中使用Beautiful Soup 4, 移植到BS4
这篇帮助文档已经被翻译成了其它语言:
如果你有关于BeautifulSoup的问题,可以发送邮件到 讨论组 .如果你的问题包含了一段需要转换的HTML代码,那么确保你提的问题描述中附带这段HTML文档的 代码诊断 [1]
下面的一段HTML代码将作为例子被多次用到.这是 爱丽丝梦游仙境的 的一段内容(以后内容中简称为 爱丽丝 的文档):
使用BeautifulSoup解析这段代码,能够得到一个 BeautifulSoup
的对象,并能按照标准的缩进格式的结构输出:
几个简单的浏览结构化数据的方法:
从文档中找到所有标签的链接:
从文档中获取所有文字内容:
这是你想要的吗?别着急,还有更好用的
如果你用的是新版的Debain或ubuntu,那么可以通过系统的软件包管理来安装:
$ apt-get install Python-bs4
Beautiful Soup 4 通过PyPi发布,所以如果你无法使用系统包管理安装,那么也可以通过easy_install
或 pip
来安装.包的名字是 beautifulsoup4
,这个包兼容Python2和Python3.
$ easy_install beautifulsoup4
$ pip install beautifulsoup4
(在PyPi中还有一个名字是 BeautifulSoup
的包,但那可能不是你想要的,那是 Beautiful Soup3的发布版本,因为很多项目还在使用BS3, 所以 BeautifulSoup
包依然有效.但是如果你在编写新项目,那么你应该安装的 beautifulsoup4
)
如果你没有安装 easy_install
或 pip
,那你也可以 下载BS4的源码 ,然后通过setup.py来安装.
$ Python setup.py install
如果上述安装方法都行不通,Beautiful Soup的发布协议允许你将BS4的代码打包在你的项目中,这样无须安装即可使用.
作者在Python2.7和Python3.2的版本下开发Beautiful Soup, 理论上Beautiful Soup应该在所有当前的Python版本中正常工作
Beautiful Soup发布时打包成Python2版本的代码,在Python3环境下安装时,会自动转换成Python3的代码,如果没有一个安装的过程,那么代码就不会被转换.
如果代码抛出了 ImportError
的异常: “No module named HTMLParser”, 这是因为你在Python3版本中执行Python2版本的代码.
如果代码抛出了 ImportError
的异常: “No module named html.parser”, 这是因为你在Python2版本中执行Python3版本的代码.
如果遇到上述2种情况,最好的解决方法是重新安装BeautifulSoup4.
如果在ROOT_TAG_NAME = u’[document]’代码处遇到 SyntaxError
“Invalid syntax”错误,需要将把BS4的Python代码版本从Python2转换到Python3. 可以重新安装BS4:
$ Python3 setup.py install
或在bs4的目录中执行Python代码版本转换脚本
$ 2to3-3.2 -w bs4
Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,其中一个是 lxml .根据操作系统不同,可以选择下列方法来安装lxml:
$ apt-get install Python-lxml
$ easy_install lxml
$ pip install lxml
另一个可供选择的解析器是纯Python实现的 html5lib , html5lib的解析方式与浏览器相同,可以选择下列方法来安装html5lib:
$ apt-get install Python-html5lib
$ easy_install html5lib
$ pip install html5lib
下表列出了主要的解析器,以及它们的优缺点:
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup,"html.parser") |
|
|
lxml HTML 解析器 | BeautifulSoup(markup,"lxml") |
|
|
lxml XML 解析器 |
|
|
|
html5lib | BeautifulSoup(markup,"html5lib") |
|
|
推荐使用lxml作为解析器,因为效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxml或html5lib, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定.
提示: 如果一段HTML或XML文档格式不正确的话,那么在不同的解析器中返回的结果可能是不一样的,查看 解析器之间的区别 了解更多细节
将一段文档传入BeautifulSoup 的构造方法,就能得到一个文档的对象, 可以传入一段字符串或一个文件句柄.
首先,文档被转换成Unicode,并且HTML的实例都被转换成Unicode编码
然后,Beautiful Soup选择最合适的解析器来解析这段文档,如果手动指定解析器那么Beautiful Soup会选择指定的解析器来解析文档.(参考 解析成XML ).
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种: Tag
, NavigableString
, BeautifulSoup
, Comment
.
Tag
对象与XML或HTML原生文档中的tag相同:
Tag有很多方法和属性,在 遍历文档树 和 搜索文档树 中有详细解释.现在介绍一下tag中最重要的属性: name和attributes
每个tag都有自己的名字,通过 .name
来获取:
如果改变了tag的name,那将影响所有通过当前Beautiful Soup对象生成的HTML文档:
一个tag可能有很多个属性. tag class="boldest">
有一个 “class” 的属性,值为 “boldest” . tag的属性的操作方法与字典相同:
也可以直接”点”取属性, 比如: .attrs
:
tag的属性可以被添加,删除或修改. 再说一次, tag的属性操作方法与字典一样
HTML 4定义了一系列可以包含多个值的属性.在HTML5中移除了一些,却增加更多.最常见的多值的属性是 class (一个tag可以有多个CSS的class). 还有一些属性 rel
, rev
, accept-charset
, headers
, accesskey
. 在Beautiful Soup中多值属性的返回类型是list:
如果某个属性看起来好像有多个值,但在任何版本的HTML定义中都没有被定义为多值属性,那么Beautiful Soup会将这个属性作为字符串返回
将tag转换成字符串时,多值属性会合并为一个值
如果转换的文档是XML格式,那么tag中不包含多值属性
字符串常被包含在tag内.Beautiful Soup用 NavigableString
类来包装tag中的字符串:
一个 NavigableString
字符串与Python中的Unicode字符串相同,并且还支持包含在 遍历文档树 和 搜索文档树 中的一些特性. 通过 unicode()
方法可以直接将 NavigableString
对象转换成Unicode字符串:
tag中包含的字符串不能编辑,但是可以被替换成其它的字符串,用 replace_with() 方法:
NavigableString
对象支持 遍历文档树 和 搜索文档树 中定义的大部分属性, 并非全部.尤其是,一个字符串不能包含其它内容(tag能够包含字符串或是其它tag),字符串不支持.contents
或 .string
属性或 find()
方法.
如果想在Beautiful Soup之外使用 NavigableString
对象,需要调用 unicode()
方法,将该对象转换成普通的Unicode字符串,否则就算Beautiful Soup已方法已经执行结束,该对象的输出也会带有对象的引用地址.这样会浪费内存.
BeautifulSoup
对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag
对象,它支持 遍历文档树 和 搜索文档树 中描述的大部分的方法.
因为 BeautifulSoup
对象并不是真正的HTML或XML的tag,所以它没有name和attribute属性.但有时查看它的 .name
属性是很方便的,所以 BeautifulSoup
对象包含了一个值为 “[document]” 的特殊属性 .name
Tag
, NavigableString
, BeautifulSoup
几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象.容易让人担心的内容是文档的注释部分:
Comment
对象是一个特殊类型的 NavigableString
对象:
但是当它出现在HTML文档中时, Comment
对象会使用特殊的格式输出:
Beautiful Soup中定义的其它类型都可能会出现在XML的文档中: CData
,ProcessingInstruction
, Declaration
, Doctype
.与 Comment
对象类似,这些类都是NavigableString
的子类,只是添加了一些额外的方法的字符串独享.下面是用CDATA来替代注释的例子:
还拿”爱丽丝梦游仙境”的文档来做例子:
通过这段例子来演示怎样从文档的一段内容找到另一段内容
一个Tag可能包含多个字符串或其它的Tag,这些都是这个Tag的子节点.Beautiful Soup提供了许多操作和遍历子节点的属性.
注意: Beautiful Soup中字符串节点不支持这些属性,因为字符串没有子节点
操作文档树最简单的方法就是告诉它你想获取的tag的name.如果想获取
标签,只要用soup.head
:
这是个获取tag的小窍门,可以在文档树的tag中多次调用这个方法.下面的代码可以获取
标签中的第一个标签:通过点取属性的方式只能获得当前名字的第一个tag:
如果想要得到所有的标签,或是通过名字得到比一个tag更多的内容的时候,就需要用到Searching the tree 中描述的方法,比如: find_all()
tag的 .contents
属性可以将tag的子节点以列表的方式输出:
BeautifulSoup
对象本身一定会包含子节点,也就是说标签也是 BeautifulSoup
对象的子节点:
字符串没有 .contents
属性,因为字符串没有子节点:
通过tag的 .children
生成器,可以对tag的子节点进行循环:
.contents
和 .children
属性仅包含tag的直接子节点.例如,
但是
.descendants
属性可以对所有tag的子孙节点进行递归循环 [5] :
上面的例子中,
标签只有一个子节点,但是有2个子孙节点:节点和的子节点,BeautifulSoup
有一个直接子节点(节点),却有很多子孙节点:
如果tag只有一个 NavigableString
类型子节点,那么这个tag可以使用 .string
得到子节点:
如果一个tag仅有一个子节点,那么这个tag也可以使用 .string
方法,输出结果与当前唯一子节点的 .string
结果相同:
如果tag包含了多个子节点,tag就无法确定 .string
方法应该调用哪个子节点的内容,.string
的输出结果是 None
:
如果tag中包含多个字符串 [2] ,可以使用 .strings
来循环获取:
输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings
可以去除多余空白内容:
全部是空格的行会被忽略掉,段首和段末的空白会被删除
继续分析文档树,每个tag或字符串都有父节点:被包含在某个tag中
通过 .parent
属性来获取某个元素的父节点.在例子“爱丽丝”的文档中,
文档title的字符串也有父节点:
文档的顶层节点比如的父节点是 BeautifulSoup
对象:
BeautifulSoup
对象的 .parent
是None:
看一段简单的例子:
因为标签和
在文档树中,使用 .next_sibling
和 .previous_sibling
属性来查询兄弟节点:
标签有 .next_sibling
属性,但是没有 .previous_sibling
属性,因为标签在同级节点中是第一个.同理,.previous_sibling
属性,却没有 .next_sibling
属性:
例子中的字符串“text1”和“text2”不是兄弟节点,因为它们的父节点不同:
实际文档中的tag的 .next_sibling
和 .previous_sibling
属性通常是字符串或空白. 看看“爱丽丝”文档:
如果以为第一个标签的 .next_sibling
结果是第二个标签,那就错了,真实结果是第一个标签和第二个标签之间的顿号和换行符:
通过 .next_siblings
和 .previous_siblings
属性可以对当前节点的兄弟节点迭代输出:
看一下“爱丽丝” 文档:
HTML解析器把这段字符串转换成一连串的事件: “打开标签”,”打开一个
标签”,”打开一个标签”,等等.Beautiful Soup提供了重现解析器初始化过程的方法.
.next_element
属性指向解析过程中下一个被解析的对象(字符串或tag),结果可能与.next_sibling
相同,但通常是不一样的.
这是“爱丽丝”文档中最后一个标签,它的 .next_sibling
结果是一个字符串,因为当前的解析过程 [2] 因为当前的解析过程因为遇到了标签而中断了:
但这个标签的 .next_element
属性结果是在标签被解析之后的解析内容,不是标签后的句子部分,应该是字符串”Tillie”:
这是因为在原始文档中,字符串“Tillie” 在分号前出现,解析器先进入标签,然后是字符串“Tillie”,然后关闭标签,然后是分号和剩余部分.分号与标签在同一层级,但是字符串“Tillie”会被先解析.
.previous_element
属性刚好与 .next_element
相反,它指向当前被解析的对象的前一个解析对象:
通过 .next_elements
和 .previous_elements
的迭代器就可以向前或向后访问文档的解析内容,就好像文档正在被解析一样:
Beautiful Soup定义了很多搜索方法,这里着重介绍2个: find()
和 find_all()
.其它方法的参数和用法类似,请读者举一反三.
再以“爱丽丝”文档作为例子:
使用 find_all()
类似的方法可以查找到想要查找的文档内容
介绍 find_all()
方法前,先介绍一下过滤器的类型 [3] ,这些过滤器贯穿整个搜索的API.过滤器可以被用在tag的name中,节点的属性中,字符串中或他们的混合中.
最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的标签:
如果传入字节码参数,Beautiful Soup会当作UTF-8编码,可以传入一段Unicode 编码来避免Beautiful Soup解析编码出错
如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match()
来匹配内容.下面例子中找出所有以b开头的标签,这表示
下面代码找出所有名字中包含”t”的标签:
True
可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点
如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数 [4] ,如果这个方法返回 True
表示当前元素匹配并且被找到,如果不是则反回 False
下面方法校验了当前元素,如果包含 class
属性却不包含 id
属性,那么将返回 True
:
将这个方法作为参数传入 find_all()
方法,将得到所有
标签:
返回结果中只有
标签没有标签,因为标签还定义了”id”,没有返回和,因为和中没有定义”class”属性.
通过一个方法来过滤一类标签属性的时候, 这个方法的参数是要被过滤的属性的值, 而不是这个标签. 下面的例子是找出 href
属性不符合指定正则的 a
标签.
标签过滤方法可以使用复杂方法. 下面的例子可以过滤出前后都有文字的标签.
现在来了解一下搜索方法的细节
find_all( name , attrs , recursive , string , **kwargs )
find_all()
方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件.这里有几个例子:
有几个方法很相似,还有几个方法是新的,参数中的 string
和 id
是什么含义? 为什么find_all("p", "title")
返回的是CSS Class为”title”的
标签? 我们来仔细看一下find_all()
的参数
name
参数可以查找所有名字为 name
的tag,字符串对象会被自动忽略掉.
简单的用法如下:
重申: 搜索 name
参数的值可以使任一类型的 过滤器 ,字符窜,正则表达式,列表,方法或是True
.
如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id
的参数,Beautiful Soup会搜索每个tag的”id”属性.
如果传入 href
参数,Beautiful Soup会搜索每个tag的”href”属性:
搜索指定名字的属性时可以使用的参数值包括 字符串 , 正则表达式 , 列表, True .
下面的例子在文档树中查找所有包含 id
属性的tag,无论 id
的值是什么:
使用多个指定名字的参数可以同时过滤tag的多个属性:
有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性:
但是可以通过 find_all()
方法的 attrs
参数定义一个字典参数来搜索包含特殊属性的tag:
按照CSS类名搜索tag的功能非常实用,但标识CSS类名的关键字 class
在Python中是保留字,使用 class
做参数会导致语法错误.从Beautiful Soup的4.1.1版本开始,可以通过 class_
参数搜索有指定CSS类名的tag:
class_
参数同样接受不同类型的 过滤器
,字符串,正则表达式,方法或 True
:
tag的 class
属性是 多值属性 .按照CSS类名搜索tag时,可以分别搜索tag中的每个CSS类名:
搜索 class
属性时也可以通过CSS值完全匹配:
完全匹配 class
的值时,如果CSS类名的顺序与实际不符,将搜索不到结果:
string
参数通过 string
参数可以搜搜文档中的字符串内容.与 name
参数的可选值一样, string
参数接受 字符串 , 正则表达式 , 列表, True . 看例子:
虽然 string
参数用于搜索字符串,还可以与其它参数混合使用来过滤tag.Beautiful Soup会找到 .string
方法与 string
参数值相符的tag.下面代码用来搜索内容里面包含“Elsie”的标签:
limit
参数find_all()
方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit
参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit
的限制时,就停止搜索返回结果.
文档树中有3个tag符合搜索条件,但结果只返回了2个,因为我们限制了返回数量:
recursive
参数调用tag的 find_all()
方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False
.
一段简单的文档:
是否使用 recursive
参数的搜索结果:
这是文档片段
recursive=False
参数之后,只能查找直接子节点,这样就查不到 Beautiful Soup 提供了多种DOM树搜索方法. 这些方法都使用了类似的参数定义. 比如这些方法: find_all()
: name
, attrs
, text
, limit
. 但是只有 find_all()
和 find()
支持 recursive
参数.
find_all()
一样调用tagfind_all()
几乎是Beautiful Soup中最常用的搜索方法,所以我们定义了它的简写方法.BeautifulSoup
对象和 tag
对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all()
方法相同,下面两行代码是等价的:
这两行代码也是等价的:
find( name , attrs , recursive , string , **kwargs )
find_all()
方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个
find_all()
方法来查找标签就不太合适, 使用 find_all
方法并设置 limit=1
参数不如直接使用 find()
方法.下面两行代码是等价的:
唯一的区别是 find_all()
方法的返回结果是值包含一个元素的列表,而 find()
方法直接返回结果.
find_all()
方法没有找到目标是返回空列表, find()
方法找不到目标时,返回 None
.
soup.head.title
是 tag的名字 方法的简写.这个简写的原理就是多次调用当前tag的 find()
方法:
find_parents( name , attrs , recursive , string , **kwargs )
find_parent( name , attrs , recursive , string , **kwargs )
我们已经用了很大篇幅来介绍 find_all()
和 find()
方法,Beautiful Soup中还有10个用于搜索的API.它们中的五个用的是与 find_all()
相同的搜索参数,另外5个与 find()
方法的搜索参数类似.区别仅是它们搜索文档的不同部分.
记住: find_all()
和 find()
只搜索当前节点的所有子节点,孙子节点等. find_parents()
和find_parent()
用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同,搜索文档搜索文档包含的内容. 我们从一个文档中的一个叶子节点开始:
find_next_siblings( name , attrs , recursive , string , **kwargs )
find_next_sibling( name , attrs , recursive , string , **kwargs )
这2个方法通过 .next_siblings 属性对当tag的所有后面解析 [5] 的兄弟tag节点进行迭代,find_next_siblings()
方法返回所有符合条件的后面的兄弟节点, find_next_sibling()
只返回符合条件的后面的第一个tag节点.
find_previous_siblings( name , attrs , recursive , string , **kwargs )
find_previous_sibling( name , attrs , recursive , string , **kwargs )
这2个方法通过 .previous_siblings 属性对当前tag的前面解析 [5] 的兄弟tag节点进行迭代,find_previous_siblings()
方法返回所有符合条件的前面的兄弟节点,find_previous_sibling()
方法返回第一个符合条件的前面的兄弟节点:
find_all_next( name , attrs , recursive , string , **kwargs )
find_next( name , attrs , recursive , string , **kwargs )
这2个方法通过 .next_elements 属性对当前tag的之后的 [5] tag和字符串进行迭代,find_all_next()
方法返回所有符合条件的节点, find_next()
方法返回第一个符合条件的节点:
第一个例子中,字符串 “Elsie”也被显示出来,尽管它被包含在我们开始查找的标签的里面.第二个例子中,最后一个 标签也被显示出来,尽管它与我们开始查找位置的标签不属于同一部分.例子中,搜索的重点是要匹配过滤器的条件,并且在文档中出现的顺序而不是开始查找的元素的位置.
find_all_previous( name , attrs , recursive , string , **kwargs )
find_previous( name , attrs , recursive , string , **kwargs )
这2个方法通过 .previous_elements 属性对当前节点前面 [5] 的tag和字符串进行迭代,find_all_previous()
方法返回所有符合条件的节点, find_previous()
方法返回第一个符合条件的节点.
find_all_previous("p")
返回了文档中的第一段(class=”title”的那段),但还返回了第二段,
标签包含了我们开始查找的标签.不要惊讶,这段代码的功能是查找所有出现在指定标签之前的 标签,因为这个
Beautiful Soup支持大部分的CSS选择器 http://www.w3.org/TR/CSS2/selector.html [6] , 在Tag
或 BeautifulSoup
对象的 .select()
方法中传入字符串参数, 即可使用CSS选择器的语法找到tag:
通过tag标签逐层查找:
找到某个tag标签下的直接子标签 [6] :
找到兄弟节点标签:
通过CSS的类名查找:
通过tag的id查找:
同时用多种CSS选择器查询元素:
通过是否存在某个属性来查找:
通过属性的值来查找:
通过语言设置来查找:
返回查找到的元素的第一个
对于熟悉CSS选择器语法的人来说这是个非常方便的方法.Beautiful Soup也支持CSS选择器API, 如果你仅仅需要CSS选择器的功能,那么直接使用 lxml
也可以, 而且速度更快,支持更多的CSS选择器语法,但Beautiful Soup整合了CSS选择器的语法和自身方便使用API.
Beautiful Soup的强项是文档树的搜索,但同时也可以方便的修改文档树
在 Attributes 的章节中已经介绍过这个功能,但是再看一遍也无妨. 重命名一个tag,改变属性的值,添加或删除属性:
给tag的 .string
属性赋值,就相当于用当前的内容替代了原来的内容:
注意: 如果当前的tag包含了其它tag,那么给它的 .string
属性赋值会覆盖掉原有的所有内容包括子tag
Tag.append()
方法想tag中添加内容,就好像Python的列表的 .append()
方法:
Tag.insert()
方法与 Tag.append()
方法类似,区别是不会把新元素添加到父节点 .contents
属性的最后,而是把元素插入到指定的位置.与Python列表总的 .insert()
方法的用法下同:
insert_before()
方法在当前tag或文本节点前插入内容:
insert_after()
方法在当前tag或文本节点后插入内容:
Tag.clear()
方法移除当前tag的内容:
PageElement.extract()
方法将当前tag移除文档树,并作为方法结果返回:
这个方法实际上产生了2个文档树: 一个是用来解析原始文档的 BeautifulSoup
对象,另一个是被移除并且返回的tag.被移除并返回的tag可以继续调用 extract
方法:
Tag.decompose()
方法将当前节点移除文档树并完全销毁:
PageElement.replace_with()
方法移除文档树中的某段内容,并用新tag或文本节点替代它:
replace_with()
方法返回被替代的tag或文本节点,可以用来浏览或添加到文档树其它地方
PageElement.wrap()
方法可以对指定的tag元素进行包装 [8] ,并返回包装后的结果:
该方法在 Beautiful Soup 4.0.5 中添加
Tag.unwrap()
方法与 wrap()
方法相反.将移除tag内的所有tag标签,该方法常被用来进行标记的解包:
与 replace_with()
方法相同, unwrap()
方法返回被移除的tag
prettify()
方法将Beautiful Soup的文档树格式化后以Unicode编码输出,每个XML/HTML标签都独占一行
BeautifulSoup
对象和它的tag节点都可以调用 prettify()
方法:
如果只想得到结果字符串,不重视格式,那么可以对一个 BeautifulSoup
对象或 Tag
对象使用Python的 unicode()
或 str()
方法:
str()
方法返回UTF-8编码的字符串,可以指定 编码 的设置.
还可以调用 encode()
方法获得字节码或调用 decode()
方法获得Unicode.
Beautiful Soup输出是会将HTML中的特殊字符转换成Unicode,比如“&lquot;”:
如果将文档转换成字符串,Unicode编码会被编码成UTF-8.这样就无法正确显示HTML特殊字符了:
如果只想得到tag中包含的文本内容,那么可以嗲用 get_text()
方法,这个方法获取到tag中包含的所有文版内容包括子孙tag中的内容,并将结果作为Unicode字符串返回:
可以通过参数指定tag的文本内容的分隔符:
还可以去除获得文本内容的前后空白:
或者使用 .stripped_strings 生成器,获得文本列表后手动处理列表:
如果仅是想要解析HTML文档,只要用文档创建 BeautifulSoup
对象就可以了.Beautiful Soup会自动选择一个解析器来解析文档.但是还可以通过参数指定使用那种解析器来解析当前文档.
BeautifulSoup
第一个参数应该是要被解析的文档字符串或是文件句柄,第二个参数用来标识怎样解析文档.如果第二个参数为空,那么Beautiful Soup根据当前系统安装的库自动选择解析器,解析器的优先数序: lxml, html5lib, Python标准库.在下面两种条件下解析器优先顺序会变化:
- 要解析的文档是什么类型: 目前支持, “html”, “xml”, 和 “html5”
- 指定使用哪种解析器: 目前支持, “lxml”, “html5lib”, 和 “html.parser”
安装解析器 章节介绍了可以使用哪种解析器,以及如何安装.
如果指定的解析器没有安装,Beautiful Soup会自动选择其它方案.目前只有 lxml 解析器支持XML文档的解析,在没有安装lxml库的情况下,创建 beautifulsoup
对象时无论是否指定使用lxml,都无法得到解析后的对象
Beautiful Soup为不同的解析器提供了相同的接口,但解析器本身时有区别的.同一篇文档被不同的解析器解析后可能会生成不同结构的树型文档.区别最大的是HTML解析器和XML解析器,看下面片段被解析成HTML结构:
因为空标签不符合HTML标准,所以解析器把它解析成
同样的文档使用XML解析如下(解析XML需要安装lxml库).注意,空标签依然被保留,并且文档前添加了XML头,而不是被包含在标签内:
HTML解析器之间也有区别,如果被解析的HTML文档是标准格式,那么解析器之间没有任何差别,只是解析速度不同,结果都会返回正确的文档树.
但是如果被解析文档不是标准格式,那么不同的解析器返回结果可能不同.下面例子中,使用lxml解析错误格式的文档,结果
标签被直接忽略掉了:使用html5lib库解析相同文档会得到不同的结果:
html5lib库没有忽略掉
标签,而是自动补全了标签,还给文档树添加了标签.使用pyhton内置库解析结果如下:
与lxml [7] 库类似的,Python内置库忽略掉了
标签,与html5lib库不同的是标准库没有尝试创建符合标准的文档格式或将文档片段包含在标签内,与lxml不同的是标准库甚至连标签都没有尝试去添加. ”是错误格式,所以以上解析方式都能算作”正确”,html5lib库使用的是HTML5的部分标准,所以最接近”正确”.不过所有解析器的结构都能够被认为是”正常”的.不同的解析器可能影响代码执行结果,如果在分发给别人的代码中使用了 BeautifulSoup
,那么最好注明使用了哪种解析器,以减少不必要的麻烦.
任何HTML或XML文档都有自己的编码方式,比如ASCII 或 UTF-8,但是使用Beautiful Soup解析后,文档都被转换成了Unicode:
这不是魔术(但很神奇),Beautiful Soup用了 编码自动检测 子库来识别当前文档编码并转换成Unicode编码. BeautifulSoup
对象的 .original_encoding
属性记录了自动识别编码的结果:
编码自动检测 功能大部分时候都能猜对编码格式,但有时候也会出错.有时候即使猜测正确,也是在逐个字节的遍历整个文档后才猜对的,这样很慢.如果预先知道文档编码,可以设置编码参数来减少自动检查编码出错的概率并且提高文档解析速度.在创建 BeautifulSoup
对象的时候设置 from_encoding
参数.
下面一段文档用了ISO-8859-8编码方式,这段文档太短,结果Beautiful Soup以为文档是用ISO-8859-7编码:
通过传入 from_encoding
参数来指定编码方式:
如果仅知道文档采用了Unicode编码, 但不知道具体编码. 可以先自己猜测, 猜测错误(依旧是乱码)时, 可以把错误编码作为 exclude_encodings
参数, 这样文档就不会尝试使用这种编码了解码了. 译者备注: 在没有指定编码的情况下, BS会自己猜测编码, 把不正确的编码排除掉, BS就更容易猜到正确编码.
猜测结果是 Windows-1255 编码, 猜测结果可能不够准确, 但是 Windows-1255 编码是 ISO-8859-8 的扩展集, 所以猜测结果已经十分接近了, 并且不影响使用. (exclude_encodings
参数是 4.4.0版本的新功能)
少数情况下(通常是UTF-8编码的文档中包含了其它编码格式的文件),想获得正确的Unicode编码就不得不将文档中少数特殊编码字符替换成特殊Unicode编码,“REPLACEMENT CHARACTER” (U+FFFD, �) [9] . 如果Beautifu Soup猜测文档编码时作了特殊字符的替换,那么Beautiful Soup会把 UnicodeDammit
或 BeautifulSoup
对象的.contains_replacement_characters
属性标记为 True
.这样就可以知道当前文档进行Unicode编码后丢失了一部分特殊内容字符.如果文档中包含�而.contains_replacement_characters
属性是 False
,则表示�就是文档中原来的字符,不是转码失败.
通过Beautiful Soup输出文档时,不管输入文档是什么编码方式,输出编码均为UTF-8编码,下面例子输入文档是Latin-1编码:
注意,输出文档中的标签的编码设置已经修改成了与输出编码一致的UTF-8.
如果不想用UTF-8编码输出,可以将编码方式传入 prettify()
方法:
还可以调用 BeautifulSoup
对象或任意节点的 encode()
方法,就像Python的字符串调用encode()
方法一样:
如果文档中包含当前编码不支持的字符,那么这些字符将呗转换成一系列XML特殊字符引用,下面例子中包含了Unicode编码字符SNOWMAN:
SNOWMAN字符在UTF-8编码中可以正常显示(看上去像是☃),但有些编码不支持SNOWMAN字符,比如ISO-Latin-1或ASCII,那么在这些编码中SNOWMAN字符会被转换成“☃”:
译者备注: UnicodeDammit 是BS内置库, 主要用来猜测文档编码.
编码自动检测 功能可以在Beautiful Soup以外使用,检测某段未知编码时,可以使用这个方法:
如果Python中安装了 chardet
或 cchardet
那么编码检测功能的准确率将大大提高. 输入的字符越多,检测结果越精确,如果事先猜测到一些可能编码, 那么可以将猜测的编码作为参数,这样将优先检测这些编码:
编码自动检测 功能中有2项功能是Beautiful Soup库中用不到的
使用Unicode时,Beautiful Soup还会智能的把引号 [10] 转换成HTML或XML中的特殊字符:
也可以把引号转换为ASCII码:
很有用的功能,但是Beautiful Soup没有使用这种方式.默认情况下,Beautiful Soup把引号转换成Unicode:
有时文档的大部分都是用UTF-8,但同时还包含了Windows-1252编码的字符,就像微软的智能引号 [10] 一样. 一些包含多个信息的来源网站容易出现这种情况.UnicodeDammit.detwingle()
方法可以把这类文档转换成纯UTF-8编码格式,看个简单的例子:
这段文档很杂乱,snowmen是UTF-8编码,引号是Windows-1252编码,直接输出时不能同时显示snowmen和引号,因为它们编码不同:
如果对这段文档用UTF-8解码就会得到 UnicodeDecodeError
异常,如果用Windows-1252解码就回得到一堆乱码. 幸好, UnicodeDammit.detwingle()
方法会吧这段字符串转换成UTF-8编码,允许我们同时显示出文档中的snowmen和引号:
UnicodeDammit.detwingle()
方法只能解码包含在UTF-8编码中的Windows-1252编码内容,但这解决了最常见的一类问题.
在创建 BeautifulSoup
或 UnicodeDammit
对象前一定要先对文档调用UnicodeDammit.detwingle()
确保文档的编码方式正确.如果尝试去解析一段包含Windows-1252编码的UTF-8文档,就会得到一堆乱码,比如: ☃☃☃“I like snowmen!”.
UnicodeDammit.detwingle()
方法在Beautiful Soup 4.1.0版本中新增
两个 NavigableString
或 Tag
对象具有相同的HTML或XML结构时, Beautiful Soup就判断这两个对象相同. 这个例子中, 2个 标签在 BS 中是相同的, 尽管他们在文档树的不同位置, 但是具有相同的表象: “pizza”
如果想判断两个对象是否严格的指向同一个对象可以通过 is
来判断
copy.copy()
方法可以复制任意 Tag
或 NavigableString
对象
复制后的对象跟与对象是相等的, 但指向不同的内存地址
源对象和复制对象的区别是源对象在文档树中, 而复制后的对象是独立的还没有添加到文档树中. 复制后对象的效果跟调用了 extract()
方法相同.
这是因为相等的对象不能同时插入相同的位置
如果仅仅因为想要查找文档中的标签而将整片文档进行解析,实在是浪费内存和时间.最快的方法是从一开始就把标签以外的东西都忽略掉. SoupStrainer
类可以定义文档的某段内容,这样搜索文档时就不必先解析整篇文档,只会解析在 SoupStrainer
中定义过的文档. 创建一个 SoupStrainer
对象并作为 parse_only
参数给 BeautifulSoup
的构造方法即可.
SoupStrainer
类接受与典型搜索方法相同的参数:name , attrs , recursive , string , **kwargs。下面举例说明三种 SoupStrainer
对象:
再拿“爱丽丝”文档来举例,来看看使用三种 SoupStrainer
对象做参数会有什么不同:
还可以将 SoupStrainer
作为参数传入 搜索文档树 中提到的方法.这可能不是个常用用法,所以还是提一下:
如果想知道Beautiful Soup到底怎样处理一份文档,可以将文档传入 diagnose()
方法(Beautiful Soup 4.2.0中新增),Beautiful Soup会输出一份报告,说明不同的解析器会怎样处理这段文档,并标出当前的解析过程会使用哪种解析器:
diagnose()
方法的输出结果可能帮助你找到问题的原因,如果不行,还可以把结果复制出来以便寻求他人的帮助
文档解析错误有两种.一种是崩溃,Beautiful Soup尝试解析一段文档结果却抛除了异常,通常是 HTMLParser.HTMLParseError
.还有一种异常情况,是Beautiful Soup解析后的文档树看起来与原来的内容相差很多.
这些错误几乎都不是Beautiful Soup的原因,这不会是因为Beautiful Soup得代码写的太优秀,而是因为Beautiful Soup没有包含任何文档解析代码.异常产生自被依赖的解析器,如果解析器不能很好的解析出当前的文档,那么最好的办法是换一个解析器.更多细节查看 安装解析器 章节.
最常见的解析错误是 HTMLParser.HTMLParseError: malformed start tag
和HTMLParser.HTMLParseError: bad end tag
.这都是由Python内置的解析器引起的,解决方法是 安装lxml或html5lib
最常见的异常现象是当前文档找不到指定的Tag,而这个Tag光是用眼睛就足够发现的了.find_all()
方法返回 [] ,而 find()
方法返回 None .这是Python内置解析器的又一个问题: 解析器会跳过那些它不知道的tag.解决方法还是 安装lxml或html5lib
SyntaxError: Invalid syntax
(异常位置在代码行: ROOT_TAG_NAME = u'[document]'
),因为Python2版本的代码没有经过迁移就在Python3中窒息感ImportError: No module named HTMLParser
因为在Python3中执行Python2版本的Beautiful SoupImportError: No module named html.parser
因为在Python2中执行Python3版本的Beautiful SoupImportError: No module named BeautifulSoup
因为在没有安装BeautifulSoup3库的Python环境下执行代码,或忘记了BeautifulSoup4的代码需要从 bs4
包中引入ImportError: No module named bs4
因为当前Python环境下还没有安装BeautifulSoup4默认情况下,Beautiful Soup会将当前文档作为HTML格式解析,如果要解析XML文档,要在BeautifulSoup
构造方法中加入第二个参数 “xml”:
当然,还需要 安装lxml
BeautifulSoup
的构造方法中中指定解析器UnicodeEncodeError: 'charmap' codec can't encode character u'\xfoo' inposition bar
(或其它类型的 UnicodeEncodeError
)的错误,主要是两方面的错误(都不是Beautiful Soup的原因),第一种是正在使用的终端(console)无法显示部分Unicode,参考Python wiki ,第二种是向文件写入时,被写入文件不支持部分Unicode,这时只要用u.encode("utf8")
方法将编码转换为UTF-8.KeyError: [attr]
因为调用 tag['attr']
方法而引起,因为这个tag没有定义该属性.出错最多的是 KeyError: 'href'
和 KeyError: 'class'
.如果不确定某个属性是否存在时,用tag.get('attr')
方法去获取它,跟获取Python字典的key一样AttributeError: 'ResultSet' object has no attribute 'foo'
错误通常是因为把find_all()
的返回结果当作一个tag或文本节点使用,实际上返回结果是一个列表或ResultSet
对象的字符串,需要对结果进行循环才能得到每个节点的 .foo
属性.或者使用 find()
方法仅获取到一个节点AttributeError: 'NoneType' object has no attribute 'foo'
这个错误通常是在调用了 find()
方法后直节点取某个属性 .foo 但是 find()
方法并没有找到任何结果,所以它的返回值是 None
.需要找出为什么 find()
的返回值是 None
.Beautiful Soup对文档的解析速度不会比它所依赖的解析器更快,如果对计算时间要求很高或者计算机的时间比程序员的时间更值钱,那么就应该直接使用 lxml .
换句话说,还有提高Beautiful Soup效率的办法,使用lxml作为解析器.Beautiful Soup用lxml做解析器比用html5lib或Python内置解析器速度快很多.
安装 cchardet 后文档的解码的编码检测会速度更快
解析部分文档 不会节省多少解析时间,但是会节省很多内存,并且搜索时也会变得更快.
Beautiful Soup 3是上一个发布版本,目前已经停止维护.Beautiful Soup 3库目前已经被几个主要的linux平台添加到源里:
$ apt-get install Python-beautifulsoup
在PyPi中分发的包名字是 BeautifulSoup
:
$ easy_install BeautifulSoup
$ pip install BeautifulSoup
或通过 Beautiful Soup 3.2.0源码包 安装
Beautiful Soup 3的在线文档查看 这里 .
只要一个小变动就能让大部分的Beautiful Soup 3代码使用Beautiful Soup 4的库和方法—-修改 BeautifulSoup
对象的引入方式:
修改为:
ImportError
异常“No module named BeautifulSoup”,原因可能是尝试执行Beautiful Soup 3,但环境中只安装了Beautiful Soup 4库ImportError
异常“No module named bs4”,原因可能是尝试运行Beautiful Soup 4的代码,但环境中只安装了Beautiful Soup 3.虽然BS4兼容绝大部分BS3的功能,但BS3中的大部分方法已经不推荐使用了,就方法按照PEP8标准 重新定义了方法名.很多方法都重新定义了方法名,但只有少数几个方法没有向下兼容.
上述内容就是BS3迁移到BS4的注意事项
Beautiful Soup 3曾使用Python的 SGMLParser
解析器,这个模块在Python3中已经被移除了.Beautiful Soup 4默认使用系统的 html.parser
,也可以使用lxml或html5lib扩展库代替.查看 安装解析器 章节
因为解析器 html.parser
与 SGMLParser
不同. BS4 和 BS3 处理相同的文档会产生不同的对象结构. 使用lxml或html5lib解析文档的时候, 如果添加了 html.parser
参数, 解析的对象又回发生变化. 如果发生了这种情况, 只能修改对应的处文档结果处理代码了.
renderContents
-> encode_contents
replaceWith
-> replace_with
replaceWithChildren
-> unwrap
findAll
-> find_all
findAllNext
-> find_all_next
findAllPrevious
-> find_all_previous
findNext
-> find_next
findNextSibling
-> find_next_sibling
findNextSiblings
-> find_next_siblings
findParent
-> find_parent
findParents
-> find_parents
findPrevious
-> find_previous
findPreviousSibling
-> find_previous_sibling
findPreviousSiblings
-> find_previous_siblings
nextSibling
-> next_sibling
previousSibling
-> previous_sibling
Beautiful Soup构造方法的参数部分也有名字变化:
BeautifulSoup(parseOnlyThese=...)
-> BeautifulSoup(parse_only=...)
BeautifulSoup(fromEncoding=...)
-> BeautifulSoup(from_encoding=...)
为了适配Python3,修改了一个方法名:
Tag.has_key()
-> Tag.has_attr()
修改了一个属性名,让它看起来更专业点:
Tag.isSelfClosing
-> Tag.is_empty_element
修改了下面3个属性的名字,以免雨Python保留字冲突.这些变动不是向下兼容的,如果在BS3中使用了这些属性,那么在BS4中这些代码无法执行.
Tag.next
-> Tag.next_element
Tag.previous
-> Tag.previous_element
将下列生成器按照PEP8标准重新命名,并转换成对象的属性:
childGenerator()
-> children
nextGenerator()
-> next_elements
nextSiblingGenerator()
-> next_siblings
previousGenerator()
-> previous_elements
previousSiblingGenerator()
-> previous_siblings
recursiveChildGenerator()
-> descendants
parentGenerator()
-> parents
所以迁移到BS4版本时要替换这些代码:
替换为:
(两种调用方法现在都能使用)
BS3中有的生成器循环结束后会返回 None
然后结束.这是个bug.新版生成器不再返回 None
.
BS4中增加了2个新的生成器, .strings 和 stripped_strings . .strings
生成器返回NavigableString对象, .stripped_strings
方法返回去除前后空白的Python的string对象.
BS4中移除了解析XML的 BeautifulStoneSoup
类.如果要解析一段XML文档,使用BeautifulSoup
构造方法并在第二个参数设置为“xml”.同时 BeautifulSoup
构造方法也不再识别 isHTML
参数.
Beautiful Soup处理XML空标签的方法升级了.旧版本中解析XML时必须指明哪个标签是空标签. 构造方法的 selfClosingTags
参数已经不再使用.新版Beautiful Soup将所有空标签解析为空元素,如果向空元素中添加子节点,那么这个元素就不再是空元素了.
HTML或XML实体都会被解析成Unicode字符,Beautiful Soup 3版本中有很多处理实体的方法,在新版中都被移除了. BeautifulSoup
构造方法也不再接受 smartQuotesTo
或convertEntities
参数. 编码自动检测 方法依然有 smart_quotes_to
参数,但是默认会将引号转换成Unicode.内容配置项 HTML_ENTITIES
, XML_ENTITIES
和 XHTML_ENTITIES
在新版中被移除.因为它们代表的特性已经不再被支持.
如果在输出文档时想把Unicode字符转换成HTML实体,而不是输出成UTF-8编码,那就需要用到 输出格式 的方法.
Tag.string 属性现在是一个递归操作.如果A标签只包含了一个B标签,那么A标签的.string属性值与B标签的.string属性值相同.
多值属性 比如 class
属性包含一个他们的值的列表,而不是一个字符串.这可能会影响到如何按照CSS类名哦搜索tag.
如果使用 find*
方法时同时传入了 string 参数 和 name 参数 .Beautiful Soup会搜索指定name的tag,并且这个tag的 Tag.string 属性包含text参数的内容.结果中不会包含字符串本身.旧版本中Beautiful Soup会忽略掉tag参数,只搜索text参数.
BeautifulSoup
构造方法不再支持 markupMassage 参数.现在由解析器负责文档的解析正确性.
很少被用到的几个解析器方法在新版中被移除,比如 ICantBelieveItsBeautifulSoup
和BeautifulSOAP
.现在由解析器完全负责如何解释模糊不清的文档标记.
prettify()
方法在新版中返回Unicode字符串,不再返回字节流.