Python网络爬虫(七)——BeautifulSoup4

简介

BeautifulSoup4 也是一个 XML/HTML 的解析器,能够解析和提取 XML/HTML 数据。

与基于 lxml 的局部遍历不同,BeautifulSoup4 则是基于 DOM(Document Object Model),一般会载入整个文档,解析整个 DOM 树,因此与 lxml 相比,BeautifulSoup4 解析时的时间和内存开销都会大的多。

BeautifulSoup4 在解析 XML 之外,还支持 CSS 选择器,Python 中的 HTML 解析器和 lxml 中的 XML 解析器。

基本使用

from bs4 import BeautifulSoup

text = """
<div>
    <ul>
         <li class="a"><a href="aaa.html">aaa</a></li>
         <li class="b"><a href="bbb.html">bbb</a></li>
         <li class="c"><a href="ccc.html">ccc</a></li>
         <li class="b"><a href="ddd.html">ddd</a></li>
         <li class="a"><a href="eee.html">eee</a></li>
     </ul>
 </div>
 """

html = BeautifulSoup(text,features='lxml')
print(html.prettify())

结果为:

<html>
 <body>
  <div>
   <ul>
    <li class="a">
     <a href="aaa.html">
      aaa
     </a>
    </li>
    <li class="b">
     <a href="bbb.html">
      bbb
     </a>
    </li>
    <li class="c">
     <a href="ccc.html">
      ccc
     </a>
    </li>
    <li class="b">
     <a href="ddd.html">
      ddd
     </a>
    </li>
    <li class="a">
     <a href="eee.html">
      eee
     </a>
    </li>
   </ul>
  </div>
 </body>
</html>

解析器

在之前的代码段中,存在 BeautifulSoup(text,features='lxml'),其中的参数 features 说明的就是解析器。

BeautifulSoup 不仅支持 python 标准库中的 HTML 解析器,还支持一些第三方的解析器,比如 lxml。不同的解析器的优缺点为:

解析器使用优点缺点
python 标准库BeautifulSoup(markup,"html.parser")

python 的内置标准库

执行速度适中

文档容错能力强

python2.7.3 or 3.2.2 前的版本中文容错能力差
lxml HTML 解析器BeautifulSoup(markup, "lxml")

速度快

文档容错能力强

需要安装 C 语言库
lxml XML 解析器

BeautifulSoup(markup, ["lxml","xml"])

BeautifulSoup(markup, "xml")

速度快

唯一支持 XML 的解析器

需要安装 C 语言库
html5libBeautifulSoup(markup,"html5lib")

最好的容错性

以浏览器的方式解析文档

生成 HTML5 格式的文档

速度慢

不依赖外部扩展

比较推荐的是使用 lxml 作为解析器,因为效率会比较高。

常见对象

  • Tag
  • NavigatableString
  • BeautifulSoup
  • Comment

Tag

该对象可以认为是 HTML 中的标签。

from bs4 import BeautifulSoup

text = """
<div>
    <ul>
         <li class="a"><a href="aaa.html">aaa</a></li>
         <li class="b"><a href="bbb.html">bbb</a></li>
         <li class="c"><a href="ccc.html">ccc</a></li>
         <li class="b"><a href="ddd.html">ddd</a></li>
         <li class="a"><a href="eee.html">eee</a></li>
     </ul>
 </div>
 """

html = BeautifulSoup(text,features='lxml')

# print title
print("html.title is:",html.title)
print(type(html.title))

# print head
print("html.head is:",html.head)
print(type(html.head))

# print body
print("html.body is:",html.body)
print(type(html.body))

# print div
print("html.div is:",html.div)
print(type(html.div))

# print ul
print("html.ul is:",html.ul)
print(type(html.ul))

# print li
print("html.li is:",html.li)
print(type(html.li))

# print a
print("html.a is:",html.a)
print(type(html.a))

结果为:

