JSON 数据处理最佳实践

从解析到实战,掌握 JSON 数据的处理技巧

一、JSON 是什么

做爬虫这几年,JSON 是我打交道最多的数据格式。现在的网站,尤其是前后端分离的项目,数据接口几乎清一色返回 JSON。学会高效处理 JSON,是爬虫开发的基本功。

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它基于 JavaScript 的对象字面量语法,但独立于语言,几乎所有编程语言都支持。

JSON 的特点
  • 纯文本格式,人类可读
  • 结构清晰,键值对形式
  • 支持嵌套,能表示复杂数据结构
  • 解析速度快,占用空间小

二、解析 JSON 数据

2.1 基础解析

import json # JSON 字符串 json_str = '{"name": "张三", "age": 25, "city": "北京"}' # 解析为 Python 字典 data = json.loads(json_str) print(data['name']) # 张三 print(data['age']) # 25

2.2 从文件读取

# 从文件读取 JSON with open('data.json', 'r', encoding='utf-8') as f: data = json.load(f) print(data)

2.3 处理 API 返回的 JSON

import requests response = requests.get('https://api.example.com/data') # 直接解析响应内容为 JSON data = response.json() print(data)

2.4 安全解析

实际项目中,API 返回的数据不一定都是合法的 JSON。做好异常处理很重要。

import json def safe_json_loads(json_str, default=None): """安全解析 JSON""" try: return json.loads(json_str) except json.JSONDecodeError as e: print(f"JSON 解析失败: {e}") return default except Exception as e: print(f"其他错误: {e}") return default # 使用 result = safe_json_loads('{"name": "张三"}') print(result) # {'name': '张三'} result = safe_json_loads('不是 JSON') print(result) # None

三、生成 JSON 数据

3.1 基础生成

import json # Python 字典 data = { 'name': '张三', 'age': 25, 'city': '北京', 'hobbies': ['读书', '编程', '旅游'] } # 转为 JSON 字符串 json_str = json.dumps(data) print(json_str) # {"name": "\u5f20\u4e09", "age": 25, "city": "\u5317\u4eac", "hobbies": ["\u8bfb\u4e66", "\u7f16\u7a0b", "\u65c5\u6e38"]}

3.2 格式化输出

# 格式化输出,带缩进 json_str = json.dumps(data, indent=2, ensure_ascii=False) print(json_str) # 输出: # { # "name": "张三", # "age": 25, # "city": "北京", # "hobbies": [ # "读书", # "编程", # "旅游" # ] # }

3.3 保存到文件

# 保存到文件 with open('output.json', 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False)
参数说明:ensure_ascii=False 让中文字符正常显示,而不是转义成 Unicode 编码。indent=2 让输出有缩进,更易读。

四、处理复杂结构

4.1 嵌套数据访问

data = { 'user': { 'name': '张三', 'profile': { 'age': 25, 'address': { 'city': '北京', 'district': '朝阳区' } } } } # 访问嵌套数据 city = data['user']['profile']['address']['city'] print(city) # 北京

4.2 安全访问嵌套数据

直接访问嵌套数据,如果某个层级不存在会报错。用 get 方法更安全。

def safe_get(data, *keys, default=None): """安全获取嵌套字典的值""" for key in keys: if isinstance(data, dict): data = data.get(key, default) if data is None: return default else: return default return data # 使用 city = safe_get(data, 'user', 'profile', 'address', 'city') print(city) # 北京 # 不存在的路径 province = safe_get(data, 'user', 'profile', 'address', 'province', default='未知') print(province) # 未知

4.3 处理列表数据

data = { 'users': [ {'name': '张三', 'age': 25}, {'name': '李四', 'age': 30}, {'name': '王五', 'age': 28} ] } # 遍历列表 for user in data['users']: print(f"{user['name']}: {user['age']}岁") # 列表推导式 names = [user['name'] for user in data['users']] print(names) # ['张三', '李四', '王五'] # 过滤 adults = [user for user in data['users'] if user['age'] >= 28] print(adults) # [{'name': '李四', 'age': 30}, {'name': '王五', 'age': 28}]

五、数据验证

5.1 检查必需的字段

def validate_user_data(data): """验证用户数据""" required_fields = ['name', 'age', 'email'] for field in required_fields: if field not in data: raise ValueError(f"缺少必需字段: {field}") if not isinstance(data['age'], int): raise ValueError("age 必须是整数") if data['age'] < 0 or data['age'] > 150: raise ValueError("age 范围无效") return True # 使用 try: validate_user_data({'name': '张三', 'age': 25, 'email': 'zhangsan@example.com'}) print("验证通过") except ValueError as e: print(f"验证失败: {e}")

