Beautiful Soup是一个可以从HTML或XML文件中提取数据的Python库,它能够通过自己喜欢的转换器实现惯用的文档当行,查找,修改文档的方式
bs4的介绍和安装
bs4安装
由于Beautiful Soup是第三方库,因此需要单独下载;
由于bs4解析页面时需要依赖文档解析器,所以需要安装lxml作为解析库为前提,才能进一步安装bs4
pip install lxml
pip install bs4
文档解析器优缺点
大部分情况下我们选择的是lxml作为解析器,因为效率更高。在Python2.7.3之前的版本和Python3中的3.2.2之前的版本,必须安装lxml或者html5lib,因为那些Python版本的标准库中内置的HTML解析方法不够稳定。
提示:如果一段HTML或XML文档格式不正确的话,那么在不同的解析器中返回的结果可能是不一样的。因此我们可以根据具体情况去选择对应的文档解析器。
bs4的使用
#从bs4库中导入BeautifulSoup模块
from bs4 import BeautifulSoup
#将html字符串转变成BeautifulSoup对象
soup=BeautifulSoup(html)
对象的种类
Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:
- tag:html中的标签。可以通过BeautifulSoup分析tag的具体内容,具体格式为soup.name,其中name是html下的标签
- NavigableString:标签中的文本对象
- BeautifulSoup:整个html文本对象。可以用作为tag对象
- Comment:特殊的NavigableString对象,如果html标签中有注释,则可能过滤注释符号并保留注释文本
遍历文档树
遍历子节点
- contents返回的是一个所有子节点的列表
- children返回的是一个子节点的迭代器
- descendants返回的是一个生成器遍历子子孙孙
- string获取
一个标签里面的内容 - strings返回的是一个生成器对象用过来获取
多个标签内容 - stripped_strings和strings基本一致,但是它可以把多余的
空格去掉
遍历父节点
- parent直接获得父节点
- parents获取所有的父节点
遍历兄弟节点
- next_sibling下一个兄弟节点
- previous_sibling上一个兄弟节点
- next_siblings下面所有兄弟节点
- previous_siblings上面所有兄弟节点
from bs4 import BeautifulSoup
html_doc = """
<html>
<head><title>The Dormouse's story</title></head>
<body>
<p class="title"><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>
</body>
</html>
"""
soup=BeautifulSoup(html_doc,'lxml')
# 获取单个标签中的内容
r1=soup.title.string
print(r1)
# 获取单个标签中的多个内容
# r2=soup.body.strings #<generator object Tag._all_strings at 0x000001DE260B9120>
r2=''.join(soup.body.strings)
print(r2)
# stripped_strings和strings用法基本一致,只是可以去除空格
r3=soup.body.stripped_strings
for i in r3:
print(i)
搜索文档树
find()
find()方法返回搜索到的第一条数据
find_all()
find_all()方法以列表形式返回所有的搜索到的标签数据
实例应用
html = """
<table class="tablelist" cellpadding="0" cellspacing="0">
<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 target="_blank" href="position_detail.php?id=33824&keywords=python&tid=87&lid=2218">22989-金融云区块链高级研发工程师(深圳)</a></td>
<td>技术类</td>
<td>1</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
<tr class="odd">
<td class="l square"><a target="_blank" href="position_detail.php?id=29938&keywords=python&tid=87&lid=2218">22989-金融云高级后台开发</a></td>
<td>技术类</td>
<td>2</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
<tr class="even">
<td class="l square"><a target="_blank" href="position_detail.php?id=31236&keywords=python&tid=87&lid=2218">SNG16-腾讯音乐运营开发工程师(深圳)</a></td>
<td>技术类</td>
<td>3</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
<tr class="odd">
<td class="l square"><a target="_blank" href="position_detail.php?id=31235&keywords=python&tid=87&lid=2218">SNG16-腾讯音乐业务运维工程师(深圳)</a></td>
<td>技术类</td>
<td>4</td>
<td>深圳</td>
<td>2017-11-25</td>
</tr>
<tr class="even">
<td class="l square"><a target="_blank" href="position_detail.php?id=34531&keywords=python&tid=87&lid=2218">TEG03-高级研发工程师(深圳)</a></td>
<td>技术类</td>
<td>5</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
<tr class="odd">
<td class="l square"><a target="_blank" href="position_detail.php?id=34532&keywords=python&tid=87&lid=2218">TEG03-高级图像算法研发工程师(深圳)</a></td>
<td>技术类</td>
<td>6</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
<tr class="even">
<td class="l square"><a target="_blank" href="position_detail.php?id=31648&keywords=python&tid=87&lid=2218">TEG11-高级AI开发工程师(深圳)</a></td>
<td>技术类</td>
<td>7</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
<tr class="odd">
<td class="l square"><a target="_blank" href="position_detail.php?id=32218&keywords=python&tid=87&lid=2218">15851-后台开发工程师</a></td>
<td>技术类</td>
<td>8</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
<tr class="even">
<td class="l square"><a target="_blank" href="position_detail.php?id=32217&keywords=python&tid=87&lid=2218">15851-后台开发工程师</a></td>
<td>技术类</td>
<td>9</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
<tr class="odd">
<td class="l square"><a id="test" class="test" target='_blank' href="position_detail.php?id=34511&keywords=python&tid=87&lid=2218">SNG11-高级业务运维工程师(深圳)</a></td>
<td>技术类</td>
<td>10</td>
<td>深圳</td>
<td>2017-11-24</td>
</tr>
</tbody>
</table>
"""
from bs4 import BeautifulSoup
soup=BeautifulSoup(html,'lxml')
# 查询所有的tr标签
r1=soup.find_all('tr')
print(r1)
# 查询第一个的tr标签
r2=soup.find('tr')
print(r2) #法1
print('xxxxx'*10)
r3=soup.find_all('tr')[0]
print(r3) #法2
# 查询所有的class =even的tr标签
r4=soup.find_all('tr',class_='even') #法1,不能直接用class,class是作为我们的关键字
print(r4)
r5=soup.find_all("tr",attrs={'class':"even"}) #法2
print(r5)
# 获取所有a标签里面的href属性值
r6=soup.find_all('a')
for i in r6:
print(i.get('href'))
# 获取所有岗位信息
r7=soup.find_all('tr')
for i in r7:
print(i.text) #法1
r8=''.join(soup.tbody.strings)
print(r8) #法2
注意:以上发现用find()的时候可以直接.text就能输出文本内容,但是find_all()却不行,那是因为,find()输出的结果类型是Tag类型,而find_all()输出的却是ResultSet类型,ResultSet对象是没有text的,所以就可以用for循环去find_all输出结果中遍历一遍,遍历之后就是Tag对象了。
select()方法
除了以上的两种方法,我们也可以通过css选择器的方式来提取数据。但是需要注意的是这里面需要我们掌握css语法www.w3school.com.cn/cssref/css_…
# 获取所有的tr标签
r1= soup.select('tr') #用法类似于find_all()
print(r1)
# 获取第二个tr标签
r2= soup.select('tr')[1]
print(r2)
# 获取所有class等于even的tr标签
r3=soup.select('.even')
print(r3)
# 获取所有的a标签的href属性
r4=soup.select('a')
for i in r4:
print(i.get('href'))
# 获取所有的职位信息
r5=soup.select('tr')
for i in r5:
print(i.text) #法1
print(''.join(soup.tbody.strings)) #法2
csv模块
什么是csv?
CSV (Comma Separated Values),就是逗号分隔值(也称之为字符分隔值,因为分隔符可以不是逗号),是一种常用的文本格式,用来存储表格数据,包括数字和字符。很多程序在处理文件的时候都会碰到csv这种格式的文件。python自带了csv模块,专门用来处理csv文件的读取
csv模块的使用
写入csv文件
- 通过创建writer对象,主要用到2个方法。一个是
writerow,写入一行,另一个是writerows写入多行; - 通过
Dictwriter可以使用字典的方式把数据写入
读取csv文件
- 通过
reader()读取到的每一条数据是一个列表。可以通过下标的方式获取具体的某一个值 - 通过
DictReader()读取到的数据是一个字典。可以通过Key值(列名)的方式获取数据
import csv
header={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
persons1 = [('小明', 27, 175), ('小美', 18, 178), ('小兰', 30, 175)]
headers1 = ('name', 'age', 'heigth')
with open('Person.py','w',encoding='utf-8',newline='') as f:#newline参数不加的话会默认行与行之间有个空行
writer1=csv.writer(f) #创建writer对象
writer1.writerow(headers1) #将表头写进去
for i in persons1:
writer1.writerow(i) #将列表中的值写入
# Dictwriter 写入字典数据格式
persons2 = [
{'name': '明明', 'age': 18, 'gender': '男'},
{'name': '梅梅', 'age': 18, 'gender': '女'},
{'name': '兰兰', 'age': 18, 'gender': '女'}
]
headers2 = ('name', 'age', 'gender')
with open('Person1.py','w',encoding='utf-8',newline='') as f:
writer2=csv.DictWriter(f,headers2)
writer2.writeheader() #写入表头,字典格式写入特有的
writer2.writerows(persons2) #写入数据
# 方法1:csv读取文件
with open('Person.py','r',encoding='utf-8',newline='') as f:
reader1=csv.reader(f)
# print(reader) #<_csv.reader object at 0x000001E15089CEE0>
for i in reader1:
print(i)
# 方法2:csv读取文件
with open('Person1.py','r',encoding='utf-8',newline='') as f:
reader2=csv.DictReader(f)
# print(reader2) #<csv.DictReader object at 0x0000014698AC0B50>
for i in reader2:
# print(i) #法1
for k,v in i.items():
print(k,v)
代理
import random
user_agent_list = [
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0',
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser)',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)'
]
proxies_list = [
'218.75.102.198:8000',
'180.123.162.172:8899'
'118.120.209.134:41122',
'114.99.3.122:8089',
'180.121.135.129:8089',
'114.104.134.70:41122',
'103.118.46.174:8080',
'180.120.212.15:8089',
'58.246.58.150:9002',
'118.120.231.118:41122',
'112.53.184.170:9091'
]
headers = {'user-agent':random.choice(user_agent_list)}
proxies = {'http':random.choice(proxies_list)}
以上代理的方法是为了防止频繁多次地访问浏览器之后被封掉。
实战演练:
'''
目标: 豆瓣小说
需求:获取小说 的名称 评分 引言 详情页的url 并保存在csv文件中
保存数据格式: [{'title':名称,'link':详情页url,...},{},{}...]
涉及的技术 request bs4 csv
思路与页面分析:
1 获取网页源码并创建soup对象
2 解析网页拿到目标数据
2.1 先找到每条小说的tag标签 每条小说的信息都是在列表中的li标签中
2.2 在li标签下找到对应数据存放的标签 并获取数据
3 将获取的数据进行存储
'''
法1:
import random
from bs4 import BeautifulSoup
import requests
user_agent_list = [
'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0',
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser)',
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)'
]
proxies_list = [
'218.75.102.198:8000',
'180.123.162.172:8899'
'118.120.209.134:41122',
'114.99.3.122:8089',
'180.121.135.129:8089',
'114.104.134.70:41122',
'103.118.46.174:8080',
'180.120.212.15:8089',
'58.246.58.150:9002',
'118.120.231.118:41122',
'112.53.184.170:9091'
]
headers = {'user-agent':random.choice(user_agent_list)}
proxies = {'http':random.choice(proxies_list)}
#对目标网站发起请求
url='https://book.douban.com/tag/%E5%B0%8F%E8%AF%B4'
res=requests.get(url,headers=headers)
res.encoding='utf-8'
#获得html数据
page=res.text
# 创建BeautifulSoup对象
soup=BeautifulSoup(page,'lxml')
for i in soup.find_all('div',attrs={"class":'info'}):
data = []
dict_info = {}
# 拿到每条小说的title属性
title1=i.find('h2').text
title2=title1.replace('\n','')
title=title2.replace(' ','')
# print(title)
# 拿到每条小说的作者属性
author1=i.find('div',attrs={"class":'pub'}).text
author2 = author1.replace('\n', '')
author=author2.replace(' ','')
# print(author)
# 拿到每条小说的内容简介
content1=i.find('p').text
content2 = content1.replace('\n', '')
content=content2.replace(' ','')
# print(content)
dict_info['title']=title
dict_info['author'] = author
dict_info['content'] = content
data.append(dict_info)
# print(data)
import csv
headers=['title','author','content']
with open('豆瓣.csv','w',encoding='utf-8',newline='')as f:
writer1=csv.DictWriter(f,headers)
writer1.writeheader() #写入表头
writer1.writerows(data)
法2:
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
import requests
def get_html(url):
# 定义请求头
headers = {'user-agent':UserAgent().edge}
# 发起请求
response = requests.get(url,headers=headers)
# 获取响应文本
page = response.text
return page
'''
解析出小说 的名称 评分 引言 详情页的url
'''
def get_data(page):
# 创建BeautifulSoup对象
soup = BeautifulSoup(page,'lxml')
# 定义存储数据的列表容器
info_list = []
for li in soup.find_all('li','subject-item'):
dict_info = {}
# 小说的名称
dict_info['title'] = li.find('h2').text.replace('\n', '').replace(' ', '')
# 详情页url
dict_info['link'] = li.find('a','nbg')['href']
# 小说的评分
dict_info['rating'] = li.find('span','rating_nums').string
# 小说的引言
dict_info['info'] = li.find('div','info').p.string.replace('\n','')
# print(dict_info)
info_list.append(dict_info)
return info_list
# 导入csv模块
import csv
def save_data(info_list):
# 定义表头
head = ('title','link','rating','info')
with open('douba.csv','w',encoding='utf-8',newline='')as f:
# 创建csv字典写入对象
writer = csv.DictWriter(f,head)
# 写入表头
writer.writeheader()
# 同时写入多行数据
writer.writerows(info_list)
if __name__ == '__main__':
url = 'https://book.douban.com/tag/%E5%B0%8F%E8%AF%B4'
# 获取响应
page = get_html(url)
# 解析响应
info_list = get_data(page)
# 存储解析数据
save_data(info_list)