爬虫基础(7)网页解析之Beautiful Soup库

一. Beautiful Soup库简介

简单来说, Beautiful Soup 就是 Python 的一个 HTML 或者 XML 的解析库,可以用它来方便地从网页中提取数据。官方解释如下:

Beautiful Soup 提供一些简单的、Python 式的函数来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据。因为简单,所以不需要多少代码就可以写出一个完整的应用程序。

Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 UTF-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时你仅仅需要说明一下原始编码方式就可以了。

Beautiful Soup 已成为和 lxml、html6lib 一样出色的 Python 解释器,为用户灵活地提供不同的解析策略或强劲的速度。

所以说,利用它可以省去很多烦琐的提取工作,提高了解析效率。

Beautiful Soup 库的详析使用方法可以参考其中文文档:https://beautifulsoup.readthedocs.io/zh_CN/v4.4.0/

二. 安装beautifulsoup库

在 Ubuntu 虚拟机中,beautifulsoup4 能通过 pip 来安装:

$ python -m pip install beautifulsoup4
或者
$ pip install beautifulsoup4

其他平台下的安装可参考其他相应的安装方式,此处不做概述。

三. Beautiful Soup库的四个对象类

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:'Tag'、'NavigableString'、'BeautifulSoup'、'Comment'

1. Tag

Tag 是什么?通俗点讲就是 HTML 中的一个个标签,例如:

<title>The Dormouse's story</title>
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

上面的 'title'、'a' 等 HTML 标签加上里面包括的内容就是 Tag,下面我们来了解一下怎样用 Beautiful Soup 来方便地获取 Tags:

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.title)
print(soup.a)

# 运行结果
<title>The Example of Beautiful Soup</title>
<a href="../news/index.html">Home Page</a>

我们可以利用 soup 加标签名轻松地获取这些标签的内容,这比正则表达式要方便很多。不过有一点是,它查找的是在所有内容中的第一个符合要求的标签,如果要查询所有的标签,我们在后面进行介绍。查看上述输出内容的类型,可以发现是bs4.element.Tag

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(type(soup.a))

# 运行结果
<class 'bs4.element.Tag'>

对于 Tag,它有两个重要的属性,是 'name'和'attrs',下面我们分别来看看:

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.name)
print(soup.div.name)

# 运行结果
[document]
div

soup 对象本身比较特殊,它的 name 即为 [document],对于其他内部标签,输出的值便为标签本身的名称。

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.div.attrs)
print(soup.li.attrs)

# 运行结果
{
   'class': ['header']}
{
   'class': ['list'], 'id': 'li-1'}

通过attrs属性打印出某一个标签的所有属性时,无论标签的属性有几个,都以字典数据类型输出结果。

如果我们想要单独获取某个属性,可以这样:例如我们想获取 pclass 属性,有如下四种写法:

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
# 写法1
print(soup.p.attrs['class'])
# 写法2
print(soup.p.attrs.get('class'))
# 写法3
print(soup.p['class'])
# 写法4
print(soup.p.get('class'))

# 运行结果
['title']
['title']
['title']
['title']
2. NavigableString

既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?用 .string 即可获取标签内容。例如

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.p.string)
print(type(soup.p.string))

# 运行结果
Python
<class 'bs4.element.NavigableString'>

检查一下输出内容的类型,可以发现是NavigableString(可遍历的字符串)

3. BeautifulSoup

BeautifulSoup 对象表示的是一个文档的全部内容。大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag。例如下面示例,我们可以分别获取它的类型,名称,以及属性:

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(type(soup.name))
print(soup.name)
print(soup.attrs)

# 运行结果
<class 'str'>
[document]
{
   }
4. Comment

Comment 对象是一个特殊类型的 NavigableString 对象,其输出的内容不包括注释符号,但是如果不好好处理它,可能会对我们的文本处理造成意想不到的麻烦。例如:

from bs4 import BeautifulSoup

