Beautiful Soup 是一个可以从HTML或XML文件中提取数据的python库,简单来说,它能将HTML的标签文件解析成树形结构,然后方便地获取到指定标签的对应属性。这个特性lxml差不多。
Beautiful Soup的安装Beautiful Soup 3 目前已经停止开发,推荐在现在的项目中使用Beautiful Soup 4,安装方法:
pip install beautifulsoup4
如果仅是想要解析HTML文档,只要用文档创建 BeautifulSoup 对象就可以了。Beautiful Soup会自动选择一个解析器来解析文档.但是还可以通过参数指定使用那种解析器来解析当前文档。BeautifulSoup 第一个参数应该是要被解析的文档字符串或是文件句柄,第二个参数用来标识怎样解析文档.如果第二个参数为空,那么Beautiful Soup根据当前系统安装的库自动选择解析器,解析器的优先数序: lxml, html5lib, Python标准库.在下面两种条件下解析器优先顺序会变化:
要解析的文档是什么类型: 目前支持, “html”, “xml”, 和 “html5” 指定使用哪种解析器: 目前支持, “lxml”, “html5lib”, 和 “parser”如果指定的解析器没有安装,Beautiful Soup会自动选择其它方案.目前只有 lxml 解析器支持XML文档的解析,在没有安装lxml库的情况下,创建 beautifulsoup 对象时无论是否指定使用lxml,都无法得到解析后的对象。
安装解析器:
lxml,具体请看:lxml的安装 html5lib pip install html5lib下表列出了主要的解析器,以及它们的优缺点:
解析器 使用方法 优势 劣势 Python标准库 BeautifulSoup(markup,“html.parser”)执行速度适中
文档容错能力强
Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差 lxml HTML 解析器 BeautifulSoup(markup,“lxml”)文档容错能力强
需要安装C语言库 lxml XML 解析器BeautifulSoup(markup,“xml”)
唯一支持XML的解析器
需要安装C语言库 html5lib BeautifulSoup(markup,“html5lib”)以浏览器的方式解析文档
生成HTML5格式的文档
不依赖外部扩展
推荐使用lxml作为解析器,因为效率更高. 在Python2.7.3之前的版本和Python3中3.2.2之前的版本,必须安装lxml或html5lib, 因为那些Python版本的标准库中内置的HTML解析方法不够稳定.
Beautiful Soup的使用 创建Beautiful Soup 对象 # -*- coding: utf-8 -*- frombs4importBeautifulSoup importrequests url = 'https://www.biaodianfu.com' r = requests.get(url, timeout=20) soup = BeautifulSoup(r.content, 'html.parser') printtype(soup) printsoupBeautifulSoup 构造方法的第二个参数为文档解析器,若不传入该参数,BeautifulSoup会自行选择最合适的解析器来解析文档,不过会有警告提示。也可以通过文件句柄来初始化,可先将HTML的源码保存到本地同级目录 reo.html,然后将文件名作为参数:
soup = BeautifulSoup(open('test.html'))Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
Tag NavigableString BeautifulSoup Comment TagTag 是什么?通俗点讲就是 HTML 中的一个个标签,下面我们来感受一下怎样用 Beautiful Soup 来方便地获取 Tags:
printsoup.title printsoup.h1我们可以利用 soup加标签名轻松地获取这些标签的内容,不过有一点是,它查找的是在所有内容中的第一个符合要求的标签,如果要查询所有的标签,我们在后面进行介绍。
对于 Tag,它有两个重要的属性,是 name 和 attrs:
name:soup 对象本身比较特殊,它的 name 即为 [document],对于其他内部标签,输出的值便为标签本身的名称。 attrs:一个Tag对象可以有多个属性,操作方法和字典相同,属性包含key和value,同样可以获取value的信息。 printsoup.p.attrs printsoup.p['class'] printsoup.p.get('class') #等价print soup.p['class'] tag的属性可添加、删除(del soup.b[‘class’])、修改,和字典方法相同。如果一个属性key对应多个value,则返回一个value的list。 NavigableString既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?很简单,用 .string 即可,Tag中的字符串即为NavigableString对象。
printsoup.p.string在BeautifulSoup之外使用该类型,推荐转换为Unicode:unicode(Tag.string)。tag能够包含其他tag或字符串,而NavigableString则不能包含其他对象。不支持.content,.string,find(),只支持部分遍历文档树和搜索文档树中的属性。
CommentComment 对象是一个特殊类型的 NavigableString 对象,其实输出的内容仍然不包括注释符号,但是如果不好好处理它,可能会对我们的文本处理造成意想不到的麻烦。
我们找一个带注释的标签
printsoup.a printsoup.a.string printtype(soup.a.string)运行结果如下:
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a> Elsie <class 'bs4.element.Comment'>a 标签里的内容实际上是注释,但是如果我们利用 .string 来输出它的内容,我们发现它已经把注释符号去掉了,所以这可能会给我们带来不必要的麻烦。
另外我们打印输出下它的类型,发现它是一个 Comment 类型,所以,我们在使用前最好做一下判断,判断代码如下
if type(soup.a.string)==bs4.element.Comment: printsoup.a.string上面的代码中,我们首先判断了它的类型,是否为 Comment 类型,然后再进行其他操作,如打印输出。
遍历文档树BeautifulSoup对象作为一棵树,有多个节点。对于一个节点,相对于它所在的位置,有子节点、父节点、兄弟节点。
子节点一个Tag可包含多个Tag以及字符串,这些都是这个Tag的子节点。而NavigableString不会有子节点。
直接子节点.contents tag 的 .content 属性可以将tag的子节点以列表的方式输出
printsoup.head.contents #[<title>The Dormouse's story</title>].children 它返回的不是一个 list,不过我们可以通过遍历获取所有子节点。
printsoup.head.children #<listiterator object at 0x7f71457f5710> for child in soup.body.children: printchild如果想要获得某个Tag,上述已提到方法:
soup.tag_name通过点取属性,只能获得当前名字的第一个tag,若要获取所有,需要使用搜索文档树中的方法:
soup.find_all('tag_name')tag的.contents属性可将所有子节点以列表的方式输出。可通过tag的.children生成器,对所有子节点进行遍历。.contents和.children只对获取Tag的直接子节点,.descendants可用于对Tag的所有子孙节点进行遍历。
如果tag只有一个NavigableString类型子节点,则可用.string获取。如果包含多个,使用.strings遍历。若输出的字符串中包含空格或空行,使用.stripped_strings去除。
所有子孙节点.contents 和 .children 属性仅包含tag的直接子节点,.descendants 属性可以对所有tag的子孙节点进行递归循环,和 children类似,我们也需要遍历获取其中的内容。
for child in soup.descendants: printchild如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点。如果一个tag仅有一个子节点,那么这个tag也可以使用 .string 方法,输出结果与当前唯一子节点的 .string 结果相同。
通俗点说就是:如果一个标签里面没有标签了,那么 .string 就会返回标签里面的内容。如果标签里面只有唯一的一个标签了,那么 .string 也会返回最里面的内容。例如
printsoup.head.string #The Dormouse's story printsoup.title.string #The Dormouse's story如果tag包含了多个子节点,tag就无法确定,string 方法应该调用哪个子节点的内容, .string 的输出结果是 None
获取多个内容,不过需要遍历获取,比如下面的例子
for string in soup.strings: print(repr(string)) # u"The Dormouse's story" # u'\n\n' # u"The Dormouse's story" # u'\n\n' # u'Once upon a time there were three little sisters; and their names were\n' # u'Elsie' # u',\n' # u'Lacie' # u' and\n' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'\n\n' # u'...' # u'\n'.stripped_strings ,输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容
for string in soup.stripped_strings: print(repr(string)) # u"The Dormouse's story" # u"The Dormouse's story" # u'Once upon a time there were three little sisters; and their names were' # u'Elsie' # u',' # u'Lacie' # u'and' # u'Tillie' # u';\nand they lived at the bottom of a well.' # u'...' 父节点 p = soup.p print p.parent.name #body content = soup.head.title.string printcontent.parent.name #title 全部父节点通过元素的 .parents 属性可以递归得到元素的所有父辈节点,例如
content = soup.head.title.string for parent in content.parents: printparent.name返回数据:
title head html [document] 兄弟节点兄弟节点可以理解为和本节点处在统一级的节点,.next_sibling 属性获取了该节点的下一个兄弟节点,.previous_sibling 则与之相反,如果节点不存在,则返回 None
注意:实际文档中的tag的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白,因为空白或者换行也可以被视作一个节点,所以得到的结果可能是空白或者换行
printsoup.p.next_sibling printsoup.p.prev_sibling printsoup.p.next_sibling.next_sibling 全部兄弟节点通过 .next_siblings 和 .previous_siblings 属性可以对当前节点的兄弟节点迭代输出
for siblingin soup.a.next_siblings: print(repr(sibling)) # u',