Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库, BeautifulSoup在解析的时候是依赖于解析器的,它除了支持Python标准库中的HTML解析器,还支持一些第三方的解析器比如lxml等。可以从其官网得到更详细的信息:http://beautifulsoup.readthedocs.io/zh_CN/latest/#。在windows环境下,直接通过pip install beautifulsoup4
安装使用它把,同时也可以将lxml解析库安装上pip install lxml
。
一、BeautifulSoup基础
1、打开文件
如下代码所示,用lxml方式解析文件,也可以使用html.parser方式。如果担心使用的文件不规则,可以使用soup.prettify()
方法来格式化文档。
# coding=utf-8
import requests
from bs4 import BeautifulSoup
#创建BeautifulSoup对象,打开文件
r = requests.get('http://music.163.com/')
soup = BeautifulSoup(r.text, "lxml")
print(soup.title)
#<title>网易云音乐</title>
soup2 = BeautifulSoup(open("D:\music.html",encoding="utf-8",errors="ignore"),"lxml")
print(soup2.title)
#<title>网易云音乐</title>
2、四大对象
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种: Tag , NavigableString , BeautifulSoup , Comment .
Tag对象:
Tag对象和xml、html原生文档中的tag相同。tag就是一个个标签,如下面html片段当中的meta、title等等。
<meta name="baidu-site-verification" content="cNhJHKEzsD" />
<meta charset="utf-8">
<meta property="qc:admins" content="27354635321361636375" />
<title>网易云音乐</title>
<meta name="applicable-device" content="pc,mobile">
<script type="text/javascript">
使用BeautifulSoup对象,可以方便的获取tag,如下所示:
soup2 = BeautifulSoup(open("D:\\test.html",encoding="utf-8",errors="ignore"),"lxml")
#获取meta这个tag对象
tag = soup2.meta
print(type(tag))
#<class 'bs4.element.Tag'>
可以看到,很方便的便获取到了Tag对象,Tag对象有两个重要的属性name和attrs,怎么获取这两个属性呢?使用print(tag.name)、print(tag.attrs)
即可,其输出为:
meta
{'name': 'baidu-site-verification', 'content': 'cNhJHKEzsD'}
可以看见,默认输出了第一个meta tag的数据,属性是以dictionary形式输出的,获取特定的属性就可以用字典的相关方法了:
print(tag.attrs["name"])
print(tag.attrs.get("content"))
#输出为:
#baidu-site-verification
#cNhJHKEzsD
NavigableString对象:
这个对象呢,就是用来获取tag中的文字的,还是用上面的例子:
tag = soup2.title
print(tag.string)
print(type(tag.string))
#网易云音乐
#<class 'bs4.element.NavigableString'>
这样我们就轻松获取到了标签里面的内容。如果tag包含了多个子节点tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None。这时可以使用 .strings 来循环获取。
BeautifulSoup对象:
BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag。比如上面使用BeautifulSoup对象调用文档中的tag信息。因为 BeautifulSoup 对象并不是真正的HTML或XML的tag,所以它没有name和attribute属性.但有时查看它的 .name 属性是很方便的,所以 BeautifulSoup 对象包含了一个值为 “[document]” 的特殊属性 .name:
soup2 = BeautifulSoup(open("D:\\test.html",encoding="utf-8",errors="ignore"),"lxml")
print(soup2.name)
#[document]
Comment 对象:
Tag , NavigableString , BeautifulSoup 几乎覆盖了html和xml中的所有内容,但是还有一些特殊对象.就像文档的注释部分。比如文档中含有这样一行:<b><!--Hey, buddy. Want to buy a used parser?--></b>
,使用以下代码来查看输出:
b_tag = soup2.b
print(b_tag.string)
print(type(b_tag.string))
#输出为:
#Hey, buddy. Want to buy a used parser?
#<class 'bs4.element.Comment'>
可以看到,输出的仅仅是注释的文字部分,已经把注释符号去掉了,要注意这个小细节。
二、遍历文档树
1、直接子节点
一个Tag可能包含多个字符串或其它的Tag,这些都是这个Tag的子节点.Beautiful Soup提供了许多操作和遍历子节点的属性。
.contents 和 .children
tag的 .contents 属性可以将tag的子节点以列表的方式输出:
head_tag = soup.head
#输出head tag中的子节点的个数
print(len(head_tag.contents))
#子节点的内容,列表形式返回
print(head_tag.contents)
tag的 .children属性可以将tag的子节点循环输出:
for child in soup.head.children:
print(child)
遇到的问题,下面这个文档执行上面代码的输出和期望的不太一致:
<html>
<head>
<meta charset="utf-8"/>
<meta name="baidu-site-verification" content="cNhJHKEzsD"/>
<meta property="qc:admins" content="27354635321361636375"/>
</head>
</html>
输出为:
#为什么会有那么多换行符?
7
['\n', <meta charset="utf-8"/>, '\n', <meta content="cNhJHKEzsD" name="baidu-site-verification"/>, '\n', <meta content="27354635321361636375" property="qc:admins"/>, '\n']
2、所有子节点
.contents 和 .children 属性仅包含tag的直接子节点,.descendants 属性可以对所有tag的子孙节点进行递归循环。
3、节点内容
单节点:如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string
得到子节点。
多节点:如果tag中包含多个字符串 ,可以使用 .strings
来循环获取,使用 .stripped_strings
可以去除多余空白内容。
4、其他节点
其他各种节点有很多,不逐个解释了,这里只列出它们:
节点名称 | 属性名称 |
---|---|
父节点 | .parent 属性 |
全部父节点 | .parents 属性 |
兄弟节点 | .next_sibling .previous_sibling 属性 |
全部兄弟节点 | .next_siblings .previous_siblings 属性 |
前后节点 | .next_element .previous_element 属性(针对所有节点,部分层次) |
所有前后节点 | .next_elements .previous_elements 属性 |
~
这些属性的使用和前面子节点的使用类似,后面会陆续用到。
三、搜索文档树
BeautifulSoup定义了很多搜索方法,这里着重介绍find_all() ,其它方法的参数和用法类似。
find_all( name , attrs , recursive , string , **kwargs )
find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件。常用的过滤器的类型有以下几种:
- 字符串: 最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容
- 正则表达式: 如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match() 来匹配内容
- 列表:如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回
- True:True 可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点
- 方法:如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数 ,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False
下面介绍一下find_all()方法中的参数:
1、name 参数:
可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉。搜索 name 参数的值可以使任一类型的 过滤器 ,字符串,正则表达式,列表,方法或是 True。
3、recursive 参数:
调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False。
r = requests.get('http://music.163.com/#/discover/toplist')
soup = BeautifulSoup(r.text, "lxml")
print(soup.title)
#<title>网易云音乐</title>
print(soup.html.find_all("title",recursive = False))
#[]
只有 find_all() 和 find() 支持 recursive 参数。
4、string 参数:
通过 string 参数可以搜搜文档中的字符串内容.与 name 参数的可选值一样, string 参数接受 字符串 , 正则表达式 , 列表, True。
5、keyword 参数(kwargs ):
如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性。
按CSS搜索:从Beautiful Soup的4.1.1版本开始,可以通过 class_ 参数搜索有指定CSS类名的tag。可以通过CSS值完全匹配,也可以使用各种过滤。
limit 参数:
使用 limit 参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果。
find_all() 的简写方法: find_all几乎是Beautiful Soup中最常用的搜索方法,所以我们定义了它的简写方法. BeautifulSoup 对象和 tag 对象可以被当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同,下面两行代码是等价的:
soup.find_all("a")
soup("a")
这两行代码也是等价的:
soup.title.find_all(string=True)
soup.title(string=True)
find()与 find_all() 方法的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果。
四、CSS选择器
Beautiful Soup支持大部分的CSS选择器 ,在 Tag 或 BeautifulSoup 对象的 .select() 方法中传入字符串参数, 即可使用CSS选择器的语法找到tag。
CSS选择器的语言可以参考笔者之前的文章:Selenium自动化测试-入门
或者官方资料: CSS 选择器参考手册