html = """<p class="title" style="color: red"><!--Python--></p>"""
soup = BeautifulSoup(html, 'lxml')
print(soup.p)
print(soup.p.string)
print(type(soup.p.string))

# 运行结果
<p class="title" style="color: red"><!--Python--></p>
Python
<class 'bs4.element.Comment'>

p 标签里的内容实际上是注释,但是如果我们利用 .string 来输出它的内容,我们发现它已经把注释符号去掉了,所以这可能会给我们带来不必要的麻烦。另外我们打印输出它的类型,发现它是一个 Comment 类型,所以,我们在使用前最好做一下判断,判断代码如下:

if type(soup.a.string)==bs4.element.Comment:
    print soup.a.string

四. Beautiful Soup库详析

Beautiful Soup 借助网页的结构和属性等特性来解析网页。有了它,我们不用再去写一些复杂的正则表达式,只需要简单的几条语句,就可以完成网页中某个元素的提取。接下来我们就来感受一下 Beautiful Soup 的强大之处。

(一)解析器

Beautiful Soup 在解析时实际上依赖解析器,它除了支持 Python 标准库中的 HTML 解析器外,还支持一些第三方解析器(比如 lxml)。下表列出了 Beautiful Soup 支持的解析器:

解析器 使用方法 优势 劣势
Python 标准库 BeautifulSoup(markup, “html.parser”) Python 的内置标准库、执行速度适中、文档容错能力强 较旧版本容错能力差
lxml HTML 解析器 BeautifulSoup(markup, “lxml”) 执行速度快、文档容错能力强 需要安装 C 语言库
lxml XML 解析器 BeautifulSoup(markup, “xml”) 执行速度快、唯一支持 XML 的解析器 需要安装 C 语言库
html5lib BeautifulSoup(markup, “html5lib”) 最好的容错性、以浏览器的方式解析文档、生成 HTML5格式的文档 速度慢、不依赖外部扩展

通过以上对比可以看出,lxml 解析器有解析 HTML 和 XML 的功能,而且速度快,容错能力强,所以推荐使用它。

(二)创建Beautiful Soup对象

  • step1:导入 bs4 库:

    from bs4 import BeautifulSoup
    
  • step2:创建一个字符串,后面的例子我们便会用它来演示:

    html = '''
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>The Example of Beautiful Soup</title>
    </head>
    <body>
    <div class="header">
        <p class="title">Python</p>
        <p class="profile">
            Life is short,you need Python.
            <i>Beautiful is better than ugly.</i>
            Explicit is better than implicit.
            <i>Simple is better than complex.</i>
        </p>
        <ul class="menu">
            <li class="li1" id="home-page"><a href="../news/index.html">Home Page</a></li>
            <li class="li2"><a href="../course/course.html">Online Class</a></li>
            <li class="li3"><a href="../doc/docDownload.html">Download Document</a></li>
            <li class="li4"><a href="../news/search.html">Search</a></li>
        </ul>
        <div class="login">
            <span class="span Admin"><a href="../user/admin.html"><strong>USER ADMIN:</strong></a></span>
            <span class="item"><a href="../user/login.html">SIGN IN | </a></span>
            <span class="item"><a href="../user/register.html">SIGN UP | </a></span>
            <span class="item" id="logout">SIGN OUT</span>
        </div>
    </div>
    </body>
    </html>
    '''
    
  • step3:创建 beautifulsoup 对象:

    soup = BeautifulSoup(html, 'lxml')
    
  • step4:指定编码方式:当 html 为其他类型编码(非 utf-8 和 ascii),比如 GB2312 的时候,需要指定相应的字符编码,BeautifulSoup才能正确解析:

    htmlcharset = "GB2312"
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml', from_encoding=htmlcharset)
    

另外,除了上述方式,我们还可以用本地 HTML 文件来创建对象,例如:

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')

前面所创建的 html 字符串就是 test_bs4.html 文件的内容。

(三)节点选择器

1. 选择元素

