• python之BeautifulSoup库


    一 什么是BeautifulSoup

        BeautifulSoup是python的一个HTML或者XML的解析库,可以通过它来实现对网页的解析,从而获得想要的数据。
         在用BeautifulSoup库进行网页解析时,其实还是要依赖解析器的,python有自己内置的html.parser解析器,Beautiful肯定是支持html.parse解析器的。不过除此之外,还有一些第三方解析器比如lxml,xml以及html5lib,其中lxml在这几个中是比较有优势的,主要体现在解析速度快,容错能力强。所以下面主要是围绕lxml解析器进行记录的。
         使用前请检查安装lxml库BeautifulSoup库。

    二 使用BeautifulSoup

    1. 初始化网页源码

    大多数情况下我们获取到的HTML都是字符串,而且格式不一,这很不方便我们从中提取数据,这时就可以将HTML源码传给BeautifulSoup库初始化,进而将字符串的HTML转换成BeautifulSoup对象。如下

    from bs4 import BeautifulSoup
    
    html = '''
    
    
    
        
        BeautifulSoup学习
    
    
    

    经典老歌

    经典老歌列表

    如上,BeautifulSoup()接受两个参数,一个是html字符串源码,第二个是要使用的解析器。我这里使用的是lxml解析器,经过初始化后的html字符串就转换成了树结构的BeautifulSoup对象了。BeautifulSoup在初始化的同时还会自动补全缺失的标签。如上html字符串结尾缺少了/div,/body标签,初始化后就会自动补上,sp.prettify()控制缩进,美化html。结果如下

    >>>
    <class 'bs4.BeautifulSoup'>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8"/>
    <title>BeautifulSoup学习</title>
    </head>
    <body>
    <div id="song list">
    <h2 class="title">经典老歌</h2>
    <p class="introduction">
    经典老歌列表
    </p>
    <ul class="list-group" id="list">
    <li data-view="2">一路上有你</li>
    <li data-view="7">
    <a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
    </li>
    <li class="active" data-view="4">
    <a href="/3.mp3" singer="齐秦">往事随风</a>
    </li>
    <li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
    <li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
    <li data-view="5">
    <a href="/6.mp3" singer="邓丽君">但愿人长久</a>
    </li>
    </ul></div></body></html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    2. 节点选择器

    节点选择器是通过一些html标签来提取对应的内容,如p,a,li等标签。如下

    2.1 通过html 标签匹配
    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    print(type(sp.title))
    print(sp.title)
    print(type(sp.h2))
    print(sp.h2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    输出:

    >>>
    <class 'bs4.element.Tag'>
    <title>BeautifulSoup学习</title>
    <class 'bs4.element.Tag'>
    <h2 class="title">经典老歌</h2>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如上,可以通过html标签元素来提取信息,也称节点选择。可以看到返回的结果类型都是bs4.element.Tag,凡是返回Tag类型的数据,它们都有如下属性

    • name: 返回节点的名字
    • string:获取节点内容,但仅限于节点里面没有其他节点的时候,返回类型NavigableString,可通过str()函数转为字符串。
    • attrs: 获取节点属性,字典形式返回
      如下
    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    print("节点html:", sp.h2)
    print("节点名字:", sp.h2.name)
    print("节点内容:", sp.h2.string)
    print("节点属性:", sp.h2.attrs)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    >>>
    节点html: <h2 class="title">经典老歌</h2>
    节点名字: h2
    节点内容: 经典老歌
    节点属性: {'class': ['title']}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面提取的都是只有单个节点的情况,而且节点里面没有其他节点。下面看看有多个同名节点,节点里面又嵌套其它节点的情况
    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    print(sp.a)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    输出结果

    >>>
    <a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
    
    • 1
    • 2

    在这里插入图片描述
    可以看到输出结果只有第一个a节点,其他a节点并没有输出,所有通过这种方式只能提取出第一个符合的内容。


    再看看节点里面有嵌套其它节点时,用string属性获取文本内容。如下

    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    print("节点div的html: ", sp.div)
    print(type(sp.div))
    print("string获取内容:", sp.div.string)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    >>>
    节点div的html:  <div id="song list">
    <h2 class="title">经典老歌</h2>
    <p class="introduction">
    经典老歌列表
    </p>
    <ul class="list-group" id="list">
    <li data-view="2">一路上有你</li>
    <li data-view="7">
    <a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
    </li>
    <li class="active" data-view="4">
    <a href="/3.mp3" singer="齐秦">往事随风</a>
    </li>
    <li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
    <li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
    <li data-view="5">
    <a href="/6.mp3" singer="邓丽君">但愿人长久</a>
    </li>
    </ul></div>
    
    <class 'bs4.element.Tag'>
    string获取内容: None
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    可以看到div节点里面还包含了其它节点,再用string获取内容时返回是None,即无法获取

    2.2 关联选择

    从上面可以看到,当a节点不止一个时,用节点选择的方法只能返回第一个匹配的值,这时候我们可以用一些关联选择的方法来进行更多的匹配,如下

    • 兄弟节点
      next_sibling: 获取节点的下一个兄弟节点
      next_siblings: 获取节点后面的兄弟节点,返回生成器类型
      previous_sibling: 获取节点的上一个兄弟节点
      previous_siblings: 获取节点前面的兄弟节点,返回生成器类型
    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    print("获取第一个li节点:", sp.li)
    print(type(sp.li.next_sibling))
    print("获取第一个li节点的下一个兄弟节点:", sp.li.next_sibling)
    print(type(sp.li.next_siblings))
    print("获取第一个li节点后面的兄弟节点:", sp.li.next_siblings)
    for i in sp.li.next_siblings:
        print(i)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    获取第一个li节点: <li data-view="2">一路上有你</li>
    <class 'bs4.element.Tag'>
    获取第一个li节点的下一个兄弟节点: <li>这是测试</li>
    <class 'generator'>
    获取第一个li节点后面的兄弟节点: <generator object PageElement.next_siblings at 0x000001D4AD66E5C8>
    <li>这是测试</li>
    
    
    <li data-view="7">
    <a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
    </li>
    
    
    <li class="active" data-view="4">
    <a href="/3.mp3" singer="齐秦">往事随风</a>
    </li>
    
    
    <li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
    
    
    <li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
    
    
    <li data-view="5">
    <a href="/6.mp3" singer="邓丽君">但愿人长久</a>
    </li>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    注意next_sibling 返回的是一个Tag类型的对象,而next_siblings 返回的是一个生成器,可以通过遍历获取数据。(由于我是手敲的html,有手动换行,所以兄弟节点存在空的)


    • 子节点和子孙节点
      contents或者children: 获取直接子节点,centents返回列表,children返回生成器类型
      descendants: 获取所有子孙节点,返回生成器类型

    获取ul节点的子节点:

    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    for i in enumerate(sp.ul.contents):
        print(i)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    >>>
    (0, '\n')
    (1, <li data-view="2">一路上有你</li>)
    (2, <li>这是测试</li>)
    (3, '\n')
    (4, <li data-view="7">
    <a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
    </li>)
    (5, '\n')
    (6, <li class="active" data-view="4">
    <a href="/3.mp3" singer="齐秦">往事随风</a>
    </li>)
    (7, '\n')
    (8, <li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>)
    (9, '\n')
    (10, <li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>)
    (11, '\n')
    (12, <li data-view="5">
    <a href="/6.mp3" singer="邓丽君">但愿人长久</a>
    </li>)
    (13, '\n')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    可以看到ul里面的a节点是没有被单独提取出来的,而是嵌套在li节点里面的,以为contents获取的是直接子节点。

    获取ul节点的子孙节点:

    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    print(type(sp.ul.descendants))
    for i in enumerate(sp.ul.descendants):
        print(i)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    <class 'generator'>
    (0, '\n')
    (1, <li data-view="2">一路上有你</li>)
    (2, '一路上有你')
    (3, <li>这是测试</li>)
    (4, '这是测试')
    (5, '\n')
    (6, <li data-view="7">
    <a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
    </li>)
    (7, '\n')
    (8, <a href="/2.mp3" singer="任贤齐">沧海一声笑</a>)
    (9, '沧海一声笑')
    (10, '\n')
    (11, '\n')
    (12, <li class="active" data-view="4">
    <a href="/3.mp3" singer="齐秦">往事随风</a>
    </li>)
    (13, '\n')
    (14, <a href="/3.mp3" singer="齐秦">往事随风</a>)
    (15, '往事随风')
    (16, '\n')
    (17, '\n')
    (18, <li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>)
    (19, <a href="/4.mp3" singer="beyond">光辉岁月</a>)
    (20, '光辉岁月')
    (21, '\n')
    (22, <li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>)
    (23, <a href="/5.mp3" singer="陈慧琳">记事本</a>)
    (24, '记事本')
    (25, '\n')
    (26, <li data-view="5">
    <a href="/6.mp3" singer="邓丽君">但愿人长久</a>
    </li>)
    (27, '\n')
    (28, <a href="/6.mp3" singer="邓丽君">但愿人长久</a>)
    (29, '但愿人长久')
    (30, '\n')
    (31, '\n')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    • 父节点和祖先节点
      parent:返回节点的父节点,返回Tag类型
      parents: 返回节点的祖先节点,返回生成器类型

    返回ul节点的父节点:

    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    print(type(sp.ul.parent))
    print(sp.ul.parent.name)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    >>>
    <class 'bs4.element.Tag'>
    div
    
    • 1
    • 2
    • 3

    获取ul节点的祖先节点:

    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    print(type(sp.ul.parents))
    for i in sp.ul.parents:
        print(i.name)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    <class 'generator'>
    div
    body
    html
    [document]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3. 方法选择器

    上面讲的都是通过html元素标签来提取数据,对于一些结构简单的html使用是很直接,很方便的,但是在html比较复杂的时候,要想准确的提取出数据还是比较繁琐的,这时候就得用上方法选择器了,具体如下。

    3.1 方法选择器:find_all()

    find_all顾名思义就是查找所有满足条件的值,用法如下

    • find_all(name, attrs, recursive, text, **kargs ), 返回列表
      name: 根据节点名称来选择,传入形式name=value
      attrs: 根据属性来选择,attrs值为字典形式
      recursive: 限定直接子节点
      text: 根据文本来选择,传入形式是字符串,可以是正则表达式。

    根据节点名称name选择

    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    a = sp.find_all(name='a')#提取a节点信息
    print(a)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    >>>
    [<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>, <a href="/3.mp3" singer="齐秦">往事随风</a>, <a href="/4.mp3" singer="beyond">光辉岁月</a>, <a href="/5.mp3" singer="陈慧琳">记事本</a>, <a href="/6.mp3" singer="邓丽君">但愿人长久</a>]
    
    • 1
    • 2

    可以看到,find_all是匹配出里所有的a节点信息,不像节点选择器则匹配第一个满足条件的值。

    根据属性attrs选择

    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    title = sp.find_all(attrs={'class': 'title'})
    print(title)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    >>>
    [<h2 class="title">经典老歌</h2>]
    
    • 1
    • 2

    根据文本text选择

    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    h2 = sp.find_all(text=r'经典老歌')
    print(h2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    >>>
    ['经典老歌']
    
    • 1
    • 2

    4. CSS选择器

    除了上面的节点选择器和方法选择器外,还有一个CSS选择器,如果对CSS比较熟悉的话也是可以此方法来进行数据提取的。CSS选择器主要是通过调用select()方法来实现,具体如下。

    4.1 CSS选择器:select()
    • select(‘css选择器’) : 参数是CSS选择器,返回列表形式
    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    a = sp.select('#list a')#CSS选择器提取歌单
    for i, j in enumerate(a):
        print(i, j)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    >>>
    0 <a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
    1 <a href="/3.mp3" singer="齐秦">往事随风</a>
    2 <a href="/4.mp3" singer="beyond">光辉岁月</a>
    3 <a href="/5.mp3" singer="陈慧琳">记事本</a>
    4 <a href="/6.mp3" singer="邓丽君">但愿人长久</a>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上面选择器传入了两个值,一个是 #list 提取 id=list 的html,后面的 a 是在 id=list 的结果里面再提取a节点的内容。

    4.2 获取文本:get_text()

    上面说过,string只能在节点内没有其他节点的时候获取文本内容,而get_text()方法会获取所有文本内容,如下

    #初始化html
    sp = BeautifulSoup(html, 'lxml')
    sp.prettify()
    # 通过html标签匹配
    li = sp.select('li')#CSS选择器提取歌单
    for i in li:
        print("get_text获取文本: ", i.get_text().strip())
        print("string获取文本: ", i.string)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    >>>
    get_text获取文本:  一路上有你
    string获取文本:  一路上有你
    get_text获取文本:  这是测试
    string获取文本:  这是测试
    get_text获取文本:  沧海一声笑
    string获取文本:  None
    get_text获取文本:  往事随风
    string获取文本:  None
    get_text获取文本:  光辉岁月
    string获取文本:  光辉岁月
    get_text获取文本:  记事本
    string获取文本:  记事本
    get_text获取文本:  但愿人长久
    string获取文本:  None
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    可以看到,当节点里面没有嵌套其他节点时,get_text()和string 都能获取文本内容,实现的效果是一样的,但当节点里面嵌套有其他节点是,string则无法获取,返回None,而get_text()则还能正常提取。

  • 相关阅读:
    docker容器内网用不了vi
    记一次kafka使用不当导致的服务器异常
    Linux - iptables防火墙
    C++(Qt)软件调试---线程死锁调试(15)
    老弟手把手教你编译Spark3.2.1源码!!!!!
    Mac os 如何安装SVN
    华为无线设备配置同一业务VLAN的AP间快速漫游
    VulnHub metasploitable-1
    【Ceph Block Device】块设备挂载使用
    小红书C++ 一面(技术面、50min)
  • 原文地址:https://blog.csdn.net/qq_44690947/article/details/126236736