从JSON向DataFrame添加solution与cve列失败的问题求助
解决Pandas处理JSON嵌套字段时多列explode的匹配问题
问题场景
从JSON文件读取数据后,需要提取asset.hostname、plugin.solution、plugin.cve三个字段整合到Pandas DataFrame中。单独对plugin.solution或plugin.cve列做explode+groupby聚合时,能正常生成按hostname分组的结果;但同时处理两列时遇到两个问题:
- 先分别对两列执行
explode,再groupby后调用.cve.apply会触发AttributeError,提示'DataFrame'对象无'cve'属性 - 直接同时
explode两列会触发ValueError,提示列元素数量不匹配
原始代码示例
import pandas as pd import json # 读取JSON数据 with open('vuln_data.json', 'r') as f: raw_data = json.load(f) # 扁平化JSON为DataFrame df = pd.json_normalize(raw_data) # 单独处理CVE的可行代码 cve_result = df.explode('plugin.cve').groupby('asset.hostname')['plugin.cve'].apply(list).reset_index() # 尝试同时处理两列的错误代码1:分别explode sol_exploded = df.explode('plugin.solution') both_exploded = sol_exploded.explode('plugin.cve') # 触发AttributeError wrong_result = both_exploded.groupby('asset.hostname').cve.apply(list) # 尝试同时处理两列的错误代码2:一次性explode两列 # 触发ValueError another_wrong_result = df.explode(['plugin.solution', 'plugin.cve']).groupby('asset.hostname').agg( {'plugin.solution': list, 'plugin.cve': list} )
示例JSON数据
[ { "asset": {"hostname": "web-server-01"}, "plugin": { "solution": ["升级Apache至2.4.57", "禁用不必要的CGI模块"], "cve": ["CVE-2023-25690"] } }, { "asset": {"hostname": "db-server-01"}, "plugin": { "solution": ["修补MySQL权限漏洞"], "cve": ["CVE-2023-32145", "CVE-2023-40045"] } } ]
错误信息详情
AttributeError
AttributeError: 'DataFrame' object has no attribute 'cve'
问题根源:groupby后未指定具体列时返回的是DataFrame对象,直接用.cve访问会找不到属性;即便修正为['plugin.cve'],两次explode也会导致数据行数异常膨胀,破坏solution与cve的对应关联关系。
ValueError
ValueError: columns must have matching element counts
问题根源:plugin.solution和plugin.cve的列表长度不一致,Pandas的多列explode要求每行的所有目标列列表长度完全相同,否则无法匹配展开。
可行解决方案(经Jason Baker协助验证)
核心思路是逐行处理原始数据,根据实际业务逻辑将solution与cve的列表元素进行正确配对,再统一聚合,避免直接使用explode的限制。
import pandas as pd import json from itertools import product def process_single_row(row): # 提取基础字段 hostname = row['asset.hostname'] # 处理非列表格式的字段(兼容单个值的情况) solutions = row['plugin.solution'] if isinstance(row['plugin.solution'], list) else [row['plugin.solution']] cves = row['plugin.cve'] if isinstance(row['plugin.cve'], list) else [row['plugin.cve']] # 按业务逻辑生成配对:这里用笛卡尔积(表示每个solution对应所有cve,反之亦然) # 可根据实际关联关系调整配对逻辑,比如按索引匹配等 paired_data = list(product(solutions, cves)) # 返回单行处理后的DataFrame片段 return pd.DataFrame([ {'hostname': hostname, 'solution': sol, 'cve': cve} for sol, cve in paired_data ]) # 读取并处理数据 with open('vuln_data.json', 'r') as f: raw_data = json.load(f) df = pd.json_normalize(raw_data) # 逐行处理并合并所有片段 expanded_df = pd.concat([process_single_row(row) for _, row in df.iterrows()], ignore_index=True) # 按hostname聚合,去重后保留列表 final_result = expanded_df.groupby('hostname').agg( { 'solution': lambda x: list(set(x)), 'cve': lambda x: list(set(x)) } ).reset_index() print(final_result)
方案说明
- 逐行处理确保每个solution和cve的关联关系符合业务逻辑,避免
explode的长度限制 - 兼容单个值的字段(非列表格式),提升代码鲁棒性
- 聚合时用
set去重,避免重复值占用空间
内容的提问来源于stack exchange,提问作者LObermeyer
相关产品推荐
相关产品推荐

