小言_互联网的博客

Python爬虫2.1 — BeautifulSoup用法教程

522人阅读  评论(0)

综述

本系列文档用于对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语言库
html5lib BeautifulSoup(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()方法选择。

其他博文链接


转载:https://blog.csdn.net/Zhihua_W/article/details/100191978
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场