Python爬虫2.1 — BeautifulSoup用法教程
综述
本系列文档用于对Python爬虫技术的学习进行简单的教程讲解,巩固自己技术知识的同时,万一一不小心又正好对你有用那就更好了。
Python 版本是3.7.4
前面四篇文章讲了urllib
和requests
两个库的用法,这两个库主要是进行访问网站进行爬取数据,但是由于爬取下来的数据有很多,我们想要的只是其中的一部分,所以下面我们开始讲解如何从爬取的数据中提取我们想要的。
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
属性来获取文本的值,那么如何获取节点属性的值呢?如何获取节点名呢?下面来统一梳理一下信息的提取方式。
- 获取名称
可以利用name
属性获取节点的名称,这里还是以上吗的文本为例,选区title
节点,然后掉用name
属性就可以得到节点名称:
# 引入bs4库
from bs4 import BeautifulSoup
# 构造解析对象
soup = BeautifulSoup(html, 'lxml')
# 打印输出title节点的名称
print(soup.title.name)
# 输出结果:title
- 获取属性
每个节点可能有多个属性,比如id
和class
等,选择这个节点元素后,可以调用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
,所以返回的是列表。在实际处理过程中,我们要注意判断类型。
- 获取内容
可以利用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学习
关联选择
在做选择的时候,有时候不能做到一步就选到想要的节点元素,需要先选中某一个节点元素,然后以它为基准再选择它的子节点、父节点、兄弟节点等,这里就来介绍如何选择这些节点元素。
- 子节点和子孙节点
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)
- 父节点和祖先节点
parent
获取所选取节点元素的父节点parents
获取所选取节点元素的所有祖先节点
示例代码如下:
# 引入bs4库
from bs4 import BeautifulSoup
# 构造解析对象
soup = BeautifulSoup(html, 'lxml')
# 选取第一个p节点元素之后
# 获取它的父节点,可以调用parent属性
print(soup.p.parent)
# 调用parents属性得到相其所有祖先节点
print(soup.head.parents)
- 兄弟节点
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()方法选择。
其他博文链接
- Python爬虫1.1 — urllib基础用法教程
- Python爬虫1.2 — urllib高级用法教程
- Python爬虫1.3 — requests基础用法教程
- Python爬虫1.4 — requests高级用法教程
转载:https://blog.csdn.net/Zhihua_W/article/details/100191978