钢铁知识库,一个学习python爬虫、数据分析的知识库。人生苦短,快用python。

上一章我们讲解针对结构化的htmlxml数据,使用Xpath实现网页内容爬取。本章我们再来聊另一个高效的神器:Beautiful Soup4。相比于传统正则表达方式去解析网页源代码,这个就简单得多,实践是检验真理的唯一标准,话不多说直接上号开搞验证。

Beautiful Soup 简介

首先说说BeautifulSoup是什么。简单来说,这是Python的一个HTML或XML的解析库,我们可以用它方便从网页中提取数据,官方解释如下:

BeautifulSoup 提供一些简单的、Python 式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。 BeautifulSoup 自动将输入文档转换为 Unicode 编码,输出文档转换为 utf-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时你仅仅需要说明一下原始编码方式就可以了。 BeautifulSoup 已成为和 lxml、html5lib 一样出色的 Python 解释器,为用户灵活地提供不同的解析策略或强劲的速度。

所以,利用它可以省去很多繁琐的提取工作,提高解析效率。

BeautifulSoup 安装

BeautifulSoup3 目前已经停止开发,推荐使用 BeautifulSoup4,不过它也被移植到bs4了,也就是说导入时我们需要import bs4

在开始之前,请确保已经正确安装beautifulsoup4lxml,使用pip安装命令如下:

pip install beautifulsoup4
pip install lxml

解析器

BeautifulSoup在解析时实际上依赖解析器。除了支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果不安装它,则Python会使用默认的解析器。

下面列出BeautifulSoup支持的解析器

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

通过上面可以看出,lxml 有解析HTML和XML的功能,相比默认的HTML解析器更加强大,速度,容错能力强。

推荐使用它,下面统一使用lxml进行演示。使用时只需在初始化时第二个参数改为 lxml 即可。

from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>Hello</p>', 'lxml')
print(soup.p.string)
'''
Hello
'''

基本使用

下面举个实例来看看BeautifulSoup的基本用法:

html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml') # 初始化
print(soup.prettify())
print(soup.title.string)

运行结果,你们也可以将上面代码复制到编辑器执行看看:

<html>
<head>
<title>
The Dormouse's story
</title>
</head>
<body>
<p class="title" name="dromouse">
<b>
The Dormouse's story
</b>
</p>
<p class="story">
Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1">
<!-- Elsie -->
</a>
,
<a class="sister" href="http://example.com/lacie" id="link2">
Lacie
</a>
and
<a class="sister" href="http://example.com/tillie" id="link3">
Tillie
</a>
;
and they lived at the bottom of a well.
</p>
<p class="story">
...
</p>
</body>
</html>
The Dormouse's story

首先声明一个html变量,它是一个HTML字符串,注意html和body标签都没有闭合。

经过初始化,使用prettify()方法把要解析的字符串以标准缩进格式输出,发现结果中自动补全了html和body标签。这一步不是prettify()方法做的,而是在初始化BeautifulSoup时就完成了。然后调用soup.title.string拿到title里面的文本内容。

通过简单调用几个属性完成文本提取,是不是非常方便呢?

节点选择器

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

选择元素

还是以上面的HTML代码为例,详细说明选择元素的方法:

from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.title)
print(type(soup.title))
print(soup.title.string)
print(soup.head)
print(soup.p)
'''
<title>The Dormouse's story</title>
<class 'bs4.element.Tag'>
The Dormouse's story
<head><title>The Dormouse's story</title></head>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
'''

首先输出title节点的选择结果,包含标签。

接下来输出它的类型,是一个bs4.element.Tag类型,Tag具有一些属性,比如string。

调用string属性可以看到输出节点的文本内容。

继续尝试head、p节点。发现p只取了第一个匹配的节点。说明当有多个节点时只取一个。

获取属性

每个节点可能有多个属性比如id 、class等,选择元素后可以调用attrs获取所有属性:

print(soup.p.attrs)
print(soup.p.attrs['name'])
'''
{'class': ['title'], 'name': 'dromouse'}
dromouse
'''

可以看到attrs返回结果是字典,它把选择节点所有属性都组合成一个字典。取值直接按字典方式即可。

当然还有一种更简单的获取方式:不写attrs,直接在元素后面中括号取值也行:

print(soup.p['name'])
print(soup.p['class'])
'''
dromouse
['title']
'''

但是注意区分:有的返回字符串、有的返回字符串组成的列表。

对于class,一个节点元素可能有多个class,所以返回的是列表。