直接调用节点的名称就可以选择节点元素,再调用 string 属性就可以得到节点内的文本了,这种选择方式速度非常快。如果单个节点结构层次非常清晰,可以选用这种方式来解析。如下示例:

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.title)
print(type(soup.title))
print(soup.head)
print(soup.li)

# 运行结果:
<title>The Example of Beautiful Soup</title>
<class 'bs4.element.Tag'>
<head>
<meta charset="utf-8"/>
<title>The Example of Beautiful Soup</title>
</head>
<li class="li1" id="home-page"><a href="../news/index.html">Home Page</a></li>

观察上述代码运行结果,可以发现,调用节点名称选择的节点元素是 bs4.element.Tag 类型,这是Beautiful Soup中一个重要的数据结构。经过选择器选择后,选择结果都是这种 Tag 类型。

另外我们可以发现,当我们要选取 li 节点时,结果只返回第一个 li 节点的内容,后面的几个 li 节点并没有选到。也就是说,当选择多个节点时,这种选择方式只会选择到第一个匹配的节点,其他后面的节点都会被忽略。

2. 提取信息

在我们创建了 BeautifulSoup 对象之后,我们不仅可以提取节点的文本内容,而且可以提取节点属性和节点名称。下表总结了提取节点信息的各种属性:

属性 功能
Tag.name 获取节点名称
Tag.attrs 获取节点属性
Tag.string 获取节点的内容
Tag.strings 获取节点的多个内容
Tag.stripped_strings 获取节点的多个内容,其中不包括空格与空行
  • 获取节点名称

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print(soup.div.name)
    
    # 运行结果:
    div
    
  • 获取节点属性

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print(soup.li.attrs)
    print(soup.span.attrs)
    print(soup.li.attrs['id'])
    
    # 运行结果:
    {
         'class': ['li1'], 'id': 'home-page'}
    {
         'class': ['span', 'Admin']}
    home-page
    

可以看到,attrs 的返回结果是字典形式,它把选择的节点的所有属性和属性值组合成一个字典。另外,需要注意的是,有的返回结果是字符串,有的返回结果是字符串组成的列表,这是因为有的节点元素属性值是唯一的,所以返回结果为字符串,比如 id 、name等;而有的节点元素属性值是不唯一的,所以返回结果为列表,比如 class。

如果我们要获取 li 节点的 id 属性,就相当于从字典中获取某个键值,只需要用中括号加属性名就可以。

  • 获取节点单个内容

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print(soup.li.string)
    
    # 运行结果:
    Home Page
    

可以看到,当我们想要获取 li 节点的文本内容时,只能返回第一个节点的文本内容,其他后面的节点都会被忽略选择。

  • 获取节点多个内容

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    for string in soup.ul.strings:
        print(string)
    
    # 运行结果:
    
    
    Home Page
    
    
    Online Class
    
    
    Download Document
    
    
    Search
    
    
    

    不难发现,上述运行结果可以输出所有的 li 节点的文本内容,但是同时也将空行输出了,这并不是我们想要的结果。那么如何可以输出所有的 li 节点的文本内容,又可以过滤掉其中的空行和空格呢?我们可以试试下面的方法:

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    for string in soup.ul.stripped_strings:
        print(string)
        
    # 运行结果:
    Home Page
    Online Class
    Download Document
    Search
    

    可见,上述代码中的 .stripped_strings 可以获取多个节点的内容,并且能过滤文本内容之间的空行。

3. 嵌套选择

在上面的例子中,我们知道每一个返回结果都是 bs4 element.Tag 类型,它同样可以继续调用节点进行下一步的选择。我们看看下面的示例:

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
print(soup.ul.li.a)
print(type(soup.ul.li.a))
print(soup.ul.li.a.string)

# 运行结果:
<a href="../news/index.html">Home Page</a>
<class 'bs4.element.Tag'>
Home Page

可以发现,当我们想要获取某一个节点的子节点时,可以进行嵌套选择。而我们在 Tag 类型的基础上再次选择得到的依然是 Tag 类型。

4. 关联选择

