[网络爬虫] 网页数据解析 — Beautiful Soup 库

🌟想系统化学习爬虫技术?看看这个:[数据抓取] Python 网络爬虫 - 学习手册-CSDN博客

0x01:Beautiful Soup 简介

Beautiful Soup 是一个用于从 HTML 文档或 XML 文档中提取目标数据的 Python 库。它历经了众多版本,其中 Beautiful Soup 3 已经停止开发与维护,官方推荐使用 Beautiful Soup 4(简称 bs4)进行程序开发。

为了快速解析 HTML 文档或 XML 文档的数据,bs4 不仅提供了多种类型的解析器,还支持 CSS 选择器。bs4 通过解析器可以将 HTML 或 XML 文档、片段转换成节点树、节点树中的每一个节点都对应一个 Python 类的对象。

0x02:Beautiful Soup 安装 & 使用介绍

Beautiful Soup 的安装十分简单,输入下面的 pip 命令即可:

pip install beautifulsoup4    # 这个是解析库
pip install lxml              # 这个是解析器

bs4 库的使用一般分为如下 3 个步骤:

  1. 根据 HTML 或 XML 文档、片段创建 BeautifulSoup 类的对象。

  2. 通过 BeautifulSoup 类的对象的查找方法或 CSS 选择器定位节点。

  3. 通过访问节点的属性或节点的名称提取文本。

0x03:创建 BeautifulSoup 类的对象

0x0301:BeautifulSoup 构造方法声明解析

要想使用 bs4 解析网页数据,首先就需要使用构造方法创建 BeautifulSoup 类的对,BeautifulSoup 类的构造方法声明如下:

BeautifulSoup(markup="", 
              features=None,
              builder=None,
              parse_only=None,
              from_encoding=None,
              exclude_encodings=None,
              element_classes=None,
              **kwargs)

上述方法中的常用参数含义如下:

  • markup: 必选参数,表示待解析的内容,可以取值为字符串或类似文件的对象。

  • features: 可选参数,表示指定的解析器。该参数可以接收解析器名称或标记类型。其中,解析器名称包括 lxml、lxml-xml、html.parser 和 html5lib,标记类型包括 html、html5 和 xml。

  • parse_only: 可选参数,指定只解析部分文档。该参数需要接收一个 SoupStrainer 类的对象。当文档太大而无法全部放入内存时,便可以考虑只解析一部分文档。

  • from_encoding: 可选参数,指定待解析文档的编码格式。

值得一提的是,如果我们只需要解析 HTML 文档,那么在创建 BeautifulSoup 类的对象时就可以不用指定解析器。此时 Beautiful Soup 会根据当前系统安装的库自动选择解析器。解析器的选择顺序为 lxml => html5lib => Python 标准库,但该选择会在以下两种情况下发生变化:

  • 要解析的文档是什么类型?目前支持 html、xml 和 html5。

  • 指定使用哪种解析器?目前支持 lxml、html5lib 和 html.parser

如果指定的解析器没有安装,那么 Beautiful Soup 会自动选择其它方案。不过,目前只有解析器 lxml 支持 XML 文档的解析。在当前系统中没有安装 lxml 解析器的情况下,即使创建 BeautifulSoup 对象时明确指定使用解析器 lxml,也无法得到解析后的内容。

下面我们通过一张表来区分上面介绍的 4 种解析器的优势与劣势:

解析器优势劣势
lxml(lxml 的 HTML 解析器)1. 执行速度快; 2. 文档容错能力强;需要安装 C 语言库
lxml-xml(lxml 的 XML 解析器)1. 执行速度快; 2. 唯一适用于 XML 文档的解析器需要安装 C 语言库
html.parser(bs4 的 HTML 解析器)1. Python 内置的标准库; 2. 执行速度适中; 3. 文档容错能力强;Python 2.7.3 或 Python 3.2.2 之前的版本中,文档容错能力差
html5lib1. 拥有 4 种解析器中最好的容错能力; 2. 以浏览器的方式解析文档; 3. 生成 HTML5 格式的文档;1. 执行速度慢 2. 不依赖外部扩展

