共计 5540 个字符,预计需要花费 14 分钟才能阅读完成。
上周帮同事调试接口时,发现很多人还在用 for 循环一层层处理那些复杂嵌套的 JSON 数据。好家伙,代码又长又容易出错,排查起来更是血压飙升。其实,用 jsonpath 库能省一半时间,代码不仅更精炼,逻辑也清晰得多。今天,我就带大家实操一遍,看看我是怎么用它高效提取数据的,告别那些繁琐的循环地狱。
第一步:初识 Jsonpath —— 告别“大海捞针”式的遍历
很多人拿到 JSON 数据,第一反应就是用 for 循环、if 判断,一层层往下挖。当 JSON 结构简单时还勉强能应付,但一旦遇到多层嵌套、数组混杂的情况,代码很快就变得难以维护。Jsonpath 提供了一种类似 XPath 的方式来定位和提取 JSON 中的数据,让你直接“指哪打哪”。
我平时倾向使用 jsonpath_ng 这个库,它比传统的 jsonpath 库更活跃,语法支持也更全面,尤其是处理一些复杂表达式时,体验会好很多。
安装 jsonpath_ng:
pip install jsonpath_ng
基础用法演示:提取简单路径
假设我们有这样一份用户数据:
{
"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
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
},
"expensive": 10
}
现在我想提取所有书的作者和标题,如果用传统方法,你可能需要循环 store 下的 book 数组,再提取每个对象的 author 和 title。用 jsonpath_ng 就会简单很多。
from jsonpath_ng import jsonpath, parse
import json
data = {
"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
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
},
"expensive": 10
}
# 提取 store 下所有书的作者
jsonpath_expr_author = parse('$.store.book[*].author') # 这里 [*] 表示匹配数组中的所有元素
authors = [match.value for match in jsonpath_expr_author.find(data)]
print(f"所有作者: {authors}")
# 输出: 所有作者: ['Nigel Rees', 'Evelyn Waugh']
# 提取第一本书的标题
jsonpath_expr_title = parse('$.store.book[0].title') # [0] 表示数组的第一个元素
first_book_title = [match.value for match in jsonpath_expr_title.find(data)]
print(f"第一本书的标题: {first_book_title[0] if first_book_title else' 未找到 '}")
# 输出: 第一本书的标题: Sayings of the Century
# 提取自行车的颜色
jsonpath_expr_color = parse('$.store.bicycle.color')
bicycle_color = [match.value for match in jsonpath_expr_color.find(data)]
print(f"自行车的颜色: {bicycle_color[0] if bicycle_color else' 未找到 '}")
# 输出: 自行车的颜色: red
# 小提醒:Jsonpath 表达式跟 XPath 有点像,初学者容易搞混。# 记住,它是针对 JSON 结构的,`.` 用于对象属性,`[]` 用于数组索引或条件筛选。# 如果路径不存在,`find()` 方法会返回一个空列表,而不是报错,这是个好习惯,避免了额外的 try-except。
第二步:进阶用法——数组、过滤与通配符
jsonpath_ng 的强大之处在于它能处理更复杂的场景,比如根据条件过滤、使用通配符匹配等。
根据条件过滤数组元素:
# 提取所有价格低于 10 的书的标题
# 注意这里的 [?()] 语法,它允许你在数组中进行条件筛选
jsonpath_expr_cheap_books = parse('$.store.book[?price < 10].title')
cheap_book_titles = [match.value for match in jsonpath_expr_cheap_books.find(data)]
print(f"价格低于 10 的书的标题: {cheap_book_titles}")
# 输出: 价格低于 10 的书的标题: ['Sayings of the Century']
# 提取所有 fiction 类书籍的作者
jsonpath_expr_fiction_author = parse('$.store.book[?category ="fiction"].author') # 字符串值需要用引号
fiction_authors = [match.value for match in jsonpath_expr_fiction_author.find(data)]
print(f"fiction 类书籍的作者: {fiction_authors}")
# 输出: fiction 类书籍的作者: ['Evelyn Waugh']
# 小提醒:条件过滤的语法 `[?()]` 里面支持各种比较操作符(<, >, =, !=, <=, >=)。# 如果遇到需要判断是否存在某个键的情况,可以用 `[?(@.key)]`,意思是存在 key 属性的元素。# 我之前爬取豆瓣时,就遇到过有些电影条目没有评分,用这个就能很方便地筛选掉。
* 使用通配符 ` 和递归下降 ..`:**
* 匹配当前层级的所有属性或数组元素。
.. 递归下降,匹配所有子孙节点。
# 提取所有书的任意属性(不推荐,但演示用法)# jsonpath_expr_all_book_props = parse('$.store.book[*].*')
# all_book_props = [match.value for match in jsonpath_expr_all_book_props.find(data)]
# print(f"所有书的所有属性值: {all_book_props}")
# 输出会很长,因为它会提取每个书对象的所有属性值。# 提取所有价格(无论它在哪里,使用递归下降)jsonpath_expr_all_prices = parse('$..price') # 匹配所有子孙节点中的 'price' 属性
all_prices = [match.value for match in jsonpath_expr_all_prices.find(data)]
print(f"所有价格: {all_prices}")
# 输出: 所有价格: [8.95, 12.99, 19.95]
# 小提醒:`..` 虽然方便,但在大型 JSON 中使用时要谨慎,因为它会遍历整个子树,可能会影响性能。# 只有当你确实不知道某个字段的具体路径时,才考虑使用它。我通常会尽量明确路径,这样可读性也更好。
第三步:实战——从复杂 API 响应中提取数据
假设我们正在处理一个电商平台的订单 API 响应,它可能包含多个订单、每个订单下又有很多商品,结构复杂。
api_response = {
"status": "success",
"timestamp": "2023-10-26T10:00:00Z",
"data": {
"orders": [
{
"orderId": "ORD001",
"userId": "user_a",
"status": "completed",
"items": [{"productId": "P001", "name": "Python 编程指南", "price": 99.0, "quantity": 1},
{"productId": "P002", "name": "Django 开发实战", "price": 129.0, "quantity": 1}
],
"totalAmount": 228.0
},
{
"orderId": "ORD002",
"userId": "user_b",
"status": "pending",
"items": [{"productId": "P003", "name": "数据结构与算法", "price": 88.0, "quantity": 2}
],
"totalAmount": 176.0
},
{
"orderId": "ORD003",
"userId": "user_a",
"status": "completed",
"items": [{"productId": "P004", "name": "机器学习入门", "price": 150.0, "quantity": 1}
],
"totalAmount": 150.0
}
]
},
"metadata": {
"page": 1,
"pageSize": 10
}
}
任务: 提取所有状态为 “completed” 的订单的 orderId 和 totalAmount,以及这些订单中所有商品的名字。
# 提取所有已完成订单的 orderId 和 totalAmount
completed_orders_info_expr = parse('$.data.orders[?status ="completed"]')
completed_orders = completed_orders_info_expr.find(api_response)
for order_match in completed_orders:
order_data = order_match.value
order_id = order_data.get("orderId")
total_amount = order_data.get("totalAmount")
print(f"已完成订单 - Order ID: {order_id}, Total Amount: {total_amount}")
# 进一步提取该订单下的所有商品名
# 这里我们不能直接在原表达式上加 `.items[*].name`,因为 `order_match` 已经是过滤后的对象了
# 我们可以对 `order_data` 这个子结构再应用 Jsonpath
item_names_expr = parse('$.items[*].name') # 注意这里的路径是相对于当前订单对象了
item_names = [match.value for match in item_names_expr.find(order_data)]
print(f"商品列表: {', '.join(item_names)}")
# 输出示例:# 已完成订单 - Order ID: ORD001, Total Amount: 228.0
# 商品列表: Python 编程指南, Django 开发实战
# 已完成订单 - Order ID: ORD003, Total Amount: 150.0
# 商品列表: 机器学习入门
# 小提醒:遇到深度嵌套的数据,不要怕,一层层分析路径就好。# 我刚开始也容易迷路,会画个简图,把 JSON 结构梳理出来,再对应地写 Jsonpath 表达式,这样清晰很多。# `get()` 方法在这里也很有用,可以避免键不存在时抛出 KeyError。
常见误区与避坑指南
作为过来人,我总结了几个新手在使用 jsonpath_ng 时常犯的错误,希望大家能少走弯路:
-
误区一:混淆
parse()和find()的返回类型。
jsonpath_ng.parse()返回的是一个JsonPath对象,而find(data)方法返回的是一个JsonPathFinder对象的列表。你必须通过match.value才能获取到实际的数据值。我刚开始学jsonpath_ng时,总以为find()直接返回想要的值,结果老是拿到一堆<JsonPathFinder object at ...>,调试半天才发现要加.value,哭笑不得。 -
误区二:数组索引与对象键的混用。
Jsonpath 表达式中,$代表根节点,.用于访问对象的属性,[]用于访问数组元素(通过索引)或进行条件筛选。例如,$.users[0].name是正确的,表示访问users数组的第一个元素的name属性。但$.users.0.name就是错误的,因为0不是users对象下的一个属性。 -
误区三:错误处理空值或路径不存在的情况。
如前所述,find()方法在路径不存在时会返回一个空列表[],而不是抛出KeyError或其他异常。这是一个设计上的优点,意味着你不需要为每次可能的路径缺失都编写try-except块。但你需要在使用结果时检查列表是否为空,例如if result:或if result[0] if result else None这种方式。
掌握 Jsonpath,能让你的 JSON 数据处理代码更精炼、更高效,大幅提升开发效率。特别是在处理来自 API 的复杂响应时,它简直是神器!
你还遇到过哪些 Jsonpath 的奇葩用法或踩坑经历?欢迎在评论区分享,咱们一起交流学习!