Python-BeautifulSoup4操作

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下的titlediv节点,返回的是一个bs4.element.Tag,接下来介绍该对象的一些重要操作。

首先获取一个Tag对象:

t = bs.a
  • name:获取Tag的名称
print t.name        # a
  • string:获取Tag的内容
print t.string      # 新闻
  • attrs:获取Tag的所有属性。一个Tag之中可能包含多个属性,例如idclass等等,此时我们需要获取所有的属性。
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的值,通常返回的是一个列表。relrevaccept-charsetheadersaccesskey这些都是多值属性,返回的都是一个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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值