Python 实战:告别手动遍历,用 JSONPath 精准提取复杂 JSON 数据

83次阅读
没有评论

共计 4515 个字符,预计需要花费 12 分钟才能阅读完成。

上周帮团队新人调试一个微服务接口,发现他在处理返回的复杂 JSON 数据时,还在用多层 for 循环和 if 判断。我一看,这效率和代码可读性都堪忧啊!其实,对于这类场景,jsonpath 库简直是神器,能让你少写一半代码,数据提取速度和准确性都大大提高。今天,咱们就来聊聊如何用 jsonpath 优雅地搞定 JSON 数据提取。


实操第一步:认识 jsonpath 并安装

想象一下,你拿到一个巨型 JSON 字符串,里面嵌套了十几层,你只想要其中某个字段的值。手动一层层 data['key1']['key2'][0]['target_key'],想想都头大,而且一旦结构变了,你代码就得跟着改。jsonpath 就是来解决这个痛点的,它提供了一种类似 XPath 的语法,让你能精准定位并提取 JSON 中的任意数据。

首先,我们得把它请到项目里:

pip install jsonpath

安装好之后,我们来个简单的体验。

import json
from jsonpath import jsonpath

# 模拟一个简单的 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
        }
    }
}

# 提取所有书的作者
authors = jsonpath(data, '$..author')
print(f"所有书的作者: {authors}") # 刚上手的时候,我老是把 `$` 写成 `.`,结果一直报错,调试半天发现是路径根节点没写对。# 提取第一本书的标题
first_book_title = jsonpath(data, '$.store.book[0].title')
print(f"第一本书的标题: {first_book_title}")

小提醒: jsonpath 函数返回的永远是一个列表,即使它只找到了一个结果。所以,如果确定只有一个结果,记得加索引 [0] 来获取具体值。


实操第二步:深入探索 jsonpath 核心语法

jsonpath 的强大之处在于其丰富的表达式。我平时用得最多的,就是下面这几个:

  • $:根节点
  • .[]:子节点,用于访问对象的属性或数组的元素
  • *:通配符,匹配所有子节点或所有数组元素
  • ..:递归下降,查找所有符合条件的节点,无论层级多深
  • []:切片操作,[start:end:step]
  • ?():过滤表达式,根据条件过滤节点,@ 代表当前节点

我们用一个更复杂的场景来把它们串起来。假设我们要从一个电商订单系统中获取特定信息:

complex_data = {
    "orderId": "ORD-2023-001",
    "customer": {
        "name": "张三",
        "email": "[email protected]",
        "address": {
            "city": "北京",
            "street": "朝阳路 100 号"
        }
    },
    "items": [
        {
            "productId": "P001",
            "productName": "MacBook Pro",
            "quantity": 1,
            "price": 15000.00,
            "tags": ["laptop", "apple", "premium"]
        },
        {
            "productId": "P002",
            "productName": "Wireless Mouse",
            "quantity": 2,
            "price": 200.00,
            "tags": ["accessories", "wireless"]
        },
        {
            "productId": "P003",
            "productName": "Monitor",
            "quantity": 1,
            "price": 2500.00,
            "tags": ["display"]
        }
    ],
    "totalAmount": 17900.00,
    "status": "completed"
}

# 1. 提取所有商品的名字 (使用通配符和子节点访问)
all_product_names = jsonpath(complex_data, '$.items[*].productName')
print(f"n 所有商品名字: {all_product_names}")

# 2. 提取价格高于 1000 元的商品信息 (使用过滤表达式)
expensive_items = jsonpath(complex_data, '$.items[?(@.price > 1000)]')
print(f"价格高于 1000 元的商品: {expensive_items}") # 我刚接触 `jsonpath` 的过滤表达式时,`@` 符号理解了好久,踩了不少坑才明白它代表当前节点,大家一定要多练练。# 3. 提取所有商品的标签 (使用递归下降,无论标签在哪个层级)
all_tags = jsonpath(complex_data, '$..tags[*]')
print(f"所有商品的标签: {all_tags}")

# 4. 提取 customer 的 email (两种写法)
customer_email_dot = jsonpath(complex_data, '$.customer.email')
customer_email_bracket = jsonpath(complex_data, '$["customer"]["email"]')
print(f"客户邮箱 ( 点语法): {customer_email_dot}")
print(f"客户邮箱 ( 方括号语法): {customer_email_bracket}")