子节点和子孙节点

选取节点元素之后,如果想要获取它的直接子节点,可以调用 contents 属性,示例如下:

html4 = """
<html>
<head>
<title>The Dormouse's story</title>
</head>
<body>
<p class="story">
钢铁知识库
<a href="http://a.com" class="钢铁学数据分析" id="link1">
<span>Elsie</span>
</a>
<a href="http://b.com" class="钢铁学自动化" id="link2">Lacie</a>
and
<a href="http://example.com" class="cccc" id="link3">Tillie</a>
钢铁学爬虫.
</p>
<p class="story">...</p>
"""
from bs4 import BeautifulSoup
soup = BeautifulSoup(html4, 'lxml')
print(soup.p.contents)
'''
['\n 钢铁知识库\n ', <a class="钢铁学数据分析" href="http://a.com" id="link1">
<span>Elsie</span>
</a>, '\n', <a class="钢铁学自动化" href="http://b.com" id="link2">Lacie</a>, ' \n and\n ', <a class="cccc" href="http://example.com" id="link3">Tillie</a>, '\n 钢铁学爬虫.\n '] '''

可以看到返回结果是列表形式。p 节点里既包含文本,又包含文本,最后统一返回列表。

需要注意,列表中的每个元素都是 p 节点的直接子节点。比如第一个 a 节点里面的span节点,这相当于子孙节点了,但返回结果并没有单独把span节点列出来。所以说,contents属性得到的结果是直接子节点的列表。

同样,我们可以调用children属性得到相应的结果:

from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'lxml')
print(soup.p.children)
for i, child in enumerate(soup.p.children):
print(i, child)
'''
<list_iterator object at 0x0000000001D9A1C0>
0
钢铁知识库 1 <a class="钢铁学数据分析" href="http://a.com" id="link1">
<span>Elsie</span>
</a>
2 3 <a class="钢铁学自动化" href="http://b.com" id="link2">Lacie</a>
4
and 5 <a class="cccc" href="http://example.com" id="link3">Tillie</a>
6
钢铁学爬虫.
'''

还是同样的 HTML 文本,这里调用了 children 属性来选择,返回结果是生成器类型。接下来,我们用 for 循环输出相应的内容。

如果要得到所有的子孙节点的话,可以调用 descendants 属性:

<generator object Tag.descendants at 0x000001D77A90E570>
0
钢铁知识库 1 <a class="钢铁学数据分析" href="http://a.com" id="link1">
<span>Elsie</span>
</a>
2 3 <span>Elsie</span>
4 Elsie
5 6 7 <a class="钢铁学自动化" href="http://b.com" id="link2">Lacie</a>
8 Lacie
9
and 10 <a class="cccc" href="http://example.com" id="link3">Tillie</a>
11 Tillie
12
钢铁学爬虫.

此时返回结果还是生成器。遍历输出一下可以看到,这次的输出结果就包含了 span 节点。descendants 会递归查询所有子节点,得到所有的子孙节点。

除此之外,还有父节点parent 和祖先节点parents,兄弟节点next_siblingprevious_siblings 日常用得少不再演示,后续需要自行查官方文档即可。

方法选择器

前面聊的通过属性选择节点,但如果进行比较复杂的话还是比较繁琐。幸好BeautifulSoup还为我们提供另外一些查询方法,比如find_all 和 find ,调用他们传入相应参数就可以灵活查询。

find_all

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

它的 API 如下:

find_all(name , attrs , recursive , text , **kwargs)

我们可以根据节点名来查询元素,下面我们用一个实例来感受一下:

html5='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">钢铁</li>
<li class="element">知识</li>
<li class="element">仓库</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">python</li>
<li class="element">java</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html5, 'lxml')
print(soup.find_all(name='ul'))
print(type(soup.find_all(name='ul')[0]))
'''
[<ul class="list" id="list-1">
<li class="element">钢铁</li>
<li class="element">知识</li>
<li class="element">仓库</li>
</ul>, <ul class="list list-small" id="list-2">
<li class="element">python</li>
<li class="element">java</li>
</ul>]
<class 'bs4.element.Tag'>
'''

可以看到返回了一个列表,分别是两个ul长度为2,且类型依然是bs4.element.Tag类型。

因为都是Tag类型,所以依然可以继续嵌套查询,还是同样文本,查询ul节点后再继续查询内部li节点。