0x0302:BeautifulSoup 构造方法演示案例

下面是对于 BeautifulSoup 构造方法的一个演示案例,保存后直接运行,控制台中会打印经过格式化处理的 HTML 页面数据:

from bs4 import BeautifulSoup
​
html_doc = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BeautifulSoup 入门案例</title>
</head>
<body>
    <p class="title"><b>1001 页</b></p>
    <p class="story">1001 页是一个很好的故事,它里面有这么几个故事:<br>
        <a href="http://www.hack3rx.cn/1.html" class="link" id="new">1.html => 国王的新衣<br></a>
        <a href="http://www.hack3rx.cn/2.html" class="link" id="light">2.html => 阿里巴巴和它的神灯<br></a>
    </p>
    <p class="story">....</p>
</body>
</html>
"""
​
# 根据 html_doc 创建 BeautifulSoup 类的对象,并指定使用 lxml 解析器解析文档 
soup = BeautifulSoup(html_doc, features='lxml')
print(soup.prettify())

0x04:节点选取 — find() & find_all()

0x0401:find() & find_all() 声明解析

BeautifulSoup 类提供了一些基于 HTML 或 XML 节点树选取节点的方法,其中比较主流的两个方法是 find() 方法和 find_all() 方法:

  • find() 方法用于查找符合条件的第一个节点;

  • find_all() 方法用于查找所有符合条件的节点,并以列表的形式返回;

由于 find() 方法与 find_all() 方法的参数相同,所以我们这里以 find_all() 方法为例进行介绍,find_all() 方法的声明如下:

find_all(self, name=None, attrs={}, recursive=True, text=None, limit=None, **kwargs)

上述方法中的各参数解析如下:

  • name: 待查找的节点名称,支持字符串、正则表达式、列表三种类型的取值。

    • 字符串:查找名称与字符串完全相同的的所有节点。

    • 正则表达式:查找名称符合正则表达式模式的所有节点。

    • 列表:查找名称与列表中任意元素相同的所有节点。

  • attrs: 待查找的属性节点,接收一个字典,字典中的键为属性名称,值为该属性对应的值。

  • recursive: 是否对当前节点的所有子孙节点进行查找。

  • text: 待查找的文本节点,支持字符串、正则表达式、列表三种类型取值,作用与 name 参数一致。

  • limit: 待查找的节点数量。如果节点树非常大,查询会很慢,通过该参数可以限定查找结果的数量,一旦数量超过了 limit 的值,就会停止查找。

  • kwargs:支持以关键字形式传递的任意一个参数。在节点树中查找节点时,会将关键字参数的名称作为节点的属性名称,值作为属性值。当我们要查询的关键字为class时,需要特别设置为class_。

0x0402:find() & find_all() 演示案例

以下是笔者整理的,针对 find_all() 方法的使用示例,请读者运行后细细的看,内容不多,但是只要掌握了这个,你处理起 HTML 页面就是手拿把恰:

import re
from bs4 import BeautifulSoup
​
html_doc = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BeautifulSoup 入门案例</title>
</head>
<body>
    <h1>这个才是标题</h1>
    <p class="title" name="whoami"><b>1001 页</b></p>
    <p class="story">1001 页是一个很好的故事,它里面有这么几个故事:<br>
        <a href="http://www.hack3rx.cn/1.html" class="link" id="new1">1<br></a>
        <a href="http://www.hack3rx.cn/1.html" class="link" id="new2">2<br></a>
        <a href="http://www.hack3rx.cn/2.html" class="link" id="light">2.html => 阿里巴巴和它的神灯<br></a>
    </p>
    <p class="story2">....</p>
</body>
</html>
"""
​
# 根据 html_doc 创建 BeautifulSoup 类的对象,并指定使用 lxml 解析器解析文档 
soup = BeautifulSoup(html_doc, features='lxml')
​
print("*" * 30 + " find_all() 方法 name 参数示例 " + "*" * 30)
print(soup.find_all('title'))                 # 选取 title 节点
print(soup.find_all(id=re.compile('new*')))   # 选取 id 符合正则表达式的所有节点
print(soup.find_all(['title', 'h1']))         # 选取被 title 或 h1 包裹的节点
​
​
print("*" * 30 + " find_all() 方法 attrs 参数示例 " + "*" * 30)
print(soup.find_all(attrs={'id' : 'light'}))    # 选取 id 属性为 light 的节点
​
​
print("*" * 30 + " find_all() 方法 revursive 参数示例 " + "*" * 30)
print(soup.find_all('title', recursive=False))   # 从 html 节点开始,不查找子节点,要求找到 title 节点 => 结果为空
print(soup.find_all('title', recursive=True))    # 从 html 节点开始,查找子节点,要求找到 title 节点
​
print("*" * 30 + " find_all() 方法 text 参数示例 " + "*" * 30) # => 这个不像是选择节点,更像是直接匹配内容
print(soup.find_all(text="1"))              # 选取文本为 1 的节点
print(soup.find_all(text=re.compile('\d'))) # 选取以数字开头的节点
print(soup.find_all(text=['1', '2']))       # 选取节点文本内容要么为 1,要么为 2 的节点
​
print("*" * 30 + " find_all() 方法 limit 参数示例 " + "*" * 30) 
print(soup.find_all('a', limit=1))          # 选取至多 1 个 a 标签
​
print("*" * 30 + " find_all() 方法 **kwargs 参数示例 " + "*" * 30) 
print(soup.find_all(id='light'))           # 选取 id= 'light' 的节点
print(soup.find_all("p", class_='story2')) # 选取 class_='story2' 的 p 标签
​
print("*" * 30 + " 进阶版,多层筛选 " + "*" * 30) 
print(soup.find_all('head')[0].find_all('title')) # 首先选择出所有 head 标签(结果是列表),然后再选择出 'title' 标签

