1. json模块
1.1. 什么是json
JSON(JavaScript Object Notation, JS对象简谱)是一种轻量级的数据交换格式。它基于 ECMAScript(European Computer Manufacturers Association, 欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。(百度百科)
{
"name": "中国",
"province": [{
"name": "黑龙江",
"cities": {
"city": ["哈尔滨", "大庆"]
}
}, {
"name": "广东",
"cities": {
"city": ["广州", "深圳", "珠海"]
}
}, {
"name": "台湾",
"cities": {
"city": ["台北", "高雄"]
}
}, {
"name": "新疆",
"cities": {
"city": ["乌鲁木齐"]
}
}]
}
1.2. json模块的使用
想要使用json模块的话,需要在代码中导入json库import json
,该模块是python的内置库,因此不需要单独去pip
安装。
常用的json模块方法有四个:json.dumps()
、json.dump()
、json.loads()
、json.load()
。下面分别对这几个方法进行介绍。
1.2.1. json.dumps()
方法功能:将 Python 对象编码成 json 字符串。
大家可能不清楚讲python对象编码成 json 字符串有什么作用有什么意义。学过前后端的同学应该是知道,后端传给前端的数据就需要是一个json格式的数据;以及当你想把数据保存为 json 文件时,也是需要将数据转为 json格式。
这里给大家演示一下,当你想要将一个字典的数据保存到json文件中时,会发生什么情况。
dic = {
"name": "广东",
"cities": {
"city": ["广州", "深圳", "珠海"]
}
}
# print(type(dic)) # <class 'dict'>
with open('广东.json', 'w', encoding='utf8') as f:
f.write(dic)
这里报错是说文件对象的write()
方法只能操作字符串,不能写入字典类型,这里你就算换成列表也是会报错的,那你可能会想,既然他只能写入字符串,那咱把字典强转成字符串不就好了吗?来试试吧。
dic = {
"name": "广东",
"cities": {
"city": ["广州", "深圳", "珠海"]
}
}
# print(type(dic)) # <class 'dict'>
with open('广东.json', 'w', encoding='utf8') as f:
f.write(str(dic)) # 强转
这个时候不报错了,文件也写入了,但是你会发现,这里的数值都飘红色波浪线了,虽然只是作为数据存储不会有太大问题,但是这也并非json的格式。所以需要使用json模块的dumps()
方法,怎么使用呢,第一步导入json
模块,然后将强转字符串的方法str()
换成json.dumps()
即可。
import json
dic = {
"name": "广东",
"cities": {
"city": ["广州", "深圳", "珠海"]
}
}
# print(type(dic)) # <class 'dict'>
with open('广东.json', 'w', encoding='utf8') as f:
f.write(json.dumps(dic))
现在红色下划线没有了,但是又出现了一个新的问题,原本中文的地方,都被进行了Unicode编码,变成了我们看不懂的样子,这是什么原因呢?其实很简单,这个是库底层实现的,会默认将中文进行编码,那有没有取消编码的方法呢,答案肯定是有的,非常简单,只需要添加一个参数即可。
json.dumps(dic, ensure_ascii=False)
添加参数后再运行就解决了编码问题,但是还有一个问题,现在是数据量比较小的情况,当数据量很多的时候,如果所有数据都在一行的话,就会非常难以查看结构,所以这里再介绍一个参数indent
,一般将其设置为4就够了,也就是缩进4字符。
json.dumps(dic, ensure_ascii=False, indent=4)
现在数据的结构就要清晰很多了,层级分明,是不是就好看多了。这个就是json.dumps()
,这个方法学会之后,json.dump()
就没问题了。
1.2.2. json.dump()
json.dump()
和json.dumps()
功能是一模一样的,区别在于json.dump()
是操作文件对象的,怎么个操作法呢,给大家看一下代码就能理解了。
import json
dic = {
"name": "广东",
"cities": {
"city": ["广州", "深圳", "珠海"]
}
}
# print(type(dic)) # <class 'dict'>
with open('广东.json', 'w', encoding='utf8') as f:
# f.write(json.dumps(dic, ensure_ascii=False, indent=4)) # json.dumps()写法
json.dump(dic, f, ensure_ascii=False, indent=4) # json.dump()写法
json.dump()
第一个参数是要保存的数据对象(不用强转),第二个参数是文件对象,也就是上面with...as f
,这个f
就是文件对象,后面的参数上文说过,可以不加,但是前面两个参数是必要参数。
1.2.3. json.loads()
json.loads()
与json.dumps()
方法是相反的功能,json.loads()
用于解码 json 数据。该函数返回 Python 字段的数据类型。什么意思呢,就好比我们从json文件中把json数据读取出来,这个时候它是字符串类型的,但是想要使用一些该字段的Python数据类型的方法,就可以使用json.loads()
。
现在来使用json.loads()
方法。
import json
with open('广东.json', 'r', encoding='utf8') as f:
line = f.read()
json_obj = json.loads(line)
print(type(json_obj))
print(json_obj)
这个时候读取出来的内容经过json.loads()
转换后就变成了字典类型。如果读取的数据是其他类型,比如是列表,那么就会是列表类型。
[
{
"name": "广东",
"cities": {
"city": [
"广州",
"深圳",
"珠海"
]
}
}
]
1.2.4. json.load()
有过json.dump()
的经验后,猜都能猜到json.load()
和json.loads()
的区别了吧。没错,json.load()
是操作文件对象的,使用方法如下。
import json
with open('广东.json', 'r', encoding='utf8') as f:
json_obj = json.load(f)
print(type(json_obj))
print(json_obj)
该方法必要参数就只有一个,文件对象参数。
2. jsonpath模块
2.1. jsonpath的介绍与安装
jsonpath模块是一种用于解析json结构的数据解析模块,可以从json中抽取指定信息。由于是第三方库,所以需要pip install jsonpath
安装,如果安装速度过慢,可以选择使用国内镜像源
豆瓣源安装该模块:pip install jsonpath -i https://pypi.douban.com/simple/
2.2. jsonpath语法
以下内容来源于JSON Path 介绍 | Apifox 帮助文档,写得蛮不错的,所以借用了一下。
$
表示文档的根元素;根对象使用$来表示,而无需区分是对象还是数组.
匹配下级元素@
表示文档的当前元素.node_name
或['node_name']
匹配下级节点[index]
检索数组中的元素;下标运算符,根据索引获取元素,JsonPath索引从0开始[start:end:step]
支持数组切片语法*
作为通配符,匹配所有成员..
子递归通配符,匹配成员的所有子元素(<expr>)
使用表达式?(<boolean expr>)
进行数据筛选
大家可能直接看语法会比较蒙,这里给大家上个自定义案例就会明白很多了。
下面是相应的JsonPath的示例,代码来源于JSONPath - XPath for JSON,JSON文档如下:
{
"store": {
"book": [{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
}, {
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
}, {
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
}, {
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
JsonPath | Result |
| 所有book的author节点 |
| 所有author节点 |
| store下的所有节点,book数组和bicycle节点 |
| store下的所有price节点 |
| 匹配第3个book节点 |
| 匹配倒数第1个book节点 |
| 匹配前两个book节点 |
| 过滤含isbn字段的节点 |
| 过滤price<10的节点 |
| 递归匹配所有子节点 |
import jsonpath
dic = {
"store": {
"book": [{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
}, {
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
}, {
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
}, {
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}
# 第一个参数是要解析的对象,注意只能解析json格式的数据,字符串是不行的;第二个参数是解析规则
# 以列表的形式返回,如果有结果就返回结果,没有结果就返回Fasle
result = jsonpath.jsonpath(dic, '$.store.book[*].author')
print(result)
其他的大家自己去测试一下吧。
2.3. jsonpath案例练习
第一章的时候给大家演示的post请求案例不知道还有没有印象,就是一个返回响应数据是一些蔬菜的数据,先进入主页面,右键打开页面源代码,看看页面中的数据是否在页面源代码里面,进入页面源代码后按下ctrl + F
搜索一下关键词,这里我就搜索"小白菜"了。
发现是没有收到结果的,没有搜到结果只能说明该数据是动态加载的,而非静态数据,这个时候我们打开抓包工具刷新页面进行抓包,再进行搜索。
数据包找到了之后,看一下该数据包的头部信息。
请求URL确定了,请求方式为post,该数据包需要参数,那就看看参数是些什么。
这个数据包的参数值全是空,有点奇怪,那就看看其他页面的,通过X-Requested-With: XMLHttpRequest
可以确定这个数据包是xhr类型的,那么就可以进行过滤抓取第二页的数据包。
第二页数据包的参数如下:
第三页的参数如下:
limit
参数控制每页数据的数量(可以通过查看每页数据量猜测得知),current
参数控制页数。参数确定后就可以开始写爬虫代码了。
import requests
url = 'http://www.xinfadi.com.cn/getPriceData.html'
data = {
'limit': '20',
'current': '3'
}
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'
}
response = requests.post(url, headers=headers).content.decode()
print(response)
print(type(response))
响应数据是字符串,因为进制解码后就是字符串,这个时候可以用json.loads()
将其转为json格式数据。当然也有另一种方法,当你确定响应数据是json格式的时候,可以直接把.content.decode()
改为.json()
。
这样也就不用再使用json模块方法转换了,是不是就方便很多了。现在就可以直接用jsonpath模块去解析数据了。这里我们就拿每个菜品的这些数据吧:
通过观察数据结构可以发现,所有的数据都是在list
里面的,列表里面的每一个字典就是每一个菜品数据,那代码就可以这么写:
import jsonpath
import requests
url = 'http://www.xinfadi.com.cn/getPriceData.html'
data = {
'limit': '20',
'current': '3'
}
headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'
}
response = requests.get(url, headers=headers).json()
# print(response)
prodName = jsonpath.jsonpath(response, '$..list..prodName')
lowPrice = jsonpath.jsonpath(response, '$..list..lowPrice')
highPrice = jsonpath.jsonpath(response, '$..list..highPrice')
avgPrice = jsonpath.jsonpath(response, '$..list..avgPrice')
print(prodName, lowPrice, highPrice, avgPrice)
非常简单,其实jsonpath只需要会..
跨节点获取数据就好了,其他的操作的话不用jsonpath也是能够实现的(毕竟返回值是列表,可以使用列表的方法实现想要的功能)。那现在如何把这些数据对应保存到json文件当中呢?
... # 接上
json_list = list(zip(prodName, lowPrice, highPrice, avgPrice))
for i in json_list:
print(i)
现在只需要每次循环都生成一个字典,再在最外面全局定义一个字典嵌套列表,每次循环将生成的字典添加到全局字典列表里面去即可。
import jsonpath
import requests
url = 'http://www.xinfadi.com.cn/getPriceData.html'
data = {
'limit': '20',
'current': '3'
}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'
}
response = requests.get(url, headers=headers).json()
# print(response)
prodName = jsonpath.jsonpath(response, '$..list..prodName')
lowPrice = jsonpath.jsonpath(response, '$..list..lowPrice')
highPrice = jsonpath.jsonpath(response, '$..list..highPrice')
avgPrice = jsonpath.jsonpath(response, '$..list..avgPrice')
json_dic = {
"list": []
}
json_list = list(zip(prodName, lowPrice, highPrice, avgPrice))
for i in json_list:
dic = {
'prodName': i[0],
'lowPrice': i[1],
'highPrice': i[2],
'avgPrice': i[3]
}
json_dic['list'].append(dic)
print(json_dic)
数据构造好之后就需要将数据存储为json文件了,那么现在使用json.dumps()
和json.dump()
都是可以的,建议大家都试试,我这里就用json.dump()
了。
import json
... # 省略
with open('菜品.json', 'w', encoding='utf8') as f:
json.dump(json_dic, f, ensure_ascii=False, indent=4)
数据存储成功,案例结束,恭喜各位又学会了一个新的知识点。