from bs4 import BeautifulSoup
soup = BeautifulSoup(html5, 'lxml')
for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
'''
[<li class="element">钢铁</li>, <li class="element">知识</li>, <li class="element">仓库</li>]
[<li class="element">python</li>, <li class="element">java</li>]
'''

返回结果是列表类型,元素依然是Tag类型。

接下来我们可以遍历每个li获取它的文本:

for ul in soup.find_all(name='ul'):
print(ul.find_all(name='li'))
for li in ul.find_all(name='li'):
print(li.string)
'''
[<li class="element">钢铁</li>, <li class="element">知识</li>, <li class="element">仓库</li>]
钢铁
知识
仓库
[<li class="element">python</li>, <li class="element">java</li>]
python
java
'''

find

除了 find_all 方法,还有 find 方法,不过 find 方法返回的是单个元素,也就是第一个匹配的元素,而 find_all 返回的是所有匹配的元素组成的列表。示例如下:

html5='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">钢铁</li>
<li class="element">知识</li>
<li class="element">仓库</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">python</li>
<li class="element">java</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html5, 'lxml')
print(soup.find(name='ul'))
print(type(soup.find(name='ul')))
print(soup.find(class_='list')) '''
<ul class="list" id="list-1">
<li class="element">钢铁</li>
<li class="element">知识</li>
<li class="element">仓库</li>
</ul>
<class 'bs4.element.Tag'>
<ul class="list" id="list-1">
<li class="element">钢铁</li>
<li class="element">知识</li>
<li class="element">仓库</li>
</ul>
'''

返回结果不再是列表形式,而是第一个匹配的节点元素,类型依然是 Tag 类型。

其它方法

另外还有许多的查询方法,用法与前面介绍的 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选择器。如果对 Web 开发熟悉的话,那么对 CSS 选择器肯定也不陌生。如果不熟悉的话,可以参考 http://www.w3school.com.cn/cssref/css_selectors.asp 了解。

使用 CSS 选择器,只需要调用 select 方法,传入相应的 CSS 选择器即可,我们用一个实例来感受一下:

html5='''
<div class="panel">
<div class="panel-heading">
<h4>Hello</h4>
</div>
<div class="panel-body">
<ul class="list" id="list-1">
<li class="element">钢铁</li>
<li class="element">知识</li>
<li class="element">仓库</li>
</ul>
<ul class="list list-small" id="list-2">
<li class="element">python</li>
<li class="element">java</li>
</ul>
</div>
</div>
'''
from bs4 import BeautifulSoup
soup = BeautifulSoup(html5, 'lxml')
print(soup.select('.panel .panel-heading'))
print(soup.select('ul li'))
print(soup.select('#list-2 .element'))
print(type(soup.select('ul')[0]))
'''
[<div class="panel-heading">
<h4>Hello</h4>
</div>]
[<li class="element">钢铁</li>, <li class="element">知识</li>, <li class="element">仓库</li>, <li class="element">python</li>, <li class="element">java</li>]
[<li class="element">python</li>, <li class="element">java</li>]
<class 'bs4.element.Tag'>
'''

结果为所有匹配的节点。例如select('ul li')则是所有ul节点下面的所有li节点,返回结果是列表。

select 方法同样支持嵌套选择(soup.select('ul'))、属性获取(ul['id']),以及文本获取(li.string/li.get_text())

---- 钢铁知识库 2022.08.22

结语

到此 BeautifulSoup 的使用介绍基本就结束了,最后钢铁知识库做一下简单的总结:

  • 推荐使用 LXML 解析库,速度快、容错能力强。
  • 建议使用 find、find_all 方法查询匹配单个结果或者多个结果。
  • 如果对 CSS 选择器熟悉的话可以使用 select 匹配,可以像Xpath一样匹配所有。