html.title is: None
<class 'NoneType'>
html.head is: None
<class 'NoneType'>
html.body is: <body><div>
<ul>
<li class="a"><a href="aaa.html">aaa</a></li>
<li class="b"><a href="bbb.html">bbb</a></li>
<li class="c"><a href="ccc.html">ccc</a></li>
<li class="b"><a href="ddd.html">ddd</a></li>
<li class="a"><a href="eee.html">eee</a></li>
</ul>
</div>
</body>
<class 'bs4.element.Tag'>
html.div is: <div>
<ul>
<li class="a"><a href="aaa.html">aaa</a></li>
<li class="b"><a href="bbb.html">bbb</a></li>
<li class="c"><a href="ccc.html">ccc</a></li>
<li class="b"><a href="ddd.html">ddd</a></li>
<li class="a"><a href="eee.html">eee</a></li>
</ul>
</div>
<class 'bs4.element.Tag'>
html.ul is: <ul>
<li class="a"><a href="aaa.html">aaa</a></li>
<li class="b"><a href="bbb.html">bbb</a></li>
<li class="c"><a href="ccc.html">ccc</a></li>
<li class="b"><a href="ddd.html">ddd</a></li>
<li class="a"><a href="eee.html">eee</a></li>
</ul>
<class 'bs4.element.Tag'>
html.li is: <li class="a"><a href="aaa.html">aaa</a></li>
<class 'bs4.element.Tag'>
html.a is: <a href="aaa.html">aaa</a>
<class 'bs4.element.Tag'>

也就是说,使用 BeautifulSoup4 可以借用实际的标签来实现输出,从最后输出的结果来看,text 中不存在的标签并不会输出,也不会创建 Tag 对象,而存在的标签都会创建 Tag 对象。需要注意的话,这种利用标签直接输出的情况,只会输出当前查找到的第一个对应的标签。

Tag 对象根据官方文档的介绍,存在两个比较重要的属性,name 和 attrs。

from bs4 import BeautifulSoup

text = """
<div>
    <ul>
         <li class="a"><a href="aaa.html">aaa</a></li>
         <li class="b"><a href="bbb.html">bbb</a></li>
         <li class="c"><a href="ccc.html">ccc</a></li>
         <li class="b"><a href="ddd.html">ddd</a></li>
         <li class="a"><a href="eee.html">eee</a></li>
     </ul>
 </div>
 """

html = BeautifulSoup(text,features='lxml')

print(type(html))
print(html.name)
print(html.attrs)

# print body
print(html.body.name)
print(html.body.attrs)

# print div
print(html.div.name)
print(html.div.attrs)

# print ul
print(html.ul.name)
print(html.ul.attrs)

# print li
print(html.li.name)
print(html.li.attrs)

# print a
print(html.a.name)
print(html.a.attrs)

 结果为:

<class 'bs4.BeautifulSoup'>
[document]
{}
body
{}
div
{}
ul
{}
li
{'class': ['a']}
a
{'href': 'aaa.html'}

从上边的结果可以看出:

  • name 属性对应的就是标签的名字
  • attrs 则是当前标签所包含的所有的属性,是一个字典
  • 如果标签不存在,也就不存在 name 和 attrs 属性
  • 如果是 BeautifulSoup 对象的话,对应的 name 属性为 document,对应的 attrs 属性为空

因为标签的属性采用的是字典的形式,因此也可以使用键值的形式进行访问:

# print li
print(html.li['class'])

# print a
print(html.a['href'])

结果为:

['a']
aaa.html

只是不能将 name 和 attrs 当作 key 来输入。

NavigatableString

如果想要知道 Tag 中的内容,可以使用 Tag.string 来获取标签中的文字,此时的 Tag.string 的类型就是 NavigatableString。

# print li
print(type(html.li.string))
print(html.li.string)

# print a
print(type(html.a.string))
print(html.a.string)

 结果为:

<class 'bs4.element.NavigableString'>
aaa
<class 'bs4.element.NavigableString'>
aaa

BeautifulSoup

在之前使用 Tag 的 name 和 attrs 属性的时候,提到对于 BeautifulSoup 对象来说,该对象的 name 属性为 document,因此可知 BeautifulSoup 也是一种对象。

