查看: 2647|回复: 3

Python伪列表字符串解析:json.loads与正则提取

[复制链接]
发表于 前天 13:00 | 显示全部楼层 |阅读模式
在 Python 开发中,经常遇到看起来像 list、实际类型是 str 的数据。例如接口或日志里出现:
  1. data = '["qwen-turbo","qwen-plus","deepseek"]'
  2. print(type(data))  # <class 'str'>
复制代码

另一种是包含自定义对象的 repr 字符串,例如 LangChain 的 Document:
  1. data = "[Document(metadata={'pk': 12, 'page': 2}, page_content='...')]"
复制代码

这两类字符串的解析方式不同:标准 JSON 数组可以用 json.loads(),而 Python 对象 repr 字符串需要正则提取字段。下面按两种场景说明。

一、标准 JSON 格式字符串转列表
题目:
  1. raw_str = '["qwen-turbo","qwen-plus","deepseek"]'
  2. # 将其转换为 list
复制代码

这是标准 JSON 格式字符串,内容是字符串数组。

方法一:json.loads() 推荐
  1. import json
  2. raw_str = '["qwen-turbo","qwen-plus","deepseek"]'
  3. result = json.loads(raw_str)
  4. print(result)       # ['qwen-turbo', 'qwen-plus', 'deepseek']
  5. print(type(result)) # <class 'list'>
复制代码

优点:速度快,标准库支持,JSON 格式通用性强。

方法二:ast.literal_eval()
  1. import ast
  2. raw_str = '["qwen-turbo","qwen-plus","deepseek"]'
  3. result = ast.literal_eval(raw_str)
  4. print(result)       # ['qwen-turbo', 'qwen-plus', 'deepseek']
  5. print(type(result)) # <class 'list'>
复制代码

优点:安全性高,只解析字面量,不会执行恶意代码。

方法三:eval() 不推荐
  1. raw_str = '["qwen-turbo","qwen-plus","deepseek"]'
  2. result = eval(raw_str)
  3. print(result)       # ['qwen-turbo', 'qwen-plus', 'deepseek']
复制代码

缺点:会执行任意 Python 代码,有安全风险,生产环境慎用。

三种方法对比:json.loads() 速度最快、安全性较好,适合 JSON 格式字符串;ast.literal_eval() 速度一般、最安全,适合 Python 字面量格式;eval() 速度一般、危险,不推荐使用。

二、对象格式字符串提取字段
题目:
  1. raw_str = """[Document(metadata={'pk': 12, 'page': 2}, page_content='2. 费⽤报销:业务活动中产⽣的费⽤...'),
  2. Document(metadata={'pk': 27, 'page': 2}, page_content='2. 费⽤报销:业务活动中产⽣的费⽤...'),
  3. Document(metadata={'pk': 14, 'page': 3}, page_content='3. 最后警告:情节严重,留司察看...')]"""
复制代码

问题是:如何获取里面的 page 属性,并按页码组织 page_content?

这个字符串包含 LangChain 的 Document 对象。它不是标准 JSON,而是 Python 的 repr() 输出格式。

为什么 json.loads() 不行?因为字符串中使用单引号,JSON 要求双引号;Document(...) 这种自定义对象也不是 JSON 支持的数据类型。调用 json.loads(raw_str) 会报 json.decoder.JSONDecodeError。

为什么 ast.literal_eval() 也不行?因为它只能解析 Python 字面量,例如字符串、数字、列表、字典等,不能解析函数调用 Document(...)。调用 ast.literal_eval(raw_str) 会报 ValueError: malformed node。

解决方案是使用正则表达式。

方法 1:只提取 page
  1. import re
  2. raw_str = "[Document(metadata={'pk': 12, 'page': 2}, page_content='...'), ...]"
  3. pages = re.findall(r"'page':\s*(\d+)", raw_str)
  4. pages = [int(p) for p in pages]
  5. print(pages)  # [2, 2, 3]
复制代码

正则含义:'page': 匹配字面量;\s* 匹配 0 个或多个空白字符;(\d+) 捕获 1 个或多个数字。

方法 2:提取 page 和 page_content,并按页分组

[code]
import re
from collections import defaultdict

raw_str = "[Document(metadata={'pk': 12, 'page': 2}, page_content='...'), ...]"
docs = re.findall(
    r"Document\(metadata=\{.*?'page':\s*(\d+).*?page_content='(.*?)'\)",
    raw_str,
    re.DOTALL
)

page_contents = defaultdict(list)
for page, content in docs:
    page_contents[int(page)].append(content)

for page in sorted(page_contents.keys()):
    combined = "\n".join(page_contents
回复

使用道具 举报

发表于 前天 19:00 | 显示全部楼层

Re: Python伪列表字符串解析:json.loads与正则提取

楼主把两类“伪列表字符串”分开处理,思路很清楚。标准 JSON 数组优先用 json.loads 是合适的,ast.literal_eval 适合 Python 字面量而且更安全,eval 确实应该尽量避开。Document 这种 repr 字符串不是 JSON,也不是纯字面量,json.loads 和 literal_eval 都会失败,用正则提取字段是比较务实的做法。 只取 page 时直接 findall 最省事;要同时取 page 和 page_content,用 DOTALL 加非贪婪匹配在格式稳定时够用,但要注意 page_content 里如果出现单引号、右括号或换行,可能会提前截断。如果担心健壮性,可以先按 Document( 切分,再逐段提取,会更可控。最后用 defaultdict 按页码聚合,再 sorted 输出,顺序和结构都很自然。 另外如果原始字符串里真的带有 &quot; 这类 HTML 实体,记得先 html.unescape 再 json.loads,否则也会解析失败。整体上这个分类和方案对实际开发挺有参考价值。
回复 支持 反对

使用道具 举报

发表于 前天 19:10 | 显示全部楼层

Re: Python伪列表字符串解析:json.loads与正则提取

这篇整理得很实用,json.loads、ast.literal_eval 和 eval 的取舍讲得很清楚,尤其强调 eval 的安全风险这点很有必要。Document 那种 repr 字符串确实不能当 JSON 处理,用正则先抓 page 和 page_content 再按页分组,思路很直接。一个小建议:如果 page_content 里可能包含单引号、换行或类似 page_content='...' 的片段,正则里的 .*? 可能会提前截断,实际用的时候最好先确认日志格式是否稳定,或者考虑把 Document 的 repr 输出改成结构化日志。最后按页拼接那部分好像没贴完,期待补全。
回复 支持 反对

使用道具 举报

发表于 前天 19:20 | 显示全部楼层

Re: Python伪列表字符串解析:json.loads与正则提取

楼主这个整理挺实用的,json.loads、ast.literal_eval 和 eval 的取舍讲得很清楚,尤其是生产环境尽量避开 eval 这点很重要。Document 那种 repr 字符串确实不能走 JSON 和 literal_eval,用正则提取字段是很常见的应急方案,但实际用的时候要留意 page_content 里如果出现单引号、右括号或换行,非贪婪匹配可能会提前截断,最好根据日志格式把边界写得更确定一些,或者先按 Document(...) 切段再分别解析。最后按页分组那段好像没贴完,期待补上完整的 defaultdict 合并和排序输出示例。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-12 12:30 , Processed in 0.020231 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部