第31节课 JSON数据解析
1.JSON基础概念
JSON 是一种轻量级的数据交换格式(另一个叫XML),具有简洁、易读的特点,并且在不同编程语言之间能很好地实现数据传递。在 Python 中,json模块能够实现 Python 数据类型与 JSON 数据格式之间的相互转换。
JSON的语法规则
- 数据以键值对形式表示:
"name": "张三"
- 数据由逗号分隔
- 花括号 {} 保存对象
- 方括号 [] 保存数组
- 支持的数据类型:
- 数字(整数或浮点数)
- 字符串(双引号中)
- 布尔值(true 或 false)
- 数组(方括号中)
- 对象(花括号中)
- null(None)
JSON 类型 | Python 类型 |
---|---|
对象 | dict |
数组 | list/tuple |
字符串 | str |
数字 | int/float |
true | True |
false | False |
null | None |
JSON数据示例
{
"name": "张三",
"age": 30,
"isStudent": false,
"courses": ["Python", "数据分析", "机器学习"],
"address": {
"city": "北京",
"postcode": "100000"
},
"phoneNumbers": null
}
json模块中的主要函数
函数 | 描述 |
---|---|
json.dumps() | 将Python对象编码成JSON字符串 |
json.dump() | 将Python对象编码成JSON字符串并写入文件 |
json.loads() | 将JSON字符串解码为Python对象 |
json.load() | 从文件中读取JSON字符串并解码为Python对象 |
2.JSON数据编码(序列化)
在实际开发中,为了让生成的 JSON 字符串更易读,可以使用indent参数进行缩进格式化,使用sort_keys参数按键名进行排序。
import json
# 字典对象
data = {
"name": "张三",
"age": 30,
"isStudent": False,
"courses": ["Python", "数据分析", "机器学习"],
"address": {
"city": "北京",
"postcode": "100000"
},
"phoneNumbers": None
}
# 将Python对象编码为JSON字符串
json_str = json.dumps(data, ensure_ascii=False)
print(json_str)
# 格式化输出
json_str = json.dumps(data, indent=4, ensure_ascii=False)
print(json_str)
json_str = json.dumps(data, indent=4, sort_keys=True, ensure_ascii=False)
print(json_str)
# 写入到文件中
with open("data.json", "w", encoding="utf-8") as file:
json.dump(data, file, ensure_ascii=False, indent=4, sort_keys=True)
3.JSON数据解码(反序列化)
import json
json_str = '{"name": "张三", "age": 30, "isStudent": false, "courses": ["Python", "数据分析", "机器学习"], "address": {"city": "北京", "postcode": "100000"}, "phoneNumbers": null}'
data = json.loads(json_str)
print(type(data))
print(data)
print(data['name'])
print(data['courses'][1])
with open("data.json", 'r', encoding='utf-8') as file:
data = json.load(file)
print(data)
print(type(data))
4.自定义编码器与解码器
标准的JSON只支持以下数据类型:
- 字符串(string)
- 数字(number)
- 对象(object)- 在Python中表示为字典
- 数组(array)- 在Python中表示为列表
- 布尔值(boolean)- 在Python中表示为True/False
- 空值(null)- 在Python中表示为None
然而,Python中有许多其他数据类型(如日期时间、自定义类、集合等)无法直接被JSON序列化。这就是为什么我们需要自定义编码器和解码器。
import json
import datetime
data = {
"name": "张三",
"create_at": datetime.datetime.now(),
"tags": {"Pythion", "JSON", "编程"}
}
json_str = json.dumps(data, indent=4)
print(json_str)
# TypeError: Object of type set is not JSON serializable
自定义编码器
就是将Python中不能够直接编码为JSON格式的数据进行转换 转换成能够被JSON识别的数据
(1)使用default参数
最简单的自定义编码方式是使用 json.dumps() 的 default 参数:
import json
import datetime
data = {
"name": "张三",
"create_at": datetime.datetime.now(),
"tags": {"Pythion", "JSON", "编程"}
}
# 编码函数:将非JSON类型的数据转换为JSON类型的数据
def custom_encoder(obj):
if isinstance(obj, datetime.datetime):
return obj.isoformat()
elif isinstance(obj, set):
return list(obj)
json_str = json.dumps(data, indent=4, default=custom_encoder, ensure_ascii=False)
print(json_str)
(2)创建JSONEncoder子类
更灵活的方法是继承 json.JSONEncoder 类并重写 default 方法:
import json
import datetime
data = {
"name": "张三",
"create_at": datetime.datetime.now(),
"tags": {"Pythion", "JSON", "编程"}
}
# 了解
class CustomJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return {
"__datetime__":True,
"value":obj.isoformat()
}
elif isinstance(obj, set):
return {
"__set__": True, # 将数据原本的类型进行录入 为了给解码做提示
"value":list(obj)
}
return super().default(obj)
json_str = json.dumps(data, indent=4, cls=CustomJSONEncoder, ensure_ascii=False)
print(json_str)
自定义解码器
就是将某些JSON数据解码为原先的样子(Python数据类型)
(1)使用object_hook参数
json.loads() 函数的 object_hook 参数允许我们自定义JSON对象的解码方式:
import json
import datetime
json_str = """
{
"name": "张三",
"create_at": {
"__datetime__": true,
"value": "2025-06-09T19:59:05.343916"
},
"tags": {
"__set__": true,
"value": [
"编程",
"JSON",
"Pythion"
]
}
}
"""
# 自定义解码函数
def custom_decoder(obj):
if "__datetime__" in obj:
return datetime.datetime.fromisoformat(obj['value'])
elif "__set__" in obj:
return set(obj['value'])
return obj
data = json.loads(json_str, object_hook=custom_decoder)
print(data)
(2)创建JSONDecoder子类
更复杂的解码可以通过继承 json.JSONDecoder 类实现:
import json
import datetime
json_str = """
{
"name": "张三",
"create_at": {
"__datetime__": true,
"value": "2025-06-09T19:59:05.343916"
},
"tags": {
"__set__": true,
"value": [
"编程",
"JSON",
"Pythion"
]
}
}
"""
# 自定义解码类
class CustomJSONDecoder(json.JSONDecoder):
# 构造函数
def __init__(self, *args, **kwargs):
json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
def object_hook(self,obj):
if "__datetime__" in obj:
return datetime.datetime.fromisoformat(obj['value'])
elif "__set__" in obj:
return set(obj['value'])
return obj
data = json.loads(json_str, cls=CustomJSONDecoder)
print(data)
5.处理复杂JSON数据
嵌套结构
{
"company": "ABC科技",
"employees": [
{
"id": 1001,
"name": "张三",
"department": "研发",
"skills": ["Python", "Django", "Docker"]
},
{
"id": 1002,
"name": "李四",
"department": "数据",
"skills": ["Python", "数据分析", "机器学习"]
}
],
"address": {
"city": "北京",
"street": "中关村",
"postcode": "100080"
}
}
import json
json_str = '''
{
"company": "ABC科技",
"employees": [
{
"id": 1001,
"name": "张三",
"department": "研发",
"skills": ["Python", "Django", "Docker"]
},
{
"id": 1002,
"name": "李四",
"department": "数据",
"skills": ["Python", "数据分析", "机器学习"]
}
],
"address": {
"city": "北京",
"street": "中关村",
"postcode": "100080"
}
}
'''
data = json.loads(json_str)
print(data['company'])
print(data['employees'][0]['name'])
print(data['employees'][1]['skills'][1])
for employee in data['employees']:
print(employee['name'])
print(employee['department'])
print(employee['skills'])
动态解析路径
import json
json_str = '''
{
"company": "ABC科技",
"employees": [
{
"id": 1001,
"name": "张三",
"department": "研发",
"skills": ["Python", "Django", "Docker"]
},
{
"id": 1002,
"name": "李四",
"department": "数据",
"skills": ["Python", "数据分析", "机器学习"]
}
],
"address": {
"city": "北京",
"street": "中关村",
"postcode": "100080"
}
}
'''
data = json.loads(json_str)
path1 = "employees.0.name"
path2 = "address.city"
def get_value_by_path(data, path):
keys = path.split(".")
result = data
for key in keys:
if key.isdigit():
result = result[int(key)]
else:
result = result[key]
return result
print(get_value_by_path(data, path1))
print(get_value_by_path(data, path2))
print(get_value_by_path(data, "employees.1.skills.2"))
6.拓展:jsonpath库
JSONPath提供了一种简洁的方式来导航JSON结构并提取特定数据,而不需要复杂的循环和条件语句。
pip install jsonpath-ng
基本语法元素
语法元素 | 描述 |
---|---|
$ |
根对象/元素 |
@ |
当前对象/元素 |
. |
子元素操作符 |
.. |
递归下降操作符 |
* |
通配符,匹配所有对象/元素 |
[] |
下标操作符 |
[,] |
并集操作符 |
[start:end:step] |
数组切片操作符 |
?() |
过滤表达式 |
() |
脚本表达式 |
示例JSON数据
{
"store": {
"book": [
{
"category": "参考书",
"author": "李明",
"title": "Python编程入门",
"price": 49.99
},
{
"category": "小说",
"author": "王芳",
"title": "梦想之旅",
"price": 29.99,
"isbn": "0-553-21311-3"
}
],
"bicycle": {
"color": "红色",
"price": 599.99
}
}
}
示例路径演示
JSONPath | 描述 |
---|---|
$.store.book[*].author |
所有书籍的作者 |
$..author |
所有作者 |
$.store.* |
store下的所有元素 |
$.store..price |
store下所有价格 |
$..book[2] |
第三本书 |
$..book[-1] |
最后一本书 |
$..book[0,1] |
前两本书 |
$..book[:2] |
前两本书 |
$..book[?(@.isbn)] |
所有有isbn属性的书 |
$..book[?(@.price<10)] |
所有价格小于10的书 |
$..book[?(@.price==8.95)] |
所有价格等于8.95的书 |
$..book[?(@.title =~ /.*REGEX.*/i)] |
标题匹配正则表达式的书 |
基本操作
import json
from jsonpath_ng import parse
json_str = """
{
"store": {
"book": [
{
"category": "参考书",
"author": "李明",
"title": "Python编程入门",
"price": 49.99
},
{
"category": "小说",
"author": "王芳",
"title": "梦想之旅",
"price": 29.99,
"isbn": "0-553-21311-3"
}
],
"bicycle": {
"color": "红色",
"price": 599.99
}
}
}
"""
data = json.loads(json_str)
# 导航对象
jsonpath_expr = parse('$.store.book[*].author')
print(jsonpath_expr.find(data))# 列表
print(jsonpath_expr.find(data)[0]) # DatumInContext对象
print(type(jsonpath_expr.find(data)[0]))
for match in jsonpath_expr.find(data):
print(match.value)
print(match.path)
print(match.context)
7.拓展:Schema验证
JSONSchema是一种用于验证JSON数据结构的规范,它允许我们定义JSON数据的格式、类型和约束条件,确保数据符合预期的结构和规则。
pip install jsonschema
JSONSchema是一种基于JSON格式的模式语言,用于:
- 描述现有数据格式
- 提供清晰的人机可读文档
- 验证数据,确保数据质量
- 自动测试和验证客户端提交的数据
核心关键字
关键字 | 描述 |
---|---|
$schema |
指定Schema的版本 |
title |
Schema的标题 |
description |
Schema的描述 |
type |
定义值的数据类型 |
properties |
定义对象的属性 |
required |
指定必需的属性 |
minimum /maximum |
数值的最小/最大值 |
minLength /maxLength |
字符串的最小/最大长度 |
pattern |
字符串必须匹配的正则表达式 |
enum |
枚举,值必须是指定的值之一 |
from jsonschema import validate
# 定义一个JSON验证Schema
# 用户信息验证
user_schema = {
"type": "object", # 要求JSON整体是个JSON对象
"properties": { # 指定属性要求
"name": {"type": "string"}, # 必须有一个name属性 string类型
"age": {"type": "integer", "minimum": 0}, # 必须有一个age属性 int类型 最小0
"email":{"type": "string", "format":"email"},
"interests": {"type":"array", "items":{"type":"string"}}
},
"required":["name","age","email"]
}
user1 = {
"name":"张三",
"age":30,
"email":"zhangsan@qq.com",
"interests":["编程", "吃饭"]
}
user2 = {
"name":"张三",
"age":30,
"interests":["编程", "吃饭"]
}
user3 = {
"name":"张三",
"age":30,
"email":"zhangsan@qq.com",
}
user4 = {
"name":"张三",
"age":"三十",
"email":"zhangsan@qq.com",
}
# 最好try-except
validate(instance=user1, schema=user_schema)
# validate(instance=user2, schema=user_schema) # 缺email
validate(instance=user3, schema=user_schema)
validate(instance=user4, schema=user_schema)
8.案例:头条新闻
import requests
def print_news(title, uniquekey):
# 1512-新闻详情查询 - 代码参考(根据实际业务情况修改)
# 基本参数配置
apiUrl = 'http://v.juhe.cn/toutiao/content' # 接口请求URL
apiKey = '3719af094e850cf3f4c4aea0bdb361d6' # 在个人中心->我的数据,接口名称上方查看
# 接口请求入参配置
requestParams = {
'key': apiKey,
'uniquekey': uniquekey,
}
# 发起接口网络请求
response = requests.get(apiUrl, params=requestParams)
# 解析响应结果
if response.status_code == 200:
data = response.json()
# 网络请求成功。可依据业务逻辑和接口文档说明自行处理。
print("=" * 20)
print(title)
print(data['result']['content'])
else:
# 网络异常等因素,解析结果异常。可依据业务逻辑自行处理。
print('请求异常')
def search_news():
# 923-新闻列表查询 - 代码参考(根据实际业务情况修改)
# 基本参数配置
apiUrl = 'http://v.juhe.cn/toutiao/index' # 接口请求URL
apiKey = '3719af094e850cf3f4c4aea0bdb361d6' # 在个人中心->我的数据,接口名称上方查看
# 接口请求入参配置
requestParams = {
'key': apiKey,
'type': 'yule',
'page': 1,
'page_size': 10,
'is_filter': 0,
}
# 发起接口网络请求
response = requests.get(apiUrl, params=requestParams)
# 解析响应结果
if response.status_code == 200: # 404
data = response.json()
# 网络请求成功。可依据业务逻辑和接口文档说明自行处理。
# print(data)
# print(type(data))
for item in data['result']['data']:
print_news(item['title'], item['uniquekey'])
else:
# 网络异常等因素,解析结果异常。可依据业务逻辑自行处理。
print('请求异常')
search_news()