Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间。本篇文章将简要介绍一个下BeautifulSoup的操作:
安装BeautifulSoup4
pip install beautifulsoup4
一些基本操作
假设有这样一个Html,具体内容如下:
<!DOCTYPE html>
<!--STATUS OK-->
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="content-type"/>
<meta content="IE=Edge" http-equiv="X-UA-Compatible"/>
<meta content="always" name="referrer"/>
<link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css"/>
<title>
百度一下,你就知道
</title>
</head>
<body link="#0000cc">
<div id="wrapper">
<div id="head">
<div class="head_wrapper">
<div id="u1">
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">
新闻
</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">
hao123
</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">
地图
</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">
视频
</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">
贴吧
</a>
<a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">
更多产品
</a>
</div>
</div>
</div>
</div>
</body>
</html>
创建beautifulsoup4对象:
from bs4 import BeautifulSoup
bs = BeautifulSoup(html_doc,"html.parser")
缩进格式的结构输出:
print bs.prettify()
几个浏览结构化数据的方法:
print bs.title
# <title>百度一下,你就知道</title>,bs4.element.Tag对象
print bs.title.name
# title
print bs.title.string
# 百度一下,你就知道
print bs.div
# <div id="wrapper"> ... </div>,bs4.element.Tag对象
print bs.div["id"]
# wrapper
print bs.a
# <a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>,bs4.element.Tag对象
print bs.find_all("a")
# [<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>, <a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>, ...]
print bs.find(id="u1")
# <div id="u1"> ... </div>
获取文档中所有a标签的链接:
for item in bs.find_all("a"):
print item.get("href")
# print item["href"]
###### 结果如下 #######
http://news.baidu.com
https://www.hao123.com
http://map.baidu.com
http://v.baidu.com
http://tieba.baidu.com
//www.baidu.com/more/
获取文档中所有a标签的内容:
for item in bs.find_all("a"):
print item.get_text()
###### 结果如下 #######
新闻
hao123
地图
视频
贴吧
更多产品
Tag对象
在上面的栗子中可以看到,当我们获取HTML下的title
或div
节点,返回的是一个bs4.element.Tag
,接下来介绍该对象的一些重要操作。
首先获取一个Tag
对象:
t = bs.a
- name:获取Tag的名称
print t.name # a
- string:获取Tag的内容
print t.string # 新闻
- attrs:获取Tag的所有属性。一个Tag之中可能包含多个属性,例如
id
、class
等等,此时我们需要获取所有的属性。
print t.attrs # {u'href': u'http://news.baidu.com', u'name': u'tj_trnews', u'class': [u'mnav']}
Tag中的attrs可以被修改、删除和添加,操作方法与字典一致:
d = t.attrs
print d["class"] # [u'mnav']
del d["class"]
print d["class"] # 报错: KeyError: 'class',因为已经被删除了
print d.get("class") # None
HTML中还有一些多值属性,比较常见的是class
,当我们获取一个标签下class
的值,通常返回的是一个列表。rel
、rev
、accept-charset
、headers
、accesskey
这些都是多值属性,返回的都是一个list:
print t["class"] # [u'mnav']
替换Tag的内容:
t.string.replace_with("哈哈")
# t.string = "哈哈"
print t # <a class="mnav" href="http://news.baidu.com" name="tj_trnews">哈哈</a>
- contents:获取Tag的所有子节点,返回一个list
t = bs.head
print t.contents
# [<meta content="text/html;charset=unicode-escape" http-equiv="content-type"/>, <meta content="IE=Edge" http-equiv="X-UA-Compatible"/>, ...]
- children:获取Tag的所有子节点,返回一个生成器
t = bs.head
print t.children # <listiterator object at 0x000000000362DCF8>
for item in t.children:
print item
###### 结果如下 #######
<meta content="text/html;charset=utf-8" http-equiv="content-type"/>
<meta content="IE=Edge" http-equiv="X-UA-Compatible"/>
<meta content="always" name="referrer"/>
<link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css"/>
<title>百度一下,你就知道</title>
- descendants:获取Tag的所有子孙节点
t = bs.head
for item in t.descendants:
print item
###### 结果如下 #######
<meta content="text/html;charset=utf-8" http-equiv="content-type"/>
<meta content="IE=Edge" http-equiv="X-UA-Compatible"/>
<meta content="always" name="referrer"/>
<link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css"/>
<title>百度一下,你就知道</title>
百度一下,你就知道
因为title
没有子节点,只有内容,所以就把内容当作了它的子节点获取到了。
- strings:如果Tag包含多个字符串,即在子孙节点中有内容,可以用此获取,而后进行遍历
t = bs.body
for item in t.strings:
print item
###### 结果如下 #######
新闻
hao123
地图
视频
贴吧
更多产品
- stripped_strings:与
strings
用法一致,只不过可以去除掉那些多余的空白内容 - parent:获取Tag的父节点
t = bs.title
print bs.parent
# <head> ... </head>
- parents:递归得到父辈元素的所有节点,返回一个生成器
- previous_sibling:获取当前Tag的上一个节点,属性通常是字符串或空白,真实结果是当前标签与上一个标签之间的顿号和换行符
- next_sibling:获取当前Tag的下一个节点,属性通常是字符串或空白,真是结果是当前标签与下一个标签之间的顿号与换行符
t = bs.a
t1 = t.next_sibling
# 为一个空白字符串
print t1
- previous_siblings:获取当前Tag的上面所有的兄弟节点,返回一个生成器
- next_siblings:获取当前Tag的下面所有的兄弟节点,返回一个生成器
t = bs.a
print t.next_siblings
# <generator object next_siblings at 0x0000000004164678>
for item in t.next_siblings:
print item
###### 结果如下 #######
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地图</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧</a>
<a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">更多产品</a>
- previous_element:获取解析过程中上一个被解析的对象(字符串或tag),可能与
previous_sibling
相同,但通常是不一样的 - next_element:获取解析过程中下一个被解析的对象(字符串或tag),可能与
next_sibling
相同,但通常是不一样的
t = bs.a
print t.next_element
# 新闻
可以看出,.next_element
与.next_sibling
是不一样的,.next_element
属性结果是在<a>
标签被解析之后的解析内容,不是<a>
标签后的句子部分
- previous_elements:返回一个生成器,可以向前访问文档的解析内容
- next_elements:返回一个生成器,可以向后访问文档的解析内容
- has_attr:判断Tag是否包含属性
t = bs.a
print t.has_attr("class") # True
print t.has_attr("id") # False
过滤器find_all
在上面的栗子中我们简单介绍了find_all
的使用,接下来介绍一下find_all
的更多用法-过滤器。这些过滤器贯穿整个搜索API,过滤器可以被用在tag的name中,节点的属性等。
- 字符串过滤:会查找与字符串完全匹配的内容
t = bs.find_all("a")
print t
# [<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>, <a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>, ...]
- 正则表达式过滤:如果传入的是正则表达式,那么BeautifulSoup4会通过
search()
来匹配内容
import re
t_list = bs.find_all(re.compile("a"))
for item in t_list:
print item
###### 结果如下 #######
<head><meta content="text/html;charset=utf-8" http-equiv="content-type"/><meta content="IE=Edge" http-equiv="X-UA-Compatible"/><meta content="always" name="referrer"/><link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css"/><title>百度一下,你就知道</title></head>
<meta content="text/html;charset=utf-8" http-equiv="content-type"/>
<meta content="IE=Edge" http-equiv="X-UA-Compatible"/>
<meta content="always" name="referrer"/>
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地图</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧</a>
<a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">更多产品</a>
- 列表:如果传入一个列表,BeautifulSoup4将会与列表中的任一元素匹配到的节点返回
t_list = bs.find_all(["meta","link"])
for item in t_list:
print item
###### 结果如下 #######
<meta content="text/html;charset=utf-8" http-equiv="content-type"/>
<meta content="IE=Edge" http-equiv="X-UA-Compatible"/>
<meta content="always" name="referrer"/>
<link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css"/>
- True、False或空值:传入True、False或空值将可以获取到所有的Tag
print bs.find_all(True)
print bs.find_all(False)
print bs.find_all()
- 方法:传入一个方法,根据方法来匹配
def name_is_exists(tag):
return tag.has_attr("name")
t_list = bs.find_all(name_is_exists)
for item in t_list:
print item
###### 结果如下 #######
<meta content="always" name="referrer"/>
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地图</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧</a>
<a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">更多产品</a>
从结果可以看出,匹配的结果是根据方法的逻辑判断是否为True
来匹配的。
- name参数:传入一个
name
参数,匹配Tag中name值,效果与传入字符串一致 - 关键字参数:可以根据Tag的属性进行搜索
# 查询id=head的Tag
t_list = bs.find_all(id="head")
print t_list
# [<div id="head"> ... </div>]
# 查询href属性包含ss1.bdstatic.com的Tag
t_list = bs.find_all(href=re.compile("ss1.bdstatic.com")
print t_list
# [<link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css"/>]
# 查询所有包含class的Tag(注意:class在Python中属于关键字,所以加_以示区别)
t_list = bs.find_all(class_=True)
for item in t_list:
print item
###### 结果如下 #######
<div class="head_wrapper"> ... </div>
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地图</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧</a>
<a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">更多产品</a>
并不是所有的属性都可以使用上面这种方式进行搜索,比如HTML的data-*
属性:
t_list = bs.find_all(data-foo="value")
如果执行这段代码,将会报错。难道此种属性就不可以被搜索了吗,我们可以使用attrs
参数,定义一个字典来搜索包含特殊属性的tag:
t_list = bs.find_all(attrs={"data-foo":"value"})
- 按CSS搜索:我们可以搜索指定标签,然后根据标签的类名或一些指定属性来进行过滤。值得注意的是,
class
在Python中是关键字,所以从BeautifulSoup4.1.1开始,使用class
搜索,需要将其更改成class_
# 查询class为mnav的所有a标签
t_list = bs.find_all("a",class_="mnav")
print t_list
# 查询class包含a的所有标签
t_list = bs.find_all(class_=re.compile("a"))
print t_list
# 查询name=tj_trnews的所有a标签
t_list = bs.find_all("a",attrs={"name":"tj_trnews"})
print t_list
当我们搜索class
中的一些特殊属性时,我们同样也可以传入一个方法,来进行特殊操作以达到过滤的目的:
def length_is_four(class_)
return class_ and len(class_) = 4
t_list = bs.find_all(class_=length_is_four)
for item in t_list:
print item
###### 结果如下 #######
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地图</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧</a>
- text参数:我们还可以传入一个
text
参数用于查询标签里的内容
# 查询内容为新闻的所有数据
t_list = bs.find_all(text="新闻")
print t_list # [u'\u65b0\u95fb']
# 查询内容包含‘新’的所有数据
t_list = bs.find_all(re.compile(u"新"))
print t_list
当我们搜索text
中的一些特殊属性时,同样也可以传入一个方法来达到我们的目的:
def length_is_two(text):
return text and len(text) == 2
t_list = bs.find_all(text=length_is_two)
for item in t_list:
print item
###### 结果如下 #######
新闻
地图
视频
贴吧
- limit参数:可以传入一个
limit
参数来限制返回的数量,当搜索出的数据量为5,而设置了limit=2
时,此时只会返回前2个数据
t_list = bs.find_all("a",limit=2)
for item in t_list:
print item
###### 结果如下 #######
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
find_all
除了上面一些常规的写法,还可以对其进行一些简写:
# 两者是相等的
t_list = bs.find_all("a") => t_list = bs("a")
# 两者是相等的
t_list = bs.a.find_all(text="新闻") => t_list = bs.a(text="新闻")
find()
find()将返回符合条件的第一个Tag,有时我们只需要或一个Tag时,我们就可以用到find()
方法了。当然了,也可以使用find_all()
方法,传入一个limit=1
,然后再取出第一个值也是可以的,不过未免繁琐。
t_list = bs.find_all("title",limit=1)
print t_list # [<title>\u767e\u5ea6\u4e00\u4e0b\uff0c\u4f60\u5c31\u77e5\u9053</title>]
t = bs.find("title")
print t # <title>百度一下,你就知道</title>
t = bs.find("abc")
print t # None
从结果可以看出find_all
,尽管传入了limit=1
,但是返回值仍然为一个列表,当我们只需要取一个值时,远不如find
方法方便。但是如果未搜索到值时,将返回一个None
在上面介绍BeautifulSoup4
的时候,我们知道可以通过bs.div
来获取第一个div标签,如果我们需要获取第一个div下的第一个div,我们可以这样:
t = bs.div.div
# 等价于
t = bs.find("div").find("div")
CSS选择器
BeautifulSoup支持发部分的CSS选择器,在Tag
获取BeautifulSoup
对象的.select()
方法中传入字符串参数,即可使用CSS选择器的语法找到Tag:
t_list = bs.select("title")
print t_list # [<title>\u767e\u5ea6\u4e00\u4e0b\uff0c\u4f60\u5c31\u77e5\u9053</title>]
通过tag标签逐层查找:
t_list = bs.select("body a")
for item in t_list:
print item
###### 结果如下 #######
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地图</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧</a>
<a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">更多产品</a>
获取某个Tag下的直接子标签:
t_list = bs.select("head > title")
print t_list # [<title>\u767e\u5ea6\u4e00\u4e0b\uff0c\u4f60\u5c31\u77e5\u9053</title>]
# 查询body下id=wrapper的所有直接子标签
t_list = bs.select("body > #wrapper")
print t_list # [<div id="wrapper">...</div>]
# 因为a标签不是body标签下的直接子标签,所以为[]
t_list = bs.select("body > a")
print t_list # []
# 查询递归4层下的class=mnav的标签
t_list = bs.select("div > div > div > div > .mnav")
for item in t_list:
print item
###### 结果如下 #######
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地图</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧</a>
找到兄弟节点标签:
t_list = bs.select(".mnav ~ .bri")
print t_list # [<a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">\u66f4\u591a\u4ea7\u54c1</a>]
通过CSS类名来查找:
t_list = bs.select(".mnav")
# 或者
t_list = bs.select("[class=mnav]")
for item in t_list:
print item
###### 结果如下 #######
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地图</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧</a>
通过ID来查找:
t_list = bs.select("#head")
# 或者->查找div标签中id=head的标签
t_list = bs.select("div#head")
print t_list # [<div id="head">...</div>]
通过是否存在某个属性来查找:
# 查找所有包含name属性的所有标签
t_list = bs.select("[name]")
# 或者->查找a标签中有name属性的所有标签
t_list = bs.select("a[name]")
通过属性的值来查找:
# 查找href=https://www.hao123.com的所有标签
t_list = bs.select("[href=https://www.hao123.com]")
print t_list # [<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>]
# 查找href以https开头的所有标签
t_list = bs.select("[href^=https]")
for item in t_list:
print item
###### 结果如下 #######
<link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css"/>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
# 查找href以.com结尾的所有标签
t_list = bs.select("[href$=.com]")
for item in t_list:
print item
###### 结果如下 #######
<a class="mnav" href="http://news.baidu.com" name="tj_trnews">新闻</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap">地图</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo">视频</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba">贴吧</a>
# 查找href包含hao123的所有标签
t_list = bs.select("[href*=hao123]")
print t_list # [<a class="mnav" href="https://www.hao123.com" name="tj_trhao123">hao123</a>]
更多BeautifulSoup4操作:https://www.crummy.com/software/BeautifulSoup/bs4/doc/