文章目录
一 什么是BeautifulSoup
BeautifulSoup是python的一个HTML或者XML的解析库,可以通过它来实现对网页的解析,从而获得想要的数据。
在用BeautifulSoup库进行网页解析时,其实还是要依赖解析器的,python有自己内置的html.parser解析器,Beautiful肯定是支持html.parse解析器的。不过除此之外,还有一些第三方解析器比如lxml,xml以及html5lib,其中lxml在这几个中是比较有优势的,主要体现在解析速度快,容错能力强。所以下面主要是围绕lxml解析器进行记录的。
使用前请检查安装lxml库和BeautifulSoup库。
二 使用BeautifulSoup
1. 初始化网页源码
大多数情况下我们获取到的HTML都是字符串,而且格式不一,这很不方便我们从中提取数据,这时就可以将HTML源码传给BeautifulSoup库初始化,进而将字符串的HTML转换成BeautifulSoup对象。如下
from bs4 import BeautifulSoup
html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>BeautifulSoup学习</title>
</head>
<body>
<div id="song list">
<h2 class="title">经典老歌</h2>
<p class="introduction">
经典老歌列表
</p>
<ul id="list" class="list-group">
<li data-view="2">一路上有你</li><li>这是测试</li>
<li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li data-view="4" class="active">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
<li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
<li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
</html>
'''
#初始化html
sp = BeautifulSoup(html, 'lxml')
print(type(sp))
sp.prettify()
print(sp)
如上,BeautifulSoup()接受两个参数,一个是html字符串源码,第二个是要使用的解析器。我这里使用的是lxml解析器,经过初始化后的html字符串就转换成了树结构的BeautifulSoup对象了。BeautifulSoup在初始化的同时还会自动补全缺失的标签。如上html字符串结尾缺少了/div,/body标签,初始化后就会自动补上,sp.prettify()控制缩进,美化html。结果如下
>>>
<class 'bs4.BeautifulSoup'>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>BeautifulSoup学习</title>
</head>
<body>
<div id="song list">
<h2 class="title">经典老歌</h2>
<p class="introduction">
经典老歌列表
</p>
<ul class="list-group" id="list">
<li data-view="2">一路上有你</li>
<li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li class="active" data-view="4">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
<li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
<li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
</ul></div></body></html>
2. 节点选择器
节点选择器是通过一些html标签来提取对应的内容,如p,a,li等标签。如下
2.1 通过html 标签匹配
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
print(type(sp.title))
print(sp.title)
print(type(sp.h2))
print(sp.h2)
输出:
>>>
<class 'bs4.element.Tag'>
<title>BeautifulSoup学习</title>
<class 'bs4.element.Tag'>
<h2 class="title">经典老歌</h2>
如上,可以通过html标签元素来提取信息,也称节点选择。可以看到返回的结果类型都是bs4.element.Tag,凡是返回Tag类型的数据,它们都有如下属性
- name: 返回节点的名字。
- string:获取节点内容,但仅限于节点里面没有其他节点的时候,返回类型NavigableString,可通过str()函数转为字符串。
- attrs: 获取节点属性,字典形式返回。
如下
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
print("节点html:", sp.h2)
print("节点名字:", sp.h2.name)
print("节点内容:", sp.h2.string)
print("节点属性:", sp.h2.attrs)
>>>
节点html: <h2 class="title">经典老歌</h2>
节点名字: h2
节点内容: 经典老歌
节点属性: {'class': ['title']}
上面提取的都是只有单个节点的情况,而且节点里面没有其他节点。下面看看有多个同名节点,节点里面又嵌套其它节点的情况
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
print(sp.a)
输出结果
>>>
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
可以看到输出结果只有第一个a节点,其他a节点并没有输出,所有通过这种方式只能提取出第一个符合的内容。
再看看节点里面有嵌套其它节点时,用string属性获取文本内容。如下
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
print("节点div的html: ", sp.div)
print(type(sp.div))
print("string获取内容:", sp.div.string)
>>>
节点div的html: <div id="song list">
<h2 class="title">经典老歌</h2>
<p class="introduction">
经典老歌列表
</p>
<ul class="list-group" id="list">
<li data-view="2">一路上有你</li>
<li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li class="active" data-view="4">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
<li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
<li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
</ul></div>
<class 'bs4.element.Tag'>
string获取内容: None
可以看到div节点里面还包含了其它节点,再用string获取内容时返回是None,即无法获取
2.2 关联选择
从上面可以看到,当a节点不止一个时,用节点选择的方法只能返回第一个匹配的值,这时候我们可以用一些关联选择的方法来进行更多的匹配,如下
- 兄弟节点
next_sibling: 获取节点的下一个兄弟节点
next_siblings: 获取节点后面的兄弟节点,返回生成器类型
previous_sibling: 获取节点的上一个兄弟节点
previous_siblings: 获取节点前面的兄弟节点,返回生成器类型
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
print("获取第一个li节点:", sp.li)
print(type(sp.li.next_sibling))
print("获取第一个li节点的下一个兄弟节点:", sp.li.next_sibling)
print(type(sp.li.next_siblings))
print("获取第一个li节点后面的兄弟节点:", sp.li.next_siblings)
for i in sp.li.next_siblings:
print(i)
获取第一个li节点: <li data-view="2">一路上有你</li>
<class 'bs4.element.Tag'>
获取第一个li节点的下一个兄弟节点: <li>这是测试</li>
<class 'generator'>
获取第一个li节点后面的兄弟节点: <generator object PageElement.next_siblings at 0x000001D4AD66E5C8>
<li>这是测试</li>
<li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>
<li class="active" data-view="4">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>
<li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>
<li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>
<li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>
注意next_sibling 返回的是一个Tag类型的对象,而next_siblings 返回的是一个生成器,可以通过遍历获取数据。(由于我是手敲的html,有手动换行,所以兄弟节点存在空的)
- 子节点和子孙节点
contents或者children: 获取直接子节点,centents返回列表,children返回生成器类型
descendants: 获取所有子孙节点,返回生成器类型
获取ul节点的子节点:
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
for i in enumerate(sp.ul.contents):
print(i)
>>>
(0, '\n')
(1, <li data-view="2">一路上有你</li>)
(2, <li>这是测试</li>)
(3, '\n')
(4, <li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>)
(5, '\n')
(6, <li class="active" data-view="4">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>)
(7, '\n')
(8, <li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>)
(9, '\n')
(10, <li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>)
(11, '\n')
(12, <li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>)
(13, '\n')
可以看到ul里面的a节点是没有被单独提取出来的,而是嵌套在li节点里面的,以为contents获取的是直接子节点。
获取ul节点的子孙节点:
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
print(type(sp.ul.descendants))
for i in enumerate(sp.ul.descendants):
print(i)
<class 'generator'>
(0, '\n')
(1, <li data-view="2">一路上有你</li>)
(2, '一路上有你')
(3, <li>这是测试</li>)
(4, '这是测试')
(5, '\n')
(6, <li data-view="7">
<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
</li>)
(7, '\n')
(8, <a href="/2.mp3" singer="任贤齐">沧海一声笑</a>)
(9, '沧海一声笑')
(10, '\n')
(11, '\n')
(12, <li class="active" data-view="4">
<a href="/3.mp3" singer="齐秦">往事随风</a>
</li>)
(13, '\n')
(14, <a href="/3.mp3" singer="齐秦">往事随风</a>)
(15, '往事随风')
(16, '\n')
(17, '\n')
(18, <li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li>)
(19, <a href="/4.mp3" singer="beyond">光辉岁月</a>)
(20, '光辉岁月')
(21, '\n')
(22, <li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li>)
(23, <a href="/5.mp3" singer="陈慧琳">记事本</a>)
(24, '记事本')
(25, '\n')
(26, <li data-view="5">
<a href="/6.mp3" singer="邓丽君">但愿人长久</a>
</li>)
(27, '\n')
(28, <a href="/6.mp3" singer="邓丽君">但愿人长久</a>)
(29, '但愿人长久')
(30, '\n')
(31, '\n')
- 父节点和祖先节点
parent:返回节点的父节点,返回Tag类型
parents: 返回节点的祖先节点,返回生成器类型
返回ul节点的父节点:
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
print(type(sp.ul.parent))
print(sp.ul.parent.name)
>>>
<class 'bs4.element.Tag'>
div
获取ul节点的祖先节点:
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
print(type(sp.ul.parents))
for i in sp.ul.parents:
print(i.name)
<class 'generator'>
div
body
html
[document]
3. 方法选择器
上面讲的都是通过html元素标签来提取数据,对于一些结构简单的html使用是很直接,很方便的,但是在html比较复杂的时候,要想准确的提取出数据还是比较繁琐的,这时候就得用上方法选择器了,具体如下。
3.1 方法选择器:find_all()
find_all顾名思义就是查找所有满足条件的值,用法如下
- find_all(name, attrs, recursive, text, **kargs ), 返回列表
name: 根据节点名称来选择,传入形式name=value
attrs: 根据属性来选择,attrs值为字典形式
recursive: 限定直接子节点
text: 根据文本来选择,传入形式是字符串,可以是正则表达式。
根据节点名称name选择
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
a = sp.find_all(name='a')#提取a节点信息
print(a)
>>>
[<a href="/2.mp3" singer="任贤齐">沧海一声笑</a>, <a href="/3.mp3" singer="齐秦">往事随风</a>, <a href="/4.mp3" singer="beyond">光辉岁月</a>, <a href="/5.mp3" singer="陈慧琳">记事本</a>, <a href="/6.mp3" singer="邓丽君">但愿人长久</a>]
可以看到,find_all是匹配出里所有的a节点信息,不像节点选择器则匹配第一个满足条件的值。
根据属性attrs选择
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
title = sp.find_all(attrs={'class': 'title'})
print(title)
>>>
[<h2 class="title">经典老歌</h2>]
根据文本text选择
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
h2 = sp.find_all(text=r'经典老歌')
print(h2)
>>>
['经典老歌']
4. CSS选择器
除了上面的节点选择器和方法选择器外,还有一个CSS选择器,如果对CSS比较熟悉的话也是可以此方法来进行数据提取的。CSS选择器主要是通过调用select()方法来实现,具体如下。
4.1 CSS选择器:select()
- select(‘css选择器’) : 参数是CSS选择器,返回列表形式
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
a = sp.select('#list a')#CSS选择器提取歌单
for i, j in enumerate(a):
print(i, j)
>>>
0 <a href="/2.mp3" singer="任贤齐">沧海一声笑</a>
1 <a href="/3.mp3" singer="齐秦">往事随风</a>
2 <a href="/4.mp3" singer="beyond">光辉岁月</a>
3 <a href="/5.mp3" singer="陈慧琳">记事本</a>
4 <a href="/6.mp3" singer="邓丽君">但愿人长久</a>
上面选择器传入了两个值,一个是 #list 提取 id=list 的html,后面的 a 是在 id=list 的结果里面再提取a节点的内容。
4.2 获取文本:get_text()
上面说过,string只能在节点内没有其他节点的时候获取文本内容,而get_text()方法会获取所有文本内容,如下
#初始化html
sp = BeautifulSoup(html, 'lxml')
sp.prettify()
# 通过html标签匹配
li = sp.select('li')#CSS选择器提取歌单
for i in li:
print("get_text获取文本: ", i.get_text().strip())
print("string获取文本: ", i.string)
>>>
get_text获取文本: 一路上有你
string获取文本: 一路上有你
get_text获取文本: 这是测试
string获取文本: 这是测试
get_text获取文本: 沧海一声笑
string获取文本: None
get_text获取文本: 往事随风
string获取文本: None
get_text获取文本: 光辉岁月
string获取文本: 光辉岁月
get_text获取文本: 记事本
string获取文本: 记事本
get_text获取文本: 但愿人长久
string获取文本: None
可以看到,当节点里面没有嵌套其他节点时,get_text()和string 都能获取文本内容,实现的效果是一样的,但当节点里面嵌套有其他节点是,string则无法获取,返回None,而get_text()则还能正常提取。