之前也提到过 BeautifulSoup 是基于 DOM(Document Object Model),一般会载入整个文档,解析整个 DOM 树。因此 BeautifulSoup 对象表示的整个文档的全部内容,该对象是一种树状结构,因此该对象支持文档树遍历和搜索的大部分方法,也可以认为是大型的 Tag 对象,因为毕竟存在 name 和 attrs 属性。

Comment

在 xml/html 中还有一些特殊对象,比如文档的注释部分:

<b><!--This is comment section.--></b>

比如上边的语句中,xml/html 就会将 <!-- 和 --> 中的部分解释为注释。

from bs4 import BeautifulSoup

text = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"

soup = BeautifulSoup(text,'lxml')
print(soup.b.string)
print(type(soup.b.string))

结果为:

Hey, buddy. Want to buy a used parser?
<class 'bs4.element.Comment'>

此时虽然使用了 Tag.string 的形式来输出数据,但是类型却不是 NavigatableString,而是 Comment。因此也可以认为 Comment 是一种比较特殊的 NavigatableString。

文档树遍历

contents 和 children

from bs4 import BeautifulSoup

text = """
<div>
    <ul>
         <li class="a"><a href="aaa.html">aaa</a></li>
         <li class="b"><a href="bbb.html">bbb</a></li>
         <li class="c"><a href="ccc.html">ccc</a></li>
         <li class="b"><a href="ddd.html">ddd</a></li>
         <li class="a"><a href="eee.html">eee</a></li>
     </ul>
 </div>
 """

html = BeautifulSoup(text,features='lxml')

results = html.ul
print(results.contents)

for result in results.children:
    print(result)

结果为:

['\n', <li class="a"><a href="aaa.html">aaa</a></li>, '\n', <li class="b"><a href="bbb.html">bbb</a></li>, '\n', <li class="c"><a href="ccc.html">ccc</a></li>, '\n', <li class="b"><a href="ddd.html">ddd</a></li>, '\n', <li class="a"><a href="eee.html">eee</a></li>, '\n']


<li class="a"><a href="aaa.html">aaa</a></li>


<li class="b"><a href="bbb.html">bbb</a></li>


<li class="c"><a href="ccc.html">ccc</a></li>


<li class="b"><a href="ddd.html">ddd</a></li>


<li class="a"><a href="eee.html">eee</a></li>


从上面的结果可以看出,使用 children 能够对文档树进行遍历,但是只会遍历子节点,而不会遍历孙子节点。如果将上边的 ul 更改为 div 会有不同的结果。

上边输出的结果中,存在很多的空行,这是由于解析器解析造成的。

strings 和 stripped_strings

from bs4 import BeautifulSoup

text = """
<div>
    <ul>
         <li class="a"><a href="aaa.html">aaa</a></li>
         <li class="b"><a href="bbb.html">bbb</a></li>
         <li class="c"><a href="ccc.html">ccc</a></li>
         <li class="b"><a href="ddd.html">ddd</a></li>
         <li class="a"><a href="eee.html">eee</a></li>
     </ul>
 </div>
 """

html = BeautifulSoup(text,features='lxml')

for string in html.strings:
    print(repr(string))

print('******************')

for string in html.stripped_strings:
    print(repr(string))

结果为:

'\n'
'\n'
'aaa'
'\n'
'bbb'
'\n'
'ccc'
'\n'
'ddd'
'\n'
'eee'
'\n'
'\n'
'\n'
******************
'aaa'
'bbb'
'ccc'
'ddd'
'eee'

使用 strings 和 stripped_strings 能够循环获取 Tag 中的多个字符串,如上边代码段中显示的那样。在第一次输出中,存在很多空行(以 '\n' 显式标明),这是解析器解析的结果,可以使用 stripped_strings 来去除多余的空白内容。

文档树搜索

find 和 find_all

使用 find 方法可以返回第一个满足条件的标签,而 find_all 方法则可以返回所有满足条件的标签。