小提醒: 过滤表达式 ?() 非常强大,但也是新手最容易犯错的地方。务必记住 @ 符号代表当前正在处理的节点。多尝试不同的条件,你会发现它的魔力。


实操第三步:实战:从 API 响应中精准提取数据

实际开发中,我们最常用 jsonpath 的场景就是处理第三方 API 返回的 JSON 数据。很多时候,API 返回的数据结构都异常复杂,这时候 jsonpath 就派上大用场了。

import requests # 假设我们需要从网络获取数据

# 模拟一个实际的 API 响应,通常是字符串形式
api_response_str = """{"code": 200,"message":"success","data": {"pagination": {"total": 120,"page": 1,"pageSize": 10},
        "records": [
            {
                "id": "A001",
                "name": "服务器 A",
                "status": "running",
                "location": "上海",
                "ipAddresses": ["192.168.1.10", "10.0.0.1"]
            },
            {
                "id": "A002",
                "name": "服务器 B",
                "status": "stopped",
                "location": "北京",
                "ipAddresses": ["192.168.1.11"]
            },
            {
                "id": "A003",
                "name": "服务器 C",
                "status": "running",
                "location": "上海",
                "ipAddresses": []}
        ]
    }
}
"""

# 首先,将 JSON 字符串解析成 Python 字典
api_data = json.loads(api_response_str)

# 1. 提取所有服务器的名称
server_names = jsonpath(api_data, '$.data.records[*].name')
print(f"n 所有服务器名称: {server_names}")

# 2. 提取所有处于 "running" 状态的服务器的 ID
running_server_ids = jsonpath(api_data, '$.data.records[?(@.status =="running")].id')
print(f"运行中的服务器 ID: {running_server_ids}")

# 3. 提取所有服务器的 IP 地址(注意,可能会有空列表)all_ips = jsonpath(api_data, '$.data.records[*].ipAddresses')
print(f"所有服务器的 IP 地址列表: {all_ips}")
# 扁平化 IP 地址列表,只获取存在的 IP
flat_ips = [ip for sublist in all_ips if sublist for ip in sublist]
print(f"扁平化后的所有 IP: {flat_ips}") # 有次爬取公开 API,发现某个字段偶尔会丢失,如果直接 `result[0].value` 就会报错。后来我学乖了,每次都先判断 `result` 是否为空。# 4. 获取总记录数
total_records = jsonpath(api_data, '$.data.pagination.total')
print(f"总记录数: {total_records}")

# 5. 尝试提取一个不存在的字段,看会发生什么
non_existent_field = jsonpath(api_data, '$.data.records[*].memory')
print(f"尝试提取不存在的字段 (memory): {non_existent_field}")

小提醒: 当你提取的字段可能不存在时,jsonpath 会返回 False 或空列表 []。所以在实际应用中,一定要对结果进行判断,防止空指针或索引越界错误。我亲测有效,这种防御性编程能省下很多调试时间。


常见误区:新手常犯的 jsonpath 错误

我刚开始学 jsonpath 时,也踩过不少坑,这里总结几个大家可能也会遇到的:

  1. 误区一:混淆 ... 的用法。 . 用于直接子节点访问,而 .. 是递归下降,会搜索所有层级的匹配项。比如 $.store.book[*].author 只会找 storebook 数组里的 author,而 $.store..author 则会找 store 下所有层级名为 author 的字段。弄清楚这个能避免很多困惑。
  2. 误区二:忘记 jsonpath() 返回的是列表。 即使你路径精准定位到唯一的那个值,它依然会把它放在一个列表里返回。比如 jsonpath(data, '$.orderId') 返回的是 ['ORD-2023-001'] 而不是 'ORD-2023-001'。取值时记得 [0]
  3. 误区三:过滤表达式 ?()@ 的理解偏差。 @ 永远代表当前正在被 ?() 表达式评估的 JSON 节点。如果你想比较当前节点的某个属性,必须写成 @.property_name

经验总结

总的来说,jsonpath 库能极大提升我们处理复杂 JSON 数据的效率和代码可读性,尤其是在处理嵌套层级深、结构多变的 JSON 时,它的优势就更加明显。相对于手动遍历,jsonpath 确实在代码量和直观性上都有显著优势,大家可根据实际场景和项目需求选择使用。

你平时处理 JSON 数据,有什么独家秘籍或者踩坑经历吗?欢迎在评论区分享,我们一起交流学习!

正文完
 0
评论(没有评论)