被LangChain的Document对象坑了3小时,我总结了两种解法,建议收藏 📅 2026/8/11 4:28:46 你是不是也遇到过这种情况明明只是想把PDF解析后的文本拼起来结果发现输出根本不是JSONjson.loads直接报错事情是这样的最近在做LangChain项目用PDF解析器把一份公司员工手册拆成了多个Document对象准备把内容按页码拼成完整文本。逻辑很简单对吧遍历列表、取page_content、拼接就完事了。但问题来了——输出长这样[Document(metadata{pk: 12, page: 2}, page_content2. 费用报销业务活动中产生的费用...), Document(metadata{pk: 27, page: 2}, page_content...), Document(metadata{pk: 14, page: 3}, page_content...)]乍一看像JSON仔细一看——Document()是Python类实例单引号字典\n转义符混杂其中。我第一反应是json.loads()结果JSONDecodeError: Expecting value: line 1 column 2 (char 1)报错了。因为JSON标准只认双引号、[]、{}根本不认识Document()这种Python对象写法。坑在哪里90%的人第一步就走错了很多人包括我第一反应是用正则硬拆或者eval()一把梭。千万别用eval()。eval()会执行任意Python代码如果字符串里有恶意内容你的系统就直接裸奔了。这不是技术风格问题是安全问题。解法一ast.literal_eval强烈推荐Python标准库里有个被严重低估的模块——ast。ast.literal_eval()可以安全地解析Python字面量字符串只认列表、字典、元组、字符串、数字这些基本类型不会执行任何代码。import ast from langchain.schema import Document # 原始字符串 raw_str [Document(metadata{pk: 12, page: 2}, page_content...)] # 一步到位安全解析 doc_list ast.literal_eval(raw_str) # 遍历提取 all_pages [] all_content for doc in doc_list: page_num doc.metadata[page] content doc.page_content all_pages.append(page_num) all_content content \n\n print(所有页码, all_pages) print(拼接文本\n, all_content)输出结果所有页码[2, 2, 3] 拼接文本 2. 费用报销业务活动中产生的费用... ...为什么推荐这个方案优势说明安全性不执行任意代码生产环境可用完整性得到完整的Document对象metadata、page_content随便取零依赖Python标准库不需要额外安装兼容性只要字符串是合法的Python字面量就能解析解法二正则提取轻量无依赖如果你的环境装不了LangChain或者只需要提取文本和页码不需要完整的Document对象正则也能搞定。import re raw_str [Document(metadata{pk: 12, page: 2}, page_content...)] # 提取页码 page_pattern re.compile(rpage: (\d)) page_nums [int(m.group(1)) for m in page_pattern.finditer(raw_str)] # 提取内容 content_pattern re.compile(rpage_content(.*?), re.S) content_list [m.group(1) for m in content_pattern.finditer(raw_str)] # 拼接 full_text \n\n.join(content_list) print(页码列表, page_nums) print(拼接全文\n, full_text)适用场景不想装LangChain的轻量项目只需要文本内容不关心metadata其他字段字符串格式不标准ast.literal_eval也解析不了时进阶技巧按页码去重仔细看上面的数据你会发现page2出现了两次pk12和pk27内容一模一样——这是PDF解析时常见的重复分块问题。直接拼接会导致内容重复解决办法是按页码去重page_content_map {} for doc in doc_list: p doc.metadata[page] if p not in page_content_map: page_content_map[p] doc.page_content # 按页码排序后拼接 sorted_text \n\n.join([ page_content_map[p] for p in sorted(page_content_map.keys()) ])这样每页只保留一份内容按顺序拼成完整文本干净利落。三种方案的对比场景推荐方案原因本地LangChain项目ast.literal_eval完整保留Document对象字段随取随用无第三方依赖环境正则提取零依赖轻量灵活有重复数据需要清洗ast.literal_eval 去重先解析再清洗逻辑最清晰写在最后LangChain的Document对象输出格式是个经典坑。很多人第一次遇到都会下意识用json.loads()然后碰壁。记住这个判断逻辑看到Document(metadata{...}, page_content...)这种格式 → 不是JSON → 用ast.literal_eval简单、安全、可靠。如果你也在做RAG项目、文档解析、知识库搭建这篇文章建议收藏。早晚用得上。你有遇到过类似的解析坑吗评论区聊聊你的解法。关注我持续分享LangChain实战中的踩坑经验和高效解法。