Python爬虫2.1 — BeautifulSoup用法教程

综述

本系列文档用于对Python爬虫技术的学习进行简单的教程讲解,巩固自己技术知识的同时,万一一不小心又正好对你有用那就更好了。
Python 版本是3.7.4

前面四篇文章讲了urllibrequests两个库的用法,这两个库主要是进行访问网站进行爬取数据,但是由于爬取下来的数据有很多,我们想要的只是其中的一部分,所以下面我们开始讲解如何从爬取的数据中提取我们想要的。

BeautifulSoup 介绍

BeautifulSoup是一个Python库,和lxml一样,BeautifulSoup也是一个HTML\XML的解析器,主要的功能也是如何解析和提取HTML\XML数据。lxml只会局部遍历,而BeautifulSoup是基于HTML DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。

BeautifulSoup用来解析HTML比较简单,API非常人性化,支持CSS选择器,Python标准库中的HTML解析器,也支持lxml的XML解析器。

要想使用BeautifulSoup首先要先安装这个库,安装方法如下:

    $ pip install beautifulsoup4

解析器

BeautifulSoup在解析HTML\XML的时候以来解析器,它除了支持Python标准库中的HTML解析器外,还支持一些第三方解析器(比如lxml),如下表所示:

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

建议使用lxml解析器,在后面,BeautifulSoup的用法实例也统一用这个解析器来演示。

几大解析工具的对比

解析工具解析速度使用难度
BeautifulSoup最慢最简单
lxml简单
正则表达式最快最难

使用方法

下面就以下列的一个html字符传作为例子进行使用方法介绍:

    html = """
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>BeautifulSoup学习</title>
    </head>
    <body>
        <div>
            <form name="form1" class="form1" action="">
                <div class="text phone">
                    <input type="text" name="username" placeholder="请输入用户名"/>
                    <p class="username">用户名不能为空</p>
                    <p class="fu">有非法字符</p>
                </div>
                <div class="text phone">
                    <select name="area_code" id="area_code"
                            style="line-height: 1.7rem;font-size: 90%;height: 1.6rem;padding-left: 5px;width: 80%;float: left;border-radius: 1.7rem;border: none;">
                        <option value="86">中国(+86)</option>
                    </select>
                </div>
                <div class="text phone">
                    <input type="tel" name="mobile" placeholder="请输入手机号"/>
                    <p class="phone-num">请输入正确的手机号</p>
                </div>
                <div class="text test">
                    <input type="text" name="mobile" placeholder="手机验证码"/>
                    <button type="button" class="test-num">获取验证码</button>
                    <p id="yan_num">请输入验证码</p>
                </div>
                <div class="text mima">
                    <input type="password" name="user_pwd" placeholder="输入登录密码"/>
                    <p>请输入登录密码</p>
                </div>
                <div class="text again">
                    <input type="password" name="user_pwd_confirm" placeholder="再次确认密码"/>
                    <p class="p1">请再次输入登录密码</p>
                    <p class="p2">两次密码不一致</p>
                </div>
                <p class="text-phone">推荐人信息</p>
                <div class="text phone">
                    <input type="tel" name="tuijian" placeholder="推荐人手机号"/>
                    <p class="tuijian">请输入正确的手机号</p>
                    <p class="isYes">请输入推荐人手机号</p>
                </div>
            </form>
        </div>
    </body>
    </html>
    """

一个简单小例子:

    # 引入bs4库
    from bs4 import BeautifulSoup
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    # prettify()方法。这个方法可以把要解析的字符串以标准的缩进格式输出
    print(soup.prettify())
    print(soup.title.string)

首先,调用prettify()方法。这个方法可以把要解析的字符串以标准的缩进格式输出。(注意:对于不标准的HTML字符串BeautifulSoup,可以自动更正格式。)这一步不是由prettify()方法做的,而是在初始化BeautifulSoup时就完成了。

然后调用soup.title.string,这实际上是输出HTML中title节点的文本内容。所以,soup.title可以选出HTML中的title节点,再调用string属性就可以得到里面的文本了。

节点选择器

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

