数据解析
一、数据解析概述
1. 回顾聚焦爬虫:
(1)编码流程:
- 指定url
- 发起请求
- 获取响应数据
- 数据解析
- 将解析到的局部数据持久化存储
2. 数据解析分类:
- 正则
- bs4(只能用在Python语言中)
- xpath(重点)
3. 数据解析原理概述
- 解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储
- 进行指定标签的定位
- 对标签或者标签对应的属性中存储的数据值进行提取(解析)
二、图片数据爬取
1. 前引代码(如何获取图片数据以及存储)
- 我们存储的是图片,图片对应的是一组二进制数据
- requests.get(url=url).content
import requests
# 如何爬取图片数据
url = 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/542/542.jpg'
# content 返回的是二进制形式的图片数据
# text (文本字符串)
# content (二进制形式)
# json() (字典 对象)
img_data_test = requests.get(url=url).content
# wb 二进制写入
with open('./test.jpg', 'wb') as fp:
fp.write(img_data_test)
print("over!!!")
2. 正则解析
(1)常用正则表达式
- 可在菜鸟上多看看
(2)需求:
-
爬取热图网“爆笑”版块的图片
-
网址
(3)分析:
- 首先用通用爬虫获取一整张页面(在通用爬虫的基础上,再运用聚焦爬虫),再使用聚焦爬虫将整张页面中局部数据(图片)进行解析。
(4)代码:(UA伪装修改一下)
import requests
import re # 正则表达式
import os
# 创建一个文件夹,保存所有的图片
if not os.path.exists('../picLibs'):
os.mkdir('../picLibs')
url = 'https://www.ratoo.net/a/baoxiao/'
headers = {
'User-Agent':'Mozil。。。。'
}
# 使用通用爬虫对url对应的一整张页面进行爬取
page_content = requests.get(url=url, headers=headers).text
# print(page_content) # 打印后发现爬取下来的界面源码 <img src='' 而不是" ",所以一开始找不到东西 ex = '<div class="pic1">.*?<img src="(.*?)" border.*?</div>'
# 元素分析
# <div class="pic1">
# <a class="listimg" href="/a/baoxiao/45457.html" title="猫咪和小鸭一起舞起来" target="_blank">
# <img src="//img.ratoo.net/uploads/tupian/20210907/fdykn12n51v662.gif" border="0">猫咪和小鸭一起舞起
# </a>
# </div>
# str = '<div class="pic1">' \
# '<a class="listimg" href="/a/baoxiao/45457.html" title="猫咪和小鸭一起舞起来" target="_blank">' \
# '<img src="//img.ratoo.net/uploads/tupian/20210907/fdykn12n51v662.gif" border="0">猫咪和小鸭一起舞起' \
# '</a>' \
# '</div>'
# 正则表达式
# 从<div class="pic1">开始 到</div>结束
ex = '<div class="pic1">.*?<img src=\'(.*?)\' border.*?</div>'
img_list = re.findall(ex, page_content, re.S)
# print(img_list)
# 仍需要拼接协议头成完整的图片地址
for src in img_list:
# 拼接出一个完整的图片url
src = 'https:' + src
# 请求到了图片的二进制数据
data = requests.get(url=src, headers=headers).content
# 生成图片名称
img_name = src.split('/')[-1] # 以 / 划分,取最后一个串 -1
# 图片存储的路径
img_path = './picLibs/' + img_name
with open(img_path, 'wb') as fp:
fp.write(data)
print(img_name,'下载成功!!!')
print("采集结束")
(5)拓展需求:
- 在这个“爆笑”版块下,还有好多个页码,每一个页码对应的是每一个页面数据,每个页面都有对应的图片数据,以上刚才所写的,只是针对第一页所表示的url中的图片数据进行爬取,但我们还想接着爬取2,3,4…页面的图片数据,怎么延伸?
- 仔细观察每页的url的不同:(只需要修改9_x的页码)
- 代码如下:
import requests
import re # 正则表达式
import os
# 创建一个文件夹,保存所有的图片
if not os.path.exists('./picLibs'):
os.mkdir('./picLibs')
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4750.0 Safari/537.36'
}
# 设置一个通用的url模板
url = 'https://www.ratoo.net/a/baoxiao/list_9_%d.html'
# pageNum = 2
for pageNum in range(1, 6):
new_url = format(url%pageNum)
# 使用通用爬虫将页面中所有的图片数据解析/提取出来
page_content = requests.get(url=new_url, headers=headers).text
ex = '<div class="pic1">.*?<img src=\'(.*?)\' border.*?</div>'
img_list = re.findall(ex, page_content, re.S)
# print(img_list)
# 仍需要拼接协议头成完整的图片地址
for src in img_list:
# 拼接出一个完整的图片url
src = 'https:' + src
# 请求到了图片的二进制数据
data = requests.get(url=src, headers=headers).content
# 生成图片名称
img_name = src.split('/')[-1] # 以 / 划分,取最后一个串 -1
# 图片存储的路径
img_path = './picLibs/' + img_name
with open(img_path, 'wb') as fp:
fp.write(data)
print(img_name,'下载成功!!!')
print("采集结束")
3. bs4进行数据解析
(1)回顾数据解析的原理:
- 标签定位
- 提取标签、标签属性中存储的数据值
(2)bs4数据解析的原理:
- 1.实例化一个BeautifulSoup对象,并且将页面源码数据加载到该对象中
- 2.通过调用BeautifulSoup对象中相关的属性或者方法进行标签定位和数据提取
(3)环境安装:
pip install bs4 # 安装模块(对应好Python环境再安装 | 可以直接在pycharm 安装)
pip install lxml # 安装解析器(在xpath中也会用到)
- 如有安装缓慢问题:
需要将pip源设置为国内源,阿里源等等(修改镜像)
(4)如何实例化BeautifulSoup对象:
-
from bs4 import BeautifulSoup (在bs4模块中,选择导入BeautifulSoup类(对象))【第一步:导包】
-
对象的实例化:
- 将本地的HTML文档中的数据加载到该对象中
fp = open('./test.html','r',encoding='utf-8') soup = BeautifulSoup('fp','lxml')
- 将互联网上获取的页面源码加载到该对象中
page_text = response.text soup = BeautifulSoup(page_text,'lxml')
-
提供的用于数据解析的方法和属性:
-
soup.tagName (后面加标签名):返回的是html(文档)中第一次出现的tagName对应的标签
-
soup.find():
- find(‘tagName’)等同于soup.tagName
- 属性定位:
- soup.find(‘div’,class_/id/attr=‘song(名称)’):class _ 不加下划线就变成关键字了
-
soup.find_all(‘tagName’):返回符合要求的所有标签(列表)
-
select:
- soup.select(选择器:类选择器,id选择器。。。):返回的是一个列表(多个)
- 层级选择器:
- soup.select(’.tang > ul > li > a’):>表示的是一个层级(直系准确定位)
- soup.select(’.tang > ul a’):空格表示的是多个层级
-
获取标签之间的文本数据:
-
soup.a.text/string/get_text()
-
text/get_text():可以获取某一个标签中所有的文本内容(即是这些文本内容不属于此标签的直系文本内容,也可以获取到)
-
string:只可以获取该标签下面直系的文本内容
-
-
获取标签中属性值:例如:[‘href’]
-
soup.a['href'] # 取到标签直接取属性值即可
-
-
(5)bs4实战项目
-
需求:爬取三国演义小说所有的章节标题和章节内容
-
诗词名句网
-
-
设计思路:
- 首先用通用爬虫爬取首页的页面数据(包含了标题,可以跳转)
- 解析获取到标题内容后,(因为具体章节内容在详情页中)还要再获取标题对应的链接地址。(a标签的href)
-
过程中注意一点,就是中文乱码的问题:(这是一种解决方法)
# 对首页的页面数据进行爬取
page_text = requests.get(url=url, headers=headers)
page_text.encoding = 'utf-8'
page_text = page_text.text
- 代码:(UA伪装需要修改)
import requests
from bs4 import BeautifulSoup
headers = {
'User-Agent':'Mozill。。。'
}
url = 'https://www.shicimingju.com/book/sanguoyanyi.html'
# 对首页的页面数据进行爬取
page_text = requests.get(url=url, headers=headers)
page_text.encoding = 'utf-8'
page_text = page_text.text
# print(page_text)
# 在首页中解析出章节的标题和详情页的url
# 1. 实例化BeautifulSoup对象, 需要将页面源码数据加载到该对象中
soup = BeautifulSoup(page_text, 'lxml')
# 解析章节标题和详情页的url
li_list = soup.select('.book-mulu > ul > li')
# print(li_list)
fp = open('./sanguo.txt','w', encoding='utf-8')
for li in li_list:
title = li.a.string
# print(title)
detail_url = 'https://www.shicimingju.com' + li.a['href']
# 对详情页发起请求,解析出章节内容(打开开发者工具查看元素结构)
detail_text = requests.get(url=detail_url, headers=headers)
detail_text.encoding = 'utf-8'
detail_text = detail_text.text
# 解析出详情页中相关的章节内容
soup_detail = BeautifulSoup(detail_text, 'lxml')
div_tagClass = soup_detail.find('div',class_='chapter_content')
# 解析到了章节的内容
content = div_tagClass.get_text()
# print(content)
# 持久化存储
fp.write(title + ':' + content + '\n')
print(title,'爬取章节结束')
print('全部结束')
4. xpath解析:最常用且最便捷高效的一种解析方式。通用性高。
(1)xpath解析原理:
1. 实例化一个etree的对象,且需要将被解析的页面源码数据加载到该对象中。
2. 调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获。
(2)环境的安装:
pip install lxml (安装解析器 |看好Python环境再安装 | 可以直接用Pycharm安装)
(3)如何实例化一个etree对象:from lxml import etree
1. 将本地的html文档中的源码数据加载到etree对象中
- etree.parse(filePath):返回一个etree对象
2. 可以将从互联网上获取的源码数据加载到该对象中
- etree.HTML('page_text)
# 接着就是调用xpath()方法 只有这一个,重点是xpath表达式 | 与BeautifulSoup(有好多方法 find select。。。)不同
- xpath('xpath表达式')
(4)xpath表达式
- 实例化好了一个etree对象,且将被解析的源码加载到了该对象中 | parse(‘待解析的源码,可以是本地的,也可以是直接网上的数据’) | HTML()
from lxml import etree
tree = etree.parse('test.html') # 本地的一个test.html | tree 返回的一个etree对象
- 接着就是xpath表达式了 | tree.xpath(‘xpath表达式’)
- / :表示的是从根节点开始定位。(最左侧的/)| 表示的是一个层级
- //:表示的是多个层级 | (最左侧)可以表示从任意位置开始定位,找到所有的
- 属性定位://div[@class=“song”] | tag[@attrName=“attrValue”]
- 索引定位://div[@class=“song”]/p[3] 索引是从1开始的。
- 取文本:(取的是标签中间存储的文本内容)| 换行 \t 空格 也属于文本内容
- /text():但是必须先定位到直系的标签,即是直系文本。| 获取的是标签中直系的文本内容
- //text():获取的是标签中非直系的文本内容 (所有的文本内容)
- 取属性:(取得定位的标签的对应属性的属性值)
- /@attrName ===> img/@src
# [<Element a at 0x1044f5f48>,...]
# 1.
r = tree.xpath('/html/head/title') # 层级 从根节点/开始逐步定位到title (但是取到的是元素列表,而不是值)
# 2.
r = tree.xpath('/html/body/div') # 返回Element 列表
# 3.
r = tree.xpath('/html//div')
# 4.
r = tree.xpath('//div') # (最左侧)可以表示从任意位置开始定位,找到所有的div
# 5.属性定位
r = tree.xpath('//div[@class="song"]') # @后跟属性定位的属性名称 id 类等等 | 属性值 song | 定位到class为song的div | 返回element列表
# 6.索引定位 | 索引是从1开始的
r = tree.xpath('//div[@class="song"]/p[3]') # /p 直系层级 p | 返回element元素列表 | 找到class为song的div下的第三个p标签
# 7. 取文本 例:<li><a href = 'https' class="du">杜牧</a></li> 取“杜牧”文本
r = tree.xpath('//div[@class="tang"]//li[5]/a/text()')[0] # 返回第5个li下的a标签间存储的文本列表,用[0]取到列表第一个字符串。
# 8.
r = tree.xpath('//div[@class="song"]/img/@src') # 取属性值 | 列表
(5)xpath实战一:爬取58二手房的房源信息
-
58
-
代码:修改UA伪装
import requests
from lxml import etree
headers = {
'User-Agent':'Mozi。。。.36'
}
# 爬取到页面源码数据
url = 'https://bj.58.com/ershoufang/?PGTID=0d100000-0000-119a-7933-11a4db6d83bf&ClickID=2'
page_text = requests.get(url=url, headers=headers).text
# 数据解析
tree = etree.HTML(page_text)
div_lists = tree.xpath('//section[@class="list"]/div[@class="property"]')
# print(div_lists[0].xpath('./a//div[@class="property-content"]//div[@class="property-content-title"]/h3/text()'))
fp = open('58.txt', 'w',encoding='utf-8')
for div in div_lists:
# 局部解析
title = div.xpath('./a//div[@class="property-content"]//div[@class="property-content-title"]/h3/text()')[0] # ./ 就代表 div 当前,我们已经定位到的“多个”div | 因为解析的参照物已经不是整个源码了,而是当前获取到的div
# 没有 “.” ,/就代表整个源码的根标签
# print(title)
fp.write(title+'\n')
print('over!!!')
(6)xpath实战二:解析下载图片数据
-
分类版块下的背景模块
-
代码:修改UA伪装
import requests
from lxml import etree
import os
headers = {
'User-Agent':'.......'
}
url = 'https://pic.netbian.com/4kbeijing/'
response = requests.get(url=url, headers=headers)
# 手动设定响应数据的编码格式
# response.encoding = 'utf-8' # 手动将响应数据编码成utf-8,没有用
# page_tex = response.text # 设置完编码格式后,.text会报错 ?
page_text = response.text
# 数据解析 :src属性值 alt属性
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@class="slist"]//li')
for li in li_list:
img_src = 'https://pic.netbian.com' + li.xpath('./a/img/@src')[0]
# print(img_src)
img_name = li.xpath('./a/img/@alt')[0] + '.jpg'
# 通用处理中文乱码的解决方案 先编码再解码,再重新赋值
img_name = img_name.encode('iso-8859-1').decode('gbk')
# print(img_name, img_src) # 发现乱码, 原始页面的编码与pycharm编码冲突 用utf-8试一试
# 创建文件夹
if not os.path.exists('./pic'):
os.mkdir('./pic')
# 请求图片进行持久化存储 图片是二进制数据
img_data = requests.get(url = img_src, headers=headers).content
img_path = 'pic/' + img_name
with open(img_path, 'wb') as fp:
fp.write(img_data)
print(img_name, '下载成功')
print('全部采集结束')
(7)xpath实战三:解析出所有城市名称
-
空气质量在线监测分析平台
-
代码:修改UA伪装
import requests
from lxml import etree
headers = {
'User-Agent':'。。。'
}
url = 'https://www.aqistudy.cn/historydata/'
page_text = requests.get(url=url, headers=headers).text
tree = etree.HTML(page_text)
# 解析到热门城市和所有城市对应的a标签
# //div[@class="bottom"]/ul/li/a 热门城市a标签的层级关系
# //div[@class="bottom"]/ul/div[2]/li/a 全部城市a标签的层级关系
# 层级关系不一样 怎么处理? 可以使用逻辑运算符
a_city_name = tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a')
all_city_name = []
for a in a_city_name:
name = a.xpath('./text()')[0]
all_city_name.append(name)
print(all_city_name, len(all_city_name))
''' 分了两步写,修改用统一的公式
hot_li_list = tree.xpath('//div[@class="bottom"]/ul/li')
all_city_names = [] # 存储热门城市的名字
for li in hot_li_list:
hot_city_name = li.xpath('./a/text()')[0]
all_city_names.append(hot_city_name) # 存储进列表中
# 解析的是全部城市的名称
city_names_list = tree.xpath('//div[@class="bottom"]/ul/div[2]/li')
for li in city_names_list:
city_name = li.xpath('./a/text()')[0]
all_city_names.append(city_name)
print(all_city_names, len(all_city_names))
'''
(8)xpath实战综合:爬取站长素材中免费简历模板
-
点击下载地址,最后下载的是.rar的数据包(增加分页操作)
-
模板
-
代码:
import requests
import os
from lxml import etree
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4750.0 Safari/537.36'
}
url = 'https://sc.chinaz.com/jianli/free_%d.html'
if not os.path.exists('./muban'):
os.mkdir('./muban')
url_list = []
# 首先先拿到各个详情页的url集合 分页(先弄个几页)获取
for pageNum in range (1, 3):
if (pageNum == 1):
new_url = 'https://sc.chinaz.com/jianli/free.html'
else:
new_url = format(url % pageNum)
# print(new_url)
page_text = requests.get(url = new_url, headers = headers).text
# print(page_text)
tree = etree.HTML(page_text)
# 拿到详情页链接并重新拼接地址
url_list_xpath = tree.xpath('//div[@id="main"]//a/@href')
# print(url_list_xpath)
for i in url_list_xpath:
detail_url = 'https:' + i
# print(detail_url)
url_list.append(detail_url)
# 到详情页获取链接地址
for i in url_list:
p_t = requests.get(url=i, headers=headers).text
tree1 = etree.HTML(p_t)
url_rar = tree1.xpath('//div[@class="clearfix mt20 downlist"]/ul/li/a/@href')[0]
# print(url_rar)
model_name = tree1.xpath('//div[@class="ppt_tit clearfix"]/h1/text()')[0]
model_name = './muban/' + model_name.encode('iso-8859-1').decode('utf-8').replace(" ", "") + str(url_rar).split('/')[-1] # 以 / 划分,取最后一个串 -1 再加上去空格
# print(model_name)
rar_data = requests.get(url=url_rar, headers=headers).content
with open(model_name, 'wb') as fp: # wb 二进制 无需再加encoding
fp.write(rar_data)
print(model_name,'下载结束')
print('所有都结束了')