本系列文档用于对Python爬虫技术的学习进行简单的教程讲解,巩固自己技术知识的同时,万一一不小心又正好对你有用那就更好了。
Python 版本是3.7.4
前面四篇文章讲了urllib
和requests
两个库的用法,这两个库主要是进行访问网站进行爬取数据,但是由于爬取下来的数据有很多,我们想要的只是其中的一部分,所以下面我们开始讲解如何从爬取的数据中提取我们想要的。
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 = """
BeautifulSoup学习
"""
一个简单小例子:
# 引入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)
# 输出结果:
# BeautifulSoup学习
# 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(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_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()
:前者返回节点后所有符合条件的节点,后者返回第一个符合条件的节点。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的用法基本就介绍完了,最后做一下简单的总结:
find()
或者find_all()
查询匹配单个结果或者多个结果。