选择元素
    # 引入bs4库
    from bs4 import BeautifulSoup
    
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    # 打印输出title节点选择结果
    print(soup.title)
    # 输出它的类型,是bs4.element.Tag类型
    print(type(soup.title))
    # Tag具有一些属性,比如string属性,调用该属性,可以得到节点的文本内容
    print(soup.title.string)
    # 选择head节点,打印节点加其内部的所有内容
    print(soup.head)
    # 选择第一个p,也就是说,当有多个节点时,这种选择方式只会选择到第一个匹配的节点,其他的后面节点都会忽略
    print(soup.p)

示例讲解:首先打印输出title节点的选择结果,输出结果正是title节点加里面的文字内容。接下来,输出它的类型,是bs4.element.Tag类型,这是BeautifulSoup中一个重要的数据结构。经过选择器选择后,选择结果都是这种Tag类型。Tag具有一些属性,比如string属性,调用该属性,可以得到节点的文本内容,所以接下来的输出结果正是节点的文本内容。

接下来,我们又尝试选择了head节点,结果也是节点加其内部的所有内容。最后,选择了p节点。不过这次情况比较特殊,我们发现结果是第一个p节点的内容,后面的几个p节点并没有选到。也就是说,当有多个节点时,这种选择方式只会选择到第一个匹配的节点,其他的后面节点都会忽略。

提取信息

上面演示了调用string属性来获取文本的值,那么如何获取节点属性的值呢?如何获取节点名呢?下面来统一梳理一下信息的提取方式。

  1. 获取名称
    可以利用name属性获取节点的名称,这里还是以上吗的文本为例,选区title节点,然后掉用name属性就可以得到节点名称:
    # 引入bs4库
    from bs4 import BeautifulSoup
    
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    # 打印输出title节点的名称
    print(soup.title.name)
    
    # 输出结果:title
  1. 获取属性
    每个节点可能有多个属性,比如idclass等,选择这个节点元素后,可以调用attrs获取所有属性:
    # 引入bs4库
    from bs4 import BeautifulSoup
    
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    # 打印第一个input节点的信息
    print(soup.input.attrs)
    print(soup.input.attrs['name'])
    
    # 输出结果:
    # {'type': 'text', 'name': 'username', 'placeholder': '请输入用户名'}
    # username

可以看到,attrs的返回结果是字典形式,它把选择的节点的所有属性和属性值组合成一个字典。接下来,如果要获取name属性,就相当于从字典中获取某个键值,只需要用中括号加属性名就可以了。比如,要获取name属性,就可以通过attrs['name']来得到。

其实这样有点烦琐,还有一种更简单的获取方式:可以不用写attrs,直接在节点元素后面加中括号,传入属性名就可以获取属性值了。样例如下:

    # 引入bs4库
    from bs4 import BeautifulSoup
    
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    # 打印第一个input节点的信息
    print(soup.input['name'])
    print(soup.input.attrs['placeholder'])
    
    # 输出结果:
    # username
    # 请输入用户名

【注意】这里需要注意的是,有的返回结果是字符串,有的返回结果是字符串组成的列表。比如,name属性的值是唯一的,返回的结果就是单个字符串。而对于class,一个节点元素可能有多个class,所以返回的是列表。在实际处理过程中,我们要注意判断类型。

  1. 获取内容
    可以利用string属性获取节点元素包含的文本内容,比如要获取第一个p节点的文本:
# 引入bs4库
    from bs4 import BeautifulSoup
    
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    print(soup.title.string)
    
    # 输出结果:
    # BeautifulSoup学习
嵌套选择

在上面的例子中,我们知道每一个返回结果都是bs4.element.Tag类型,它同样可以继续调用节点进行下一步的选择。比如,我们获取了head节点元素,我们可以继续调用head来选取其内部的head节点元素,示例如下:

    # 引入bs4库
    from bs4 import BeautifulSoup
    
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    print(soup.head.title)
    print(soup.head.title.string)
    
    # 输出结果:
    # <title>BeautifulSoup学习</title>
    # BeautifulSoup学习
关联选择

在做选择的时候,有时候不能做到一步就选到想要的节点元素,需要先选中某一个节点元素,然后以它为基准再选择它的子节点、父节点、兄弟节点等,这里就来介绍如何选择这些节点元素。

  1. 子节点和子孙节点
    • contents获取所选取节点元素的所有直接子节点
    • children获取所选取节点元素的所有直接子节点
    • descendants获取所选取节点元素的所有的子孙节点