5.2 使用 JSON Schema

对于复杂的数据验证,可以使用 JSON Schema。

from jsonschema import validate, ValidationError # 定义 Schema schema = { "type": "object", "properties": { "name": {"type": "string", "minLength": 1}, "age": {"type": "integer", "minimum": 0, "maximum": 150}, "email": {"type": "string", "format": "email"}, "hobbies": { "type": "array", "items": {"type": "string"} } }, "required": ["name", "age"] } # 验证 try: validate(instance=data, schema=schema) print("验证通过") except ValidationError as e: print(f"验证失败: {e.message}")

六、数据转换

6.1 JSON 转 CSV

import csv import json # JSON 数据 data = [ {'name': '张三', 'age': 25, 'city': '北京'}, {'name': '李四', 'age': 30, 'city': '上海'}, {'name': '王五', 'age': 28, 'city': '广州'} ] # 转为 CSV with open('output.csv', 'w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=['name', 'age', 'city']) writer.writeheader() writer.writerows(data)

6.2 JSON 转 Excel

import pandas as pd # JSON 数据转为 DataFrame df = pd.DataFrame(data) # 保存为 Excel df.to_excel('output.xlsx', index=False)

6.3 扁平化嵌套 JSON

def flatten_json(data, parent_key='', sep='.'): """扁平化嵌套 JSON""" items = [] for k, v in data.items(): new_key = f"{parent_key}{sep}{k}" if parent_key else k if isinstance(v, dict): items.extend(flatten_json(v, new_key, sep=sep).items()) elif isinstance(v, list): for i, item in enumerate(v): if isinstance(item, dict): items.extend(flatten_json(item, f"{new_key}[{i}]", sep=sep).items()) else: items.append((f"{new_key}[{i}]", item)) else: items.append((new_key, v)) return dict(items) # 使用 nested = {'user': {'name': '张三', 'age': 25}} flat = flatten_json(nested) print(flat) # {'user.name': '张三', 'user.age': 25}

七、常见问题

7.1 单引号问题

JSON 标准规定必须用双引号,但有时候会遇到单引号的"伪 JSON"。

# 错误的 JSON(单引号) bad_json = "{'name': '张三', 'age': 25}" # 解决方法1:替换单引号 good_json = bad_json.replace("'", '"') data = json.loads(good_json) # 解决方法2:使用 ast 模块(不推荐,有安全风险) import ast data = ast.literal_eval(bad_json)

7.2 中文乱码

# 确保文件编码正确 with open('data.json', 'r', encoding='utf-8') as f: data = json.load(f) # 输出时指定 ensure_ascii=False json_str = json.dumps(data, ensure_ascii=False)

7.3 处理日期

JSON 没有日期类型,通常用字符串表示。

from datetime import datetime # 自定义 JSON 编码器 class DateTimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return obj.strftime('%Y-%m-%d %H:%M:%S') return super().default(obj) # 使用 data = {'name': '张三', 'created_at': datetime.now()} json_str = json.dumps(data, cls=DateTimeEncoder) print(json_str) # {"name": "\u5f20\u4e09", "created_at": "2024-01-15 10:30:00"}

八、性能优化

8.1 使用 orjson

orjson 是一个高性能的 JSON 库,比标准库快很多。

# 安装:pip install orjson import orjson # 解析 data = orjson.loads(json_str) # 生成 json_bytes = orjson.dumps(data) json_str = json_bytes.decode('utf-8') # 带选项的生成 json_bytes = orjson.dumps(data, option=orjson.OPT_INDENT_2)

8.2 流式处理大文件

import ijson # 流式解析大 JSON 文件 with open('large_data.json', 'rb') as f: # 逐个解析 items 数组中的对象 for item in ijson.items(f, 'items.item'): print(item) # 处理每个 item,内存占用很小

九、总结

JSON 处理看似简单,但实际项目中有很多细节需要注意。记住这几个核心要点:

场景 推荐做法 注意事项
解析 JSON json.loads() 做好异常处理
生成 JSON json.dumps() 设置 ensure_ascii=False
嵌套数据 封装安全访问函数 避免 KeyError
数据验证 JSON Schema 定义完整的 Schema
性能优化 orjson 大量数据时使用

JSON 是爬虫开发中最常处理的数据格式。熟练掌握这些技巧,能让你在数据处理环节节省大量时间。