在这两种方法使用过程中,可以使用 name 或者 attrs 参数来找出符合要求的标签,或者直接传入属性的名字作为关键字参数进行查找。

from bs4 import BeautifulSoup

text = """
<div>
    <ul>
         <li class="a"><a href="aaa.html">aaa</a></li>
         <li class="b"><a href="bbb.html">bbb</a></li>
         <li class="c"><a href="ccc.html">ccc</a></li>
         <li class="b"><a href="ddd.html">ddd</a></li>
         <li class="a"><a href="eee.html">eee</a></li>
     </ul>
 </div>
 """

html = BeautifulSoup(text,features='lxml')

print(html.find('li'))
print(html.find_all('li'))

print(html.find('li',attrs = {'class':['a']}))
print(html.find_all('li',attrs = {'class':['a']}))

print(html.find('a',attrs = {'href':['aaa.html']}))
print(html.find_all('a',href = ['aaa.html']))

结果为:

<li class="a"><a href="aaa.html">aaa</a></li>
[<li class="a"><a href="aaa.html">aaa</a></li>, <li class="b"><a href="bbb.html">bbb</a></li>, <li class="c"><a href="ccc.html">ccc</a></li>, <li class="b"><a href="ddd.html">ddd</a></li>, <li class="a"><a href="eee.html">eee</a></li>]
<li class="a"><a href="aaa.html">aaa</a></li>
[<li class="a"><a href="aaa.html">aaa</a></li>, <li class="a"><a href="eee.html">eee</a></li>]
<a href="aaa.html">aaa</a>
[<a href="aaa.html">aaa</a>]

从结果可以看出,如果使用 find_all 方法,则会返回返回所有满足条件的标签列表。

不过比较特殊的是,不能使用 ‘class’ 作为关键字参数进行查找。

select

使用 select 方法可以通过 css 选择器来进行查找。

from bs4 import BeautifulSoup

text = """
<div>
    <ul>
         <li class="a"><a href="aaa.html">aaa</a></li>
         <li id="b"><a href="bbb.html">bbb</a></li>
         <li id="c"><a href="ccc.html">ccc</a></li>
         <li class="b"><a href="ddd.html">ddd</a></li>
         <li class="a"><a href="eee.html">eee</a></li>
     </ul>
 </div>
 """

html = BeautifulSoup(text,features='lxml')

# 通过标签名查找
print(html.select('li'))

# 通过类名查找
# 使用类名进行查找的话,应该在类名的前边加上一个.
print(html.select('.a'))

# 通过 id 查找
# 使用 id 进行查找的话,应该在 id 的前边加上一个#
print(html.select('#b'))

# 组合查找
# 使用组合查找的话,要注意各自的查找方式不变,方式之间用空格分隔
# 直接子标签查找,则使用 > 分隔
print(html.select('li a'))

# 通过属性查找
# 使用属性进行查找的话,应该将属性用中括号括起来
# 只是属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到
print(html.select('li[class="b"]'))

# 获取内容
# 对返回的列表先遍历内容,然后使用 get_text 方法,可以获取内容
for item in html.select('li'):
    print(item.get_text())

结果为:

[<li class="a"><a href="aaa.html">aaa</a></li>, <li id="b"><a href="bbb.html">bbb</a></li>, <li id="c"><a href="ccc.html">ccc</a></li>, <li class="b"><a href="ddd.html">ddd</a></li>, <li class="a"><a href="eee.html">eee</a></li>]
[<li class="a"><a href="aaa.html">aaa</a></li>, <li class="a"><a href="eee.html">eee</a></li>]
[<li id="b"><a href="bbb.html">bbb</a></li>]
[<a href="aaa.html">aaa</a>, <a href="bbb.html">bbb</a>, <a href="ccc.html">ccc</a>, <a href="ddd.html">ddd</a>, <a href="eee.html">eee</a>]
[<li class="b"><a href="ddd.html">ddd</a></li>]
aaa
bbb
ccc
ddd
eee
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值