代码示例如下:

    # 引入bs4库
    from bs4 import BeautifulSoup
    
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    # 选取head节点元素之后
    # 获取它的直接子节点,可以调用contents属性
    print(soup.head.contents)
    # 调用children属性得到相应的结果,获得所有的直接子节点,是个list对象,可使用循环输出
    print(soup.head.children)
    # 如果要得到所有的子孙节点的话,可以调用descendants属性:
    print(soup.head.descendants)
  1. 父节点和祖先节点
    • parent获取所选取节点元素的父节点
    • parents获取所选取节点元素的所有祖先节点

示例代码如下:

    # 引入bs4库
    from bs4 import BeautifulSoup
    
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    # 选取第一个p节点元素之后
    # 获取它的父节点,可以调用parent属性
    print(soup.p.parent)
    # 调用parents属性得到相其所有祖先节点
    print(soup.head.parents)
  1. 兄弟节点
    • next_sibling获取所选取节点元素的下一个兄弟节点
    • previous_sibling获取所选取节点元素的上一个兄弟节点
    • next_siblings获取所选取节点元素前面的所有的兄弟节点
    • previous_siblings获取所选取节点元素后面的所有的兄弟节点

方法选择器

前面所讲的选择方法都是通过属性来选择的,这种方法非常快,但是如果进行比较复杂的选择的话,它就比较烦琐,不够灵活了。幸好,BeautifulSoup还为我们提供了一些查询方法,比如find_all()find()等,调用它们,然后传入相应的参数,就可以灵活查询了。

find_all()

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

find_all(name, attrs={}, recursive=True, text=None, limit=None, **kwargs)
  • name:查询节点的名称
  • attrs:查询节点的属性
  • recursive:是否进行递归查找
  • text:参数可用来匹配节点的文本,传入的形式可以是字符串,可以是正则表达式对象
  • limit:限制查询数量

代码示例如下:

    import re
    # 引入bs4库
    from bs4 import BeautifulSoup
    
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    # 查询出所有的div
    div = soup.find_all(name='div')
    print(div)
    print(len(div))
    # 查询出所有class名有phone的div,限制查询出2个
    div_phone = soup.find_all(name='div', attrs={'class': 'phone'}, limit=2)
    # 对于一些常用的属性,比如id和class等,我们可以不用attrs来传递。比如,要查询id为list-1的节点,可以直接传入id这个参数
    # div_phone = soup.find_all(name='div', class_='phone')
    print(div_phone)
    print(len(div_phone))
    # 查询出所有内容带有手机号的p标签节点
    p = soup.find_all(name='p', text=re.compile('手机号'))
    print(p)
find()

除了上面的find_all()方法,还有find()方法,只不过后者返回的是单个元素,也就是第一个匹配的元素,而前者返回的是所有匹配的元素组成的列表,用法基本一致(find()没有limit参数)。

代码示例如下:

    # 引入bs4库
    from bs4 import BeautifulSoup
    
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    # 查询第一个匹配的div
    div = soup.find(name='div')
    print(div)
    # 查询出第一个class名有phone的div
    div_phone = soup.find(name='div', class_='phone')
    print(div_phone)
    inp = soup.find(name='input', attrs={'name': 'user_pwd'})
    print(inp, inp['type'])

另外,还有许多查询方法,其用法与前面介绍的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():前者返回节点后所有符合条件的节点,后者返回第一个符合条件的节点。

CSS选择器

BeautifulSoup还提供了另外一种选择器,那就是CSS选择器。使用CSS选择器时,只需要调用select()方法,传入相应的CSS选择器即可。

CSS选择器教程文档:https://www.w3school.com.cn/cssref/css_selectors.asp

示例代码如下:

    # 引入bs4库
    from bs4 import BeautifulSoup
    
    # 构造解析对象
    soup = BeautifulSoup(html, 'lxml')
    # 查询标签class为phone的元素
    print(soup.select('.phone'))
    print(soup.select('.again'))
    # 查询标签id为phone的元素
    print(soup.select('#area_code'))
    # 查询标签class为again的div元素下的p元素
    print(soup.select('div.again p'))

总结

到此,BeautifulSoup的用法基本就介绍完了,最后做一下简单的总结:

  • 推荐使用lxml解析库,必要时使用html.parser。
  • 节点选择筛选功能弱但是速度快。
  • 建议使用find()或者find_all()查询匹配单个结果或者多个结果。
  • 如果对CSS选择器熟悉的话,可以使用select()方法选择。

其他博文链接

  • 8
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值