和 lxml库 一样,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 数据。
lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM(Document Object Model)的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。
BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。
Beautiful Soup 3 目前已经停止开发,推荐现在的项目使用Beautiful Soup 4。
解析工具 | 解析速度 | 使用难度 |
---|---|---|
BeautifulSoup4 | 最慢 | 最简单 |
lxml库 | 快 | 简单 |
正则 | 最快 | 最难 |
使用以下代码安装BeautifulSoup4库,
pip install bs4
使用bs4库解析html页面,需要先使用BeautifulSoup()方法,实例化一个对象,第一个参数为要解析的html代码,第二个参数为解析方式(如,lxml、html5lib等)。
注意,第二个参数中的解析方式所使用的库,需要提前安装。例如,我们第二个参数设置为lxml,那么我们要保证lxml库已经安装了。
若要显示解析后的html代码,可以直接使用print()打印BeautifulSoup对象,例如,
from bs4 import BeautifulSoup
html = """
职位名称
职位类别
人数
地点
发布时间
22989-金融云区块链高级研发工程师(深圳)
技术类
1
深圳
2017-11-25
22989-金融云高级后台开发
技术类
2
深圳
2017-11-25
SNG16-腾讯音乐运营开发工程师(深圳)
技术类
2
深圳
2017-11-25
SNG16-腾讯音乐业务运维工程师(深圳)
技术类
1
深圳
2017-11-25
TEG03-高级研发工程师(深圳)
技术类
1
深圳
2017-11-24
TEG03-高级图像算法研发工程师(深圳)
技术类
1
深圳
2017-11-24
TEG11-高级AI开发工程师(深圳)
技术类
4
深圳
2017-11-24
15851-后台开发工程师
技术类
1
深圳
2017-11-24
15851-后台开发工程师
技术类
1
深圳
2017-11-24
SNG11-高级业务运维工程师(深圳)
技术类
1
深圳
2017-11-24
"""
soup = BeautifulSoup(html, "lxml")
print(soup) # 也可以print(soup.prettify()),这样会显示经过格式化后的html代码,层级更加明显
执行结果如下,
<html><body><table cellpadding="0" cellspacing="0" class="tablelist">
<tbody>
<tr class="h">
<td class="l" width="374">职位名称</td>
<td>职位类别</td>
<td>人数</td>
<td>地点</td>
<td>发布时间</td>
</tr>
<tr class="even">
<td class="l square"><a href="position_detail.php?id=33824&keywords=python&tid=87&lid=2218" target="_blank">22989-金融云区块链高级研发工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
<tr class="odd">
<td class="l square"><a href="position_detail.php?id=29938&keywords=python&tid=87&lid=2218" target="_blank">22989-金融云高级后台开发</a></td>
<td>技术类</td>
<td>2</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
<tr class="even">
<td class="l square"><a href="position_detail.php?id=31236&keywords=python&tid=87&lid=2218" target="_blank">SNG16-腾讯音乐运营开发工程师(深圳)</a></td>
<td>技术类</td>
<td>2</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
<tr class="odd">
<td class="l square"><a href="position_detail.php?id=31235&keywords=python&tid=87&lid=2218" target="_blank">SNG16-腾讯音乐业务运维工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
<tr class="even">
<td class="l square"><a href="position_detail.php?id=34531&keywords=python&tid=87&lid=2218" target="_blank">TEG03-高级研发工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
<tr class="odd">
<td class="l square"><a href="position_detail.php?id=34532&keywords=python&tid=87&lid=2218" target="_blank">TEG03-高级图像算法研发工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
<tr class="even">
<td class="l square"><a href="position_detail.php?id=31648&keywords=python&tid=87&lid=2218" target="_blank">TEG11-高级AI开发工程师(深圳)</a></td>
<td>技术类</td>
<td>4</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
<tr class="odd">
<td class="l square"><a href="position_detail.php?id=32218&keywords=python&tid=87&lid=2218" target="_blank">15851-后台开发工程师</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
<tr class="even">
<td class="l square"><a href="position_detail.php?id=32217&keywords=python&tid=87&lid=2218" target="_blank">15851-后台开发工程师</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
<tr class="odd">
<td class="l square"><a class="test" href="position_detail.php?id=34511&keywords=python&tid=87&lid=2218" target="_blank">SNG11-高级业务运维工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
</tbody>
</table>
</body></html>
我们可以看到,html代码被补全。
搜索文档树,一般用得比较多的就是两个方法,一个是find(),一个是find_all()。find()方法是找到第一个满足条件的标签后就立即返回,只返回一个元素。find_all()方法是把所有满足条件的标签都选到,然后生成一个列表返回回去。使用这两个方法,最常用的用法是出入name以及attrs参数找出符合要求的标签。
我们来获取上例中html代码中所有的tr标签,
# 获取所有的tr标签
trs = soup.find_all('tr')
for tr in trs:
print(tr)
print("=" * 50)
执行结果如下,
<tr class="h">
<td class="l" width="374">职位名称</td>
<td>职位类别</td>
<td>人数</td>
<td>地点</td>
<td>发布时间</td>
</tr>
==================================================
<tr class="even">
<td class="l square"><a href="position_detail.php?id=33824&keywords=python&tid=87&lid=2218" target="_blank">22989-金融云区块链高级研发工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
==================================================
<tr class="odd">
<td class="l square"><a href="position_detail.php?id=29938&keywords=python&tid=87&lid=2218" target="_blank">22989-金融云高级后台开发</a></td>
<td>技术类</td>
<td>2</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
==================================================
<tr class="even">
<td class="l square"><a href="position_detail.php?id=31236&keywords=python&tid=87&lid=2218" target="_blank">SNG16-腾讯音乐运营开发工程师(深圳)</a></td>
<td>技术类</td>
<td>2</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
==================================================
<tr class="odd">
<td class="l square"><a href="position_detail.php?id=31235&keywords=python&tid=87&lid=2218" target="_blank">SNG16-腾讯音乐业务运维工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
==================================================
<tr class="even">
<td class="l square"><a href="position_detail.php?id=34531&keywords=python&tid=87&lid=2218" target="_blank">TEG03-高级研发工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
==================================================
<tr class="odd">
<td class="l square"><a href="position_detail.php?id=34532&keywords=python&tid=87&lid=2218" target="_blank">TEG03-高级图像算法研发工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
==================================================
<tr class="even">
<td class="l square"><a href="position_detail.php?id=31648&keywords=python&tid=87&lid=2218" target="_blank">TEG11-高级AI开发工程师(深圳)</a></td>
<td>技术类</td>
<td>4</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
==================================================
<tr class="odd">
<td class="l square"><a href="position_detail.php?id=32218&keywords=python&tid=87&lid=2218" target="_blank">15851-后台开发工程师</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
==================================================
<tr class="even">
<td class="l square"><a href="position_detail.php?id=32217&keywords=python&tid=87&lid=2218" target="_blank">15851-后台开发工程师</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
==================================================
<tr class="odd">
<td class="l square"><a class="test" href="position_detail.php?id=34511&keywords=python&tid=87&lid=2218" target="_blank">SNG11-高级业务运维工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
==================================================
我们可以在find_all()方法中添加limit参数获取指定个数的相应标签,例如,我们获取html代码中第二个tr标签,
# 获取第二个tr标签
tr = soup.find_all('tr', limit=2)[-1]
print(tr)
执行结果如下,
<tr class="even">
<td class="l square"><a href="position_detail.php?id=33824&keywords=python&tid=87&lid=2218" target="_blank">22989-金融云区块链高级研发工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
我们可以通过在find()和find_all()方法中添加attrs参数获取指定要求的标签,注意attrs参数为字典形式。例如,获取html代码中class属性为even的tr标签,
# 获取所有class为even的标签
trs = soup.find_all('tr', attrs={'class': 'even'})
for tr in trs:
print(tr)
print("=" * 50)
执行结果如下,
<tr class="even">
<td class="l square"><a href="position_detail.php?id=33824&keywords=python&tid=87&lid=2218" target="_blank">22989-金融云区块链高级研发工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
==================================================
<tr class="even">
<td class="l square"><a href="position_detail.php?id=31236&keywords=python&tid=87&lid=2218" target="_blank">SNG16-腾讯音乐运营开发工程师(深圳)</a></td>
<td>技术类</td>
<td>2</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
==================================================
<tr class="even">
<td class="l square"><a href="position_detail.php?id=34531&keywords=python&tid=87&lid=2218" target="_blank">TEG03-高级研发工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
==================================================
<tr class="even">
<td class="l square"><a href="position_detail.php?id=31648&keywords=python&tid=87&lid=2218" target="_blank">TEG11-高级AI开发工程师(深圳)</a></td>
<td>技术类</td>
<td>4</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
==================================================
<tr class="even">
<td class="l square"><a href="position_detail.php?id=32217&keywords=python&tid=87&lid=2218" target="_blank">15851-后台开发工程师</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
==================================================
我们也可以在find()和find_all()方法中直接添加相应属性的值来获取指定标签。例如,我们获取html代码中class和id属性均为test的a标签,
# 获取所有id和class属性为test的a标签
trs = soup.find_all('a', id='test', class_='test') # 注意class为关键字,因此需要加下划线
for tr in trs:
print(tr)
print("=" * 50)
执行结果如下,
<a class="test" href="position_detail.php?id=34511&keywords=python&tid=87&lid=2218" id="test" target="_blank">SNG11-高级业务运维工程师(深圳)</a>
==================================================
我们如果只获取纯文本数据,可以使用string、strings和stripped_strings。
注意,string用与获取单行字符串;strings和stripped_strings用于返回多行字符串,会返回一个生成器。例如,我们使用string获取html中的所有职位信息,
# 获取所有职位的信息(纯文本)
jobs = [] # 建立值为列表,用于存放所有的职位信息
trs = soup.find_all('tr')[1:] # 因为第一个tr标签为表头,所以从第二个tr开始取值
for tr in trs:
job = {}
tds = tr.find_all('td')
title = tds[0].string
category = tds[1].string
num = tds[2].string
city = tds[3].string
pub_time = tds[4].string
# 将相应数据存入job字典
job['title'] = title
job['category'] = category
job['num'] = num
job['city'] = city
job['pub_time'] = pub_time
jobs.append(job) # 将job字典添加到jobs列表中
print(jobs)
执行结果如下,
[{'title': '22989-金融云区块链高级研发工程师(深圳)', 'category': '技术类', 'num': '1', 'city': '深圳', 'pub_time': '2017-11-25'}, {'title': '22989-金融云高级后台开发', 'category': '技术类', 'num': '2', 'city': '深圳', 'pub_time': '2017-11-25'}, {'title': 'SNG16-腾讯音乐运营开发工程师(深圳)', 'category': '技术类', 'num': '2', 'city': '深圳', 'pub_time': '2017-11-25'}, {'title': 'SNG16-腾讯音乐业务运维工程师(深圳)', 'category': '技术类', 'num': '1', 'city': '深圳', 'pub_time': '2017-11-25'}, {'title': 'TEG03-高级研发工程师(深圳)', 'category': '技术类', 'num': '1', 'city': '深圳', 'pub_time': '2017-11-24'}, {'title': 'TEG03-高级图像算法研发工程师(深圳)', 'category': '技术类', 'num': '1', 'city': '深圳', 'pub_time': '2017-11-24'}, {'title': 'TEG11-高级AI开发工程师(深圳)', 'category': '技术类', 'num': '4', 'city': '深圳', 'pub_time': '2017-11-24'}, {'title': '15851-后台开发工程师', 'category': '技术类', 'num': '1', 'city': '深圳', 'pub_time': '2017-11-24'}, {'title': '15851-后台开发工程师', 'category': '技术类', 'num': '1', 'city': '深圳', 'pub_time': '2017-11-24'}, {'title': 'SNG11-高级业务运维工程师(深圳)', 'category': '技术类', 'num': '1', 'city': '深圳', 'pub_time': '2017-11-24'}]
或接下来,我们使用stripped_strings获取所有职位信息,
jobs = [] # 建立值为列表,用于存放所有的职位信息
trs = soup.find_all('tr')[1:] # 因为第一个tr标签为表头,所以从第二个tr开始取值
for tr in trs:
job = {}
infos = list(tr.stripped_strings)
job['title'] = infos[0]
job['category'] = infos[1]
job['nums'] = infos[2]
job['city'] = infos[3]
job['pub_time'] = infos[4]
jobs.append(job)
print(jobs)
我们也可以使用select()方法获取特定标签。代码如下,
# 获取所有的tr标签
trs = soup.select('tr') # 返回值为列表
for tr in trs:
print(tr)
print(type(tr))
print("=" * 50)
# 获取第二个tr标签
tr = soup.select('tr')[1] # 通过列表的下标获取第二个标签
print(tr)
# 获取所有class为even的标签
trs = soup.select('.even') # 或者使用trs = soup.select('tr[class="even"]'),.属性值为获取指定class属性的标签
for tr in trs:
print(tr)
print("=" * 50)
# 将所有id和class为test的a标签找出来
aList = soup.select('#test, .test') # #属性值为获取指定id属性的值,注意,如果有两个筛选条件需要加逗号分隔
for a in aList:
print(a)
# 获取所有a标签的属性
aList = soup.select('a')
for a in aList:
print(a['href'])
# 获取所有职位的信息(纯文本)
trs = soup.select('tr')
for tr in trs:
infos = list(tr.stripped_strings)
print(infos)