Python 解析“长得像 list 的 Document 字符串“:
Python 解析"长得像 list 的 Document 字符串":提取 page 并拼接(别再无脑 eval)
这是字符串转 list 系列的第 2 题。第 1 题里字符串是标准的 JSON / Python 列表,用
json.loads/ast.literal_eval就能解决。但这一题的字符串不是普通列表,而是一堆
Document(...)——它是 LangChain 里Document对象的字符串表示(repr):目标:取出每个 Document 的
page(页码),并把每页的page_content(正文)拼接起来。
一、为什么第 1 题的方法在这里全部失效
这个字符串里既有单引号,又有Document(metadata=..., page_content=...)这种函数调用写法,它既不是 JSON,也不是 Python 字面量:
所以第 1 题那套在这里直接报废。
二、最稳的解法:正则提取(不碰 eval,最安全)
既然字符串格式是固定的Document(metadata={...}, page_content='...'),我们用正则只解析结构,不执行任何代码。这样无论来源是否可信都很安全。
思路:
- 用正则找出每一段
Document(...),抓出它的metadata={...}和page_content='...';
- 从 metadata 里取
'page': 数字;
- 把每段的
page_content收集起来,按需拼接。
✅优点:纯正则、零执行、对任意来源都安全;不关心page_content里有什么奇怪字符,只认结构。
💡 小提示:如果原始字符串里的\n是"被转义过"的(\\n两个字符),提取出来的是字面量换行符。想要真正的换行,拼接前加一句:
三、拼接时想按"页码"排序怎么办
默认parse_documents是按字符串里出现的顺序返回的。如果你希望结果按page从小到大排:
四、如果你"完全掌控来源":用 eval 重建真实 Document
如果这段字符串确定是你自己代码print/repr出来的,绝对不会被外部篡改,也可以直接把它变回真正的Document对象,然后像平时一样用.metadata['page']访问:
⚠️为什么说"一般不推荐":eval会执行字符串里的任意 Python 代码。一旦这段字符串来自用户、接口、数据库等外部输入,别人塞一句__import__('os').system('rm -rf /')就能造成破坏。而且page_content里的转义字符(\n)会被eval当成真换行解析,行为取决于字符串来源,不可控。
五、正确的设计:别把 Document 存成 repr 字符串
Document的repr只是给人看的调试输出,不是稳定的序列化格式。如果你要持久化或传输 Documents,请用 JSON / pickle:
这样以后无论是取page、拼正文,还是存数据库,都规规矩矩,不用再跟repr字符串斗智斗勇。
六、三种方式对比
方式 | 安全性 | 是否需可信来源 | 适用场景 |
正则提取(推荐) | 高 | 否 | 只能拿到字符串、来源不可信或不想引依赖 |
| 低(代码注入) | 是 | 你 100% 信任这段 repr 字符串、临时调试 |
JSON 序列化 / 反序列化 | 高 | 否 | 从设计上就该这么存/传,根本不该出现 repr 字符串 |
七、总结
- 字符串里是
Document(metadata=..., page_content=...)这种repr,不是 JSON / 字面量,json.loads/ast.literal_eval都救不了;
- 首选正则提取:抓
metadata里的'page'和page_content,安全又稳;
- 需要真实
Document对象且来源可信时可用eval,但务必清楚它的注入风险;
- 治本办法:从一开始就用
json.dumps序列化 Document,而不是把repr当数据存。
一句话:拿 page、拼正文,用正则;想省事又安全,从源头改用 JSON。