在做选择的时候,有时候不能做到一步就选到想要的节点元素,需要先选中某一个节点元素,然后以它为基准再选择它的子节点、父节点、 兄弟节点等,下面就来介绍如何选择这些节点元素。在关联选择中可调用的属性如下表所示:

属性 功能 类型
Tag.contents 以列表形式返回所有子节点 list
Tag.children 获取所有的子节点,返回一个 list 生成器对象,可通过遍历获取其中的内容 list_iterator
Tag.descendants 对所有 tag 的子孙节点进行递归循环 generator
Tag.parent 获取该节点的父节点 bs4.element.Tag
Tag.parents 获取父到根的所有节点,即祖先节点 generator
Tag.next_sibling 获取该节点的下一个兄弟节点 bs4.element.NavigableString
Tag.previous_sibling 获取该节点的上一个兄弟节点 bs4.element.NavigableString
Tag.next_siblings 获取该节点的所有前面的兄弟节点的生成器 generator
Tag.previous_siblings 获取该节点的所有后面的兄弟节点的生成器 generator
Tag.next_element 获取该节点前面的一个节点,并不只是针对兄弟节点 bs4.element.NavigableString
Tag.previous_element 获取该节点后面的一个节点,并不只是针对兄弟节点 bs4.element.NavigableString
Tag.next_elements 获取该节点的所有前面的节点,并不只是针对兄弟节点 generator
Tag.previous_elements 获取该节点的所有后面的节点,并不只是针对兄弟节点 generator

上述的属性中,返回结果的类型各不相同,有列表(list)、列表迭代器(list_iterator)、生成器(generator)、标签对象(bs4.element.Tag)和可遍历的字符串对象(bs4.element.NavigableString)等。其中的列表迭代器和生成器可以通过遍历进行获取,或者将其转换为 list 对象进行输出。下面我们通过实例进行演示:

  • 获取子节点

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print(soup.ul.contents)
    
    # 运行结果:
    ['\n', <li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>, '\n', <li class="list" id="li-2"><a href="../course/course.html">Online Class</a></li>, '\n', <li class="list" id="li-3"><a href="../doc/docDownload.html">Download Document</a></li>, '\n', <li class="list" id="li-4"><a href="../news/search.html">Search</a></li>, '\n']
    

    可以看到,该方法返回的是一个列表,列表元素包括了 ul 节点内的空行与子节点 li 。

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print(soup.ul.children)
    for i, child in enumerate(soup.ul.children):
        print("第%d个元素:%s"%(i,child))
        
    # 运行结果:
    <list_iterator object at 0x7f95bf6d2978>0个元素:
    
    第1个元素:<li class="li1" id="home-page"><a href="../news/index.html">Home Page</a></li>2个元素:
    
    第3个元素:<li class="li2"><a href="../course/course.html">Online Class</a></li>4个元素:
    
    第5个元素:<li class="li3"><a href="../doc/docDownload.html">Download Document</a></li>6个元素:
    
    第7个元素:<li class="li4"><a href="../news/search.html">Search</a></li>8个元素:
    
    

    可以看到,返回结果是一个 list_iterator 对象,要输出其内容,需要以遍历的方法循环输出。当然,此列表生成器也可以转换成一个列表对象直接输出:

    print(list(enumerate(soup.ul.children)))
    
    # 运行结果:
    [(0, '\n'), (1, <li class="li1" id="home-page"><a href="../news/index.html">Home Page</a></li>), (2, '\n'), (3, <li class="li2"><a href="../course/course.html">Online Class</a></li>), (4, '\n'), (5, <li class="li3"><a href="../doc/docDownload.html">Download Document</a></li>), (6, '\n'), (7, <li class="li4"><a href="../news/search.html">Search</a></li>), (8, '\n')]
    

    转换成列表输出后,每一个列表元素都是一个元组,其中元组的第一个元素是索引,第二个元素是节点内容。

  • 获取子孙节点

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print(soup.ul.descendants)
    for i, child 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值