ijson通过流式解析避免OOM,用ijson.items()按路径逐个提取数组元素,需指定yajl2_cffi后端、正确处理编码,并手动容错处理不规范数据。

用 ijson 按需拉取数组元素,避免一次性加载
大型 JSON 文件(比如几百 MB 的日志数组)用 json.load() 会直接 OOM。核心思路是不解析整个文档,而是定位到目标数组后,逐个读取其内部对象。ijson 是最成熟的选择,它基于 C 扩展(ijson.backends.yajl2_cffi),边流式读取边触发事件,内存占用稳定在 KB 级别。
常见错误是误以为 ijson.parse() 能直接拿到 Python 对象——它返回的是 (event, value) 元组流,真正要用的是 ijson.items() 或 ijson.kvitems() 这类封装函数:
- 若 JSON 是纯数组:
ijson.items(file_obj, "item")直接迭代每个元素 - 若数组嵌套在字段里(如
{"data": [...]}):用ijson.items(file_obj, "data.item") - 路径中的
item是 ijson 的保留字,代表数组任意下标元素,不是字面量
ijson.items() 的参数陷阱和性能关键点
很多人卡在路径写错或编码异常上。ijson 默认按字节流读取,如果文件含 BOM 或非 UTF-8 编码(如 GBK 日志),必须显式传入解码后的 io.TextIOWrapper,否则抛 UnicodeDecodeError 或静默乱码。
性能差异主要来自后端选择:yajl2_cffi 比纯 Python 后端快 5–10 倍,但需安装 cffi;Windows 上可能需先装 Visual Studio Build Tools。实操建议:
立即学习“Python免费学习笔记(深入)”;
- 始终指定后端:
ijson.parse(file_obj, multiple_values=True, use_float=True, backend="yajl2_cffi") - 用
buffer_size=65536(默认值)足够,调大反而可能增加延迟 - 避免在循环内反复调用
ijson.items()——每次都会重置解析器,应复用一次打开的文件对象
处理中途失败、数据不规整的容错策略
真实场景中,JSON 数组可能被截断、混入脏数据(如某行多一个逗号)、或部分对象字段缺失。ijson 默认遇到语法错误就抛 ijson.common.IncompleteJSONError 或 ValueError,无法跳过。
解决办法是手动封装一层迭代器,捕获异常并尝试从下一个合法位置继续(依赖 yajl2 的恢复能力):
import ijson
def robust_json_array_iter(file_path, path="item"):
with open(file_path, "rb") as f:
parser = ijson.parse(f, backend="yajl2_cffi")
# 手动跳过开头直到到达数组起始
while True:
try:
event, value = next(parser)
if event == "start_array" and path == "item":
break
except StopIteration:
return
# 用 items 重新绑定数组上下文
f.seek(0)
for obj in ijson.items(f, path, use_float=True, backend="yajl2_cffi"):
try:
yield obj
except (ijson.common.IncompleteJSONError, ValueError):
continue注意:这种容错会丢失错误位置信息,仅适用于“宁可漏掉几条也不中断”的场景;若需精确定位,得改用 jsonstream 或自研基于 json.JSONDecoder.raw_decode 的增量解析。
替代方案对比:什么时候不该用 ijson?
ijson 强在数组流式提取,但对以下情况不适用:
- 需要随机访问数组某下标(如取第 10000 个元素)——ijson 必须顺序遍历,此时应先用
mmap+ 正则定位行偏移 - JSON 是单个巨型对象而非数组(如
{"logs": [...], "meta": {...}})——用ijson.kvitems()提取键值对更合适 - 环境受限无法装 C 扩展(如某些 Serverless 平台)——改用
jsonlines库,但前提是数据本身是 JSON Lines 格式(每行一个 JSON)
真正棘手的是数组里套数组(如 [{"items": [...]}, ...]),这时路径要写成 "item.items.item",且必须确认内层数组结构一致——一旦某对象缺 items 字段,ijson.items() 就会静默跳过整个外层对象,这点极易被忽略。


















