爬虫基础(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 语言库
html5libBeautifulSoup(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 in enumerate(soup.ul.descendants):
        print("第%d个元素:%s"%(i,child))
        
    # 运行结果:
    <generator object descendants at 0x7f95bce07ca8>0个元素:
    
    第1个元素:<li class="li1" id="home-page"><a href="../news/index.html">Home Page</a></li>2个元素:<a href="../news/index.html">Home Page</a>3个元素:Home Page
    第4个元素:
    
    第5个元素:<li class="li2"><a href="../course/course.html">Online Class</a></li>6个元素:<a href="../course/course.html">Online Class</a>7个元素:Online Class
    第8个元素:
    
    第9个元素:<li class="li3"><a href="../doc/docDownload.html">Download Document</a></li>10个元素:<a href="../doc/docDownload.html">Download Document</a>11个元素:Download Document
    第12个元素:
    
    第13个元素:<li class="li4"><a href="../news/search.html">Search</a></li>14个元素:<a href="../news/search.html">Search</a>15个元素:Search
    第16个元素:
    
    

    可以看出,返回结果是一个生成器对象。对比前面的示例,我们不难发现,.descendants 属性不同于 .children 属性,它可以输出 ul 节点下的所有子孙节点,包括 li 节点、li 节点下的 a 节点以及 a 节点的文本内容;而 .children 属性只能输出子节点 li。另外,此生成器对象也可以转换成列表之后再输出:

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

    列表中的每一个元素都是一个节点。

  • 获取父节点

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print(soup.p.parent)
    
    # 运行结果:
    <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>
    

    可以看到,输出的结果是第一个 p 节点 <p class="title">Python</p> 的父节点的所有内容。

  • 获取祖先节点

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print(soup.p.parents)
    for parent in enumerate(soup.p.parents):
        print(parent)
    
    # 运行结果:
    <generator object parents at 0x7fab03f4bca8>0个元素:<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>1个元素:<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>2个元素:<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>3个元素:<!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>
    

    可以看出,.parents 属性的输出结果是一个生成器对象。以遍历方式输出的每一个元素都是一个元组。第一个元素是 p 节点的父节点 div;第二个元素是父节点 div 的父节点 body;…依次往上递推,直到输出根节点 <!DOCTYPE html>。同样,这个生成器对象也可以转换成列表对象进行输出,此处不再演示。

  • 获取兄弟节点

    获取兄弟节点时,我们可以获取单个兄弟节点,也可以获取所有的兄弟节点,具体方法如下:

    获取单个兄弟节点

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print('Next Sibling:', soup.i.next_sibling)
    print('Prev Sibling:', soup.i.previous_sibling)
    
    # 运行结果:
    Next Sibling: 
            Explicit is better than implicit.
            
    Prev Sibling: 
            Life is short,you need Python.
    
    

    获取所有兄弟节点

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print('Next Sibling:', list(enumerate(soup.i.next_siblings)))
    print('Prev Sibling:', list(enumerate(soup.i.previous_siblings)))
    
    # 运行结果:
    Next Sibling: [(0, '\n        Explicit is better than implicit.\n        '), (1, <i>Simple is better than complex.</i>), (2, '\n')]
    Prev Sibling: [(0, '\n        Life is short,you need Python.\n        ')]
    
  • 获取前后节点

    获取单个前后节点

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print('Next Element:', soup.i.next_element)
    print('Prev Element:', soup.i.previous_element)
    
    # 运行结果:
    Next Element: Beautiful is better than ugly.
    Prev Element: 
            Life is short,you need Python.
            
    

    获取所有前后节点

    from bs4 import BeautifulSoup
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    print('Next Elements:', list(enumerate(soup.i.next_elements)))
    print('Prev Elements:', list(enumerate(soup.i.previous_elements)))
    
    # 运行结果:
    Next Elements: [(0, 'Beautiful is better than ugly.'), (1, '\n        Explicit is better than implicit.\n        '), (2, <i>Simple is better than complex.</i>), (3, 'Simple is better than complex.'), (4, '\n'), (5, '\n'), (6, <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>), (7, '\n'), (8, <li class="li1" id="home-page"><a href="../news/index.html">Home Page</a></li>), (9, <a href="../news/index.html">Home Page</a>), (10, 'Home Page'), (11, '\n'), (12, <li class="li2"><a href="../course/course.html">Online Class</a></li>), (13, <a href="../course/course.html">Online Class</a>), (14, 'Online Class'), (15, '\n'), (16, <li class="li3"><a href="../doc/docDownload.html">Download Document</a></li>), (17, <a href="../doc/docDownload.html">Download Document</a>), (18, 'Download Document'), (19, '\n'), (20, <li class="li4"><a href="../news/search.html">Search</a></li>), (21, <a href="../news/search.html">Search</a>), (22, 'Search'), (23, '\n'), (24, '\n'), (25, <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>), (26, '\n'), (27, <span class="span Admin"><a href="../user/admin.html"><strong>USER ADMIN:</strong></a></span>), (28, <a href="../user/admin.html"><strong>USER ADMIN:</strong></a>), (29, <strong>USER ADMIN:</strong>), (30, 'USER ADMIN:'), (31, '\n'), (32, <span class="item"><a href="../user/login.html">SIGN IN | </a></span>), (33, <a href="../user/login.html">SIGN IN | </a>), (34, 'SIGN IN | '), (35, '\n'), (36, <span class="item"><a href="../user/register.html">SIGN UP | </a></span>), (37, <a href="../user/register.html">SIGN UP | </a>), (38, 'SIGN UP | '), (39, '\n'), (40, <span class="item" id="logout">SIGN OUT</span>), (41, 'SIGN OUT'), (42, '\n'), (43, '\n'), (44, '\n'), (45, '\n')]
    Prev Elements: [(0, '\n        Life is short,you need Python.\n        '), (1, <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>), (2, '\n'), (3, 'Python'), (4, <p class="title">Python</p>), (5, '\n'), (6, <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>), (7, '\n'), (8, <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>), (9, '\n'), (10, '\n'), (11, 'The Example of Beautiful Soup'), (12, <title>The Example of Beautiful Soup</title>), (13, '\n'), (14, <meta charset="utf-8"/>), (15, '\n'), (16, <head>
    <meta charset="utf-8"/>
    <title>The Example of Beautiful Soup</title>
    </head>), (17, '\n'), (18, <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>), (19, 'html')]
    

对于关联选择所选取的节点,我们可以通过 .string 属性获取其文本内容,方法与前面相同,此处不再叙述。

(四)方法选择器

前面所讲的选择方法都是通过属性来选择的,这种方法非常快。但是如果进行比较复杂的选择的话,它就比较烦琐,不够灵活。所幸,Beautiful Soup 还为我们提供了一些搜索文档树的方法,比如 find_all()、find() 等,调用它们,然后传入相应的参数,就可以灵活查询了。下表总结了这些方法的功能与返回结果的类型:

方法选择器功能返回结果的类型
find_all()获取所有子节点与子孙节点ResultSet
find()获取单个子节点与子孙节点Tag
find_parents()获取目标节点的所有父节点或祖先节点ResultSet
find_parent()获取目标节点的一个父节点或祖先节点Tag
find_next_siblings()获取目标节点后面所有的兄弟节点ResultSet
find_next_sibling()获取目标节点后面第一个兄弟节点Tag
find_previous_siblings()获取目标节点前面所有的兄弟节点ResultSet
find_previous_sibling()获取目标节点前面第一个兄弟节点Tag
find_all_next()获取目标节点后所有符合条件的节点ResultSet
find_next获取目标节点后第一个符合条件的节点Tag
find_all_previous获取目标节点前所有符合条件的节点ResultSet
find_previous获取目标节点前第一个符合条件的节点Tag

上述这些方法的可选参数可以是 name、attrs、recursive、limit、text 等之中的几个或者全部,下表统计了各个方法的可选参数:

方法nameattrsrecursivelimittext
find_all
find
find_parents
find_parent
find_next_siblings
find_next_sibling
find_previous_siblings
find_previous_sibling
find_all_next
find_next
find_all_previous
find_previous

这些获取节点的方法在选择节点时可以只传递一个参数,也可以传递多个参数,以选取符合条件的节点。这些参数在传递值时可以以多种数据形式传递,例如列表、字符串、布尔值、正则表达式等,下表总结了各个参数可以传递的数据形式:

参数值的形式nameattrslimitrecursivetext
数值
列表
字典
字符串
正则表达式
布尔值
方法

上述六组搜索文档树的方法按照其返回结果的数据类型可以分为两类,一类返回的是 ResultSet ,即列表;另一类返回的是 Tag,即字符串。

1. 获取子节点与子孙节点

获取子节点与子孙节点的方法选择器有 find_all() 和 find()。

(1)find_all()

find_all(),可以查询所有符合条件的元素。给它传入一些属性或文本,就可以得到符合条件的元素,它的功能十分强大。

它的 API 如下:

find_all(name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)
  • name参数:可以通过name参数传递节点的名称,以此来获取符合条件的节点。如下示例:

    from bs4 import BeautifulSoup
    import re
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    # 传递字符串————————————————————————————————————————————————————————————
    tag = soup.find_all(name='title')
    print(tag)
    
    # 运行结果:
    [<title>The Example of Beautiful Soup</title>]
    
    # 传递正则表达式————————————————————————————————————————————————————————————
    tag = soup.find_all(name=re.compile("^i"))
    print(tag)
    print(tag[1])
    
    # 运行结果:
    [<i>Beautiful is better than ugly.</i>, <i>Simple is better than complex.</i>]
    <i>Simple is better than complex.</i>
    
    # 传递列表————————————————————————————————————————————————————————————
    tag = soup.find_all(name=['p','span'])
    print(tag)
    print(tag[-1])
    
    # 运行结果:
    [<p class="title" style="color: red">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>, <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>]
    <span class="item" id="logout">SIGN OUT</span>
    
    # 传递方法————————————————————————————————————————————————————————————
    def has_id(tag):
        return tag.has_attr('id')
    tag = soup.find_all(has_id)
    print(tag)
    print(tag[0:-1])
    
    # 运行结果:
    [<li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>, <li class="list" id="li-2"><a href="../course/course.html">Online Class</a></li>, <li class="list" id="li-3"><a href="../doc/docDownload.html">Download Document</a></li>, <li class="list" id="li-4"><a href="../news/search.html">Search</a></li>, <span class="item" id="logout">SIGN OUT</span>]
    [<li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>, <li class="list" id="li-2"><a href="../course/course.html">Online Class</a></li>, <li class="list" id="li-3"><a href="../doc/docDownload.html">Download Document</a></li>, <li class="list" id="li-4"><a href="../news/search.html">Search</a></li>]
    

    通过上述示例,我们可以发现,find_all() 的返回结果相当于是列表。既然是列表,那么就可以使用列表元素的选择方法,即通过列表索引和列表切片选择想要的节点。

  • attrs参数:此参数作为一个可选参数,可以传递字符串、正则表达式或字典来选择想要的节点元素。示例如下:

    from bs4 import BeautifulSoup
    import re
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    # 传递字典————————————————————————————————————————————————————————————
    tag = soup.find_all(attrs={'class':'item'})
    print(tag)
    
    # 运行结果:
    [<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>]
    
    # 传递字符串————————————————————————————————————————————————————————————
    tag = soup.find_all(id='li-1')
    print(tag)
    
    # 运行结果:
    [<li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>]
    
    # 传递正则表达式————————————————————————————————————————————————————————————
    tag = soup.find_all(href=re.compile("user"))
    print(tag)
    
    # 运行结果:
    [<a href="../user/admin.html"><strong>USER ADMIN:</strong></a>, <a href="../user/login.html">SIGN IN | </a>, <a href="../user/register.html">SIGN UP | </a>]
    
    # 传递多个attrs参数————————————————————————————————————————————————————————————
    # 方法1:
    tag = soup.find_all(class_='item', id='logout')
    print(tag)
    
    # 运行结果:
    [<span class="item" id="logout">SIGN OUT</span>]
    
    # 方法2:
    tag = soup.find_all(attrs={'class':'item','id':'logout'})
    print(tag)
    
    # 运行结果:
    [<span class="item" id="logout">SIGN OUT</span>]
    

    通过上述示例,我们需要注意的是:所有的 HTML 标签的属性值都可以通过 attrs={'attr':'value'} 这种格式来传递,但是常用的一些标签属性可以直接以 attr='value' 这种格式传递属性值,而有些并不常用的标签属性只能通过前面的方式进行传递,例如 data-*。另外,我们可以传递多个 attrs 参数来过滤节点。

  • text参数:可以通过传递字符串、正则表达式或列表来选择节点。示例如下:

    from bs4 import BeautifulSoup
    import re
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    # 传递字符串————————————————————————————————————————————————————————————
    tag = soup.find_all(text='Python')
    print(tag)
    
    # 运行结果:
    ['Python']
    
    # 传递正则表达式————————————————————————————————————————————————————————————
    tag = soup.find_all(text=re.compile("sign",re.I))
    print(tag)
    
    # 运行结果:
    ['SIGN IN | ', 'SIGN UP | ', 'SIGN OUT']
    
    # 传递列表————————————————————————————————————————————————————————————
    tag = soup.find_all(text=['Home Page','Search'])
    print(tag)
    
    # 运行结果:
    ['Home Page', 'Search']
    
  • limit参数:limit 参数限制返回结果的数量。示例如下:

    from bs4 import BeautifulSoup
    import re
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    tag = soup.find_all(class_='list', limit=2)
    print(tag)
    
    # 运行结果:
    [<li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>, <li class="list" id="li-2"><a href="../course/course.html">Online Class</a></li>]
    

    如果上述代码中不传递 limit 参数,则会返回四个 li 节点。

  • recursive参数:如果只想搜索目标节点的直接子节点,则可以使用参数 recursive=False。Beautiful Soup 提供了多种DOM树搜索方法。这些方法都使用了类似的参数定义,但是只有 find_all()find() 支持 recursive 参数。

(2)find()

除了 find_all() 方法,还有 find() 方法,只不过后者返回的是单个元素 ,也就是第一个匹配的元素,而前者返回的是所有匹配的元素组成的列表。

它的 API 如下:

find(name=None, attrs={}, recursive=True, text=None, **kwargs)

此方法除了不能使用 limit 参数选择节点之外,与 find_all() 的用法基本相同。下面通过一些简单的示例来熟悉此方法的用法:

from bs4 import BeautifulSoup
import re

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
# 传递name参数选择节点
tag = soup.find(name='span')
print(tag)

# 运行结果:
<span class="span Admin"><a href="../user/admin.html"><strong>USER ADMIN:</strong></a></span>

# 传递多个可选参数选择节点————————————————————————————————————————————————————————————
tag = soup.find('a', text='Download Document')
print(tag)

# 运行结果:
<a href="../doc/docDownload.html">Download Document</a>

# 以正则表达式传递attrs参数选择节点————————————————————————————————————————————————————————————
tag = soup.find(href=re.compile('user'))
print(tag)

# 运行结果:
<a href="../user/admin.html"><strong>USER ADMIN:</strong></a>

# 以列表传递name参数选择节点————————————————————————————————————————————————————————————
tag1 = soup.find_all(name=['a','i'])
tag2 = soup.find(name=['a','i'])
print(tag1)
print(tag2)

# 运行结果:
[<i>Beautiful is better than ugly.</i>, <i>Simple is better than complex.</i>, <a href="../news/index.html">Home Page</a>, <a href="../course/course.html">Online Class</a>, <a href="../doc/docDownload.html">Download Document</a>, <a href="../news/search.html">Search</a>, <a href="../user/admin.html"><strong>USER ADMIN:</strong></a>, <a href="../user/login.html">SIGN IN | </a>, <a href="../user/register.html">SIGN UP | </a>]
<i>Beautiful is better than ugly.</i>

通过上述示例,我们可以发现,find() 方法的返回结果是 Tag 类型,该方法只能返回一个节点。如果在find_all() 方法中用相同的过滤器选择节点,则 find() 方法的返回结果是 find_all() 返回的列表节点中的第一个元素。

2. 获取父节点与祖先节点

  • find_parents()

    from bs4 import BeautifulSoup
    import re
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    # 获取class属性中含有“list”字符串的节点的父节点————————————————————————————————————————
    tag = soup.find(href=re.compile('news'))
    res = tag.find_parents(class_=re.compile('list'))
    print(res)
    print(res[0])
    
    # 运行结果:
    [<li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>, <ul class="menu-list">
    <li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>
    <li class="list" id="li-2"><a href="../course/course.html">Online Class</a></li>
    <li class="list" id="li-3"><a href="../doc/docDownload.html">Download Document</a></li>
    <li class="list" id="li-4"><a href="../news/search.html">Search</a></li>
    </ul>]
    <li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>
    
    # 获取<i>节点的父节点,父节点的名称是“div”————————————————————————————————————————
    tag = soup.find('i')
    res = tag.find_parents(name='div')
    print(res)
    
    # 运行结果:
    [<div class="header">
    <p class="title" style="color: red">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-list">
    <li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>
    <li class="list" id="li-2"><a href="../course/course.html">Online Class</a></li>
    <li class="list" id="li-3"><a href="../doc/docDownload.html">Download Document</a></li>
    <li class="list" id="li-4"><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>]
    
  • find_parent()

    from bs4 import BeautifulSoup
    import re
    
    soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
    tag = soup.find(text='Home Page')
    res = tag.find_parent('a')
    print(res)
    
    # 运行结果:
    <a href="../news/index.html">Home Page</a>
    
    tag = soup.find(href=re.compile('news'))
    res = tag.find_parent('li')
    print(res)
    
    # 运行结果:
    <li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>
    

其他四组文档树搜索方法的用法可以参考 parent() 和 parents() 方法,此处不再赘述。

通过上述内容,我们可以对文档树选择方法有以下总结:

  • 可以传递多个可选参数过滤节点元素,比如:tag=soup.find_all(name=‘span’, class_=‘item’, limit=1);
  • find_parent、find_next_sibling、find_previous_sibling、find_next 和 find_previous 五个获取单个节点的方法的可选参数、返回结果类型和使用方法同 find() 方法基本相同;而其他五个获取所有节点的方法的可选参数、返回结果类型和使用方法同 find_all() 方法基本相同;
  • 对于返回结果类型是 ResultSet 的方法,我们可以对获取的节点列表进行索引操作或切片操作过滤不想要的节点元素;
  • 参数 limit 只能用于获取所有节点元素的方法,即通过传递该参数可以限制返回的节点列表中的元素数量。
  • 除了 find_all() 和 find() 方法之外,其他方法在使用时,必须用find() 方法选择一个目标节点,针对此目标节点,选择父节点、祖先节点、兄弟节点或前后节点。

(五)CSS选择器

Beautiful Soup 还提供了另外一种选择器,那就是 CSS 选择器。如果对 Web 开发熟悉的话,那么对 CSS 选择器肯定也不陌生。下面我们具体了解一下。

1. 基本用法

使用 CSS 选择器时,只需要调用 select () 方法,传入相应的 css 选择器或者标签名称即可。例如:

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
#直接选择标签
print(soup.select('ul li'))
#选择class的类型
print(soup.select('.menu-list .list'))
#选择id的类型
print(soup.select('.login #logout'))

# 运行结果
[<li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>, <li class="list" id="li-2"><a href="../course/course.html">Online Class</a></li>, <li class="list" id="li-3"><a href="../doc/docDownload.html">Download Document</a></li>, <li class="list" id="li-4"><a href="../news/search.html">Search</a></li>]

[<li class="list" id="li-1"><a href="../news/index.html">Home Page</a></li>, <li class="list" id="li-2"><a href="../course/course.html">Online Class</a></li>, <li class="list" id="li-3"><a href="../doc/docDownload.html">Download Document</a></li>, <li class="list" id="li-4"><a href="../news/search.html">Search</a></li>]

[<span class="item" id="logout">SIGN OUT</span>]

除了利用标签名和 CSS 选择器来选择标签之外,还可以两者相互组合来提取。例如:

print(soup.select('div #li-2'))

# 运行结果
[<li class="list" id="li-2"><a href="../course/course.html">Online Class</a></li>]

直接查找子标签,则使用 > 分隔:

print(soup.select('div > p'))

# 运行结果
[<p class="title" style="color: red">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>]

2. 嵌套选择

select() 方法同样支持嵌套选择。例如,先选择所有 ul 节点,再遍历每个 ul 节点,选择其 li 节点,样例如下:

from bs4 import BeautifulSoup

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

3. 获取属性

我们知道节点类型是 Tag 类型,所以获取属性还可以用原来的方法。仍然是上面的 HTML 文本,这里尝试获取每个 ul 节点的 id 属性:

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
for p in soup.select('p'):
    print(p['class'])
    
# 运行结果
['title']
['profile']

除了上述方式,还可以通过下面的方式获取属性:

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
for p in soup.select('p'):
    print(p.attrs['class'])

4. 获取文本

要获取文本,当然也可以用前面所讲的 string 属性。此外,还有一个方法,那就是 get_text() ,示例如下:

from bs4 import BeautifulSoup

soup = BeautifulSoup(open('test_bs4.html'), 'lxml')
for li in soup.select('li'):
    print(li.get_text())
    
# 运行结果
Home Page
Online Class
Download Document
Search

例如,先选择所有 ul 节点,再遍历每个 ul 节点,选择其 li 节点,样例如下:

from bs4 import BeautifulSoup

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

版权声明:本文为qq_45617055原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。