python爬虫之BeautifulSoup4使用的更多相关文章

  1. Python 爬虫之 Beautifulsoup4,爬网站图片

    安装: pip3 install beautifulsoup4 pip install beautifulsoup4 Beautifulsoup4 解析器使用 lxml,原因为,解析速度快,容错能力强 ...

  2. python爬虫——利用BeautifulSoup4爬取糗事百科的段子

    import requests from bs4 import BeautifulSoup as bs #获取单个页面的源代码网页 def gethtml(pagenum): url = 'http: ...

  3. python爬虫(7)——BeautifulSoup

    今天介绍一个非常好用的python爬虫库--beautifulsoup4.beautifulsoup4的中文文档参考网址是:http://beautifulsoup.readthedocs.io/zh ...

  4. Python爬虫初探 - selenium+beautifulsoup4+chromedriver爬取需要登录的网页信息

    目标 之前的自动答复机器人需要从一个内部网页上获取的消息用于回复一些问题,但是没有对应的查询api,于是想到了用脚本模拟浏览器访问网站爬取内容返回给用户.详细介绍了第一次探索python爬虫的坑. 准 ...

  5. Python爬虫教程-25-数据提取-BeautifulSoup4(三)

    Python爬虫教程-25-数据提取-BeautifulSoup4(三) 本篇介绍 BeautifulSoup 中的 css 选择器 css 选择器 使用 soup.select 返回一个列表 通过标 ...

  6. Python爬虫教程-24-数据提取-BeautifulSoup4(二)

    Python爬虫教程-24-数据提取-BeautifulSoup4(二) 本篇介绍 bs 如何遍历一个文档对象 遍历文档对象 contents:tag 的子节点以列表的方式输出 children:子节 ...

  7. Python爬虫教程-23-数据提取-BeautifulSoup4(一)

    Beautiful Soup是python的一个库,最主要的功能是从网页抓取数据,查看文档 https://www.crummy.com/software/BeautifulSoup/bs4/doc. ...

  8. Python爬虫beautifulsoup4常用的解析方法总结(新手必看)

    今天小编就为大家分享一篇关于Python爬虫beautifulsoup4常用的解析方法总结,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧摘要 如何用beau ...

  9. python3.4学习笔记(十七) 网络爬虫使用Beautifulsoup4抓取内容

    python3.4学习笔记(十七) 网络爬虫使用Beautifulsoup4抓取内容 Beautiful Soup 是用Python写的一个HTML/XML的解析器,它可以很好的处理不规范标记并生成剖 ...

随机推荐

  1. JAVA - 线程同步和线程调度的相关方法

    JAVA - 线程同步和线程调度的相关方法 wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁:wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等 ...

  2. mac上使用Vmware Fusion虚拟机配置Centos的静态ip

    一.背景 本文简单记录一下,在mac arm 架构下使用 Vmware Fusion虚拟机下Centos7下如何配置静态ip地址.如果使用dhcp静态ip地址的动态分配,那么可能ip地址会发生变化,因 ...

  3. 不忍了,快速下载Visual Studio Code

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年5月2日. 奇怪的原因 因为一些众所周知的原因,在国内下载Visual Studio Code的速度比较慢,所以我们需要一些方法来加快 ...

  4. 眼球3D可视化解决方案——案例详解

    医疗器械行业伴随着人类健康需求的增长而不断发展,是名副其实的朝阳行业,也是全球发达国家竞相争夺的领域. 一方面,行业门槛高,集中度低,外资企业挤占市场空间成了我国所有医疗器械行业入局者面临的共同挑战. ...

  5. 让你的Nginx支持分布式追踪

    Background NGINX 是一个通用且流行的应用程序.也是最流行的 Web 服务器,它可用于提供静态文件内容,但也通常与其他服务一起用作分布式系统中的组件,在其中它用作反向代理.负载均衡 或 ...

  6. 安装rlwrap

    一. 安装readlineyum install readline* -y 二. 安装rlwrap[root@dbserver ~]# tar -zxvf rlwrap-0.43.tar.gz[roo ...

  7. cup缓存基础知识

    目录 cup缓存 缓存结构 直接映射缓存 cup缓存 CPU缓存(CPU Cache)的目的是为了提高访问内存(RAM)的效率,这虽然已经涉及到硬件的领域,但它仍然与我们息息相关,了解了它的一些原理, ...

  8. HMS Core安全检测服务如何帮助大学新生防范电信诈骗?

    一年一度的高考结束了,很多学生即将离开父母,一个人踏入大学生活,但由于人生阅历较少,容易被不法分子盯上. 每年开学季也是大一新生遭受诈骗的高峰期,以下是一些常见的案例.有的骗子会让新生下载注册一些恶意 ...

  9. 详解 Apache Hudi Schema Evolution(模式演进)

    Schema Evolution(模式演进)允许用户轻松更改 Hudi 表的当前模式,以适应随时间变化的数据. 从 0.11.0 版本开始,支持 Spark SQL(spark3.1.x 和 spar ...

  10. CF1381B Unmerge(位运算的作用)

    题目大意: 给定长度为 \(2n\) 的排列 \(p\) .确定是否存在两个数组 \(a\) 和 \(b\) ,每个数组的长度都为 \(n\) ,并且没有相等的元素,使得 \(p = \operato ...