0x05:内容获取 — 从节点中提取属性 & 内容

前面我们介绍了如何使用 BeautifuleSoup 库选取节点,下面我们介绍如何提取我们想要的内容。从网页中筛选的数据,内容要么在标签内部,比如 <a>whoami</a> 中的 whoami ;要么就是直接以属性形式出现在标签中,比如 <a href="http://www.baidu.com">百度以下</a> 中的 href 的内容。

那么本节,就教你如何从选取的节点中提取我们想要的数据,示例如下:

from bs4 import BeautifulSoup
​
html_doc = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BeautifulSoup 入门案例</title>
</head>
<body>
    <h1>这个才是标题</h1>
    <p class="title" name="whoami"><b>1001 页</b></p>
    <p class="story">1001 页是一个很好的故事,它里面有这么几个故事:<br>
        <a href="http://www.hack3rx.cn/1.html" class="link" id="new1">1<br></a>
        <a href="http://www.hack3rx.cn/1.html" class="link" id="new2">2<br></a>
        <a href="http://www.hack3rx.cn/2.html" class="link mna" id="light" redirecturl name="Goto">2.html => 阿里巴巴和它的神灯<br></a>
    </p>
    <p class="story2">....</p>
</body>
</html>
"""
​
# 根据 html_doc 创建 BeautifulSoup 类的对象,并指定使用 lxml 解析器解析文档 
soup = BeautifulSoup(html_doc, features='lxml')
​
# 提取标签内部文本内容
element = soup.find(id='light')
print(element)
print(element.attrs) # 打印该标签包含的属性
print(element.text) # 打印 id=light 的标签的内部文本
​
# 提取标签属性内容
print(element['href'])     # 尝试获取标签中的 href 对应的属性
​
# 推荐通过 get 方法获取属性,这样如果属性不存在就会返回 None,而不是报错
print(element.get('class'))  # class 属性会以列表返回
print(element.get('id'))     # id 属性会直接返回对应字符内容
​
# 拓展:提取属性中包含 redirecturl 的标签
print(soup.find(attrs={'redirecturl':True}))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Blue17 :: Hack3rX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值