公司动态

字符串里扒数据:从“假 list“到“一堆 Document“的两种正确姿势

📅 2026/8/11 4:30:24
字符串里扒数据:从“假 list“到“一堆 Document“的两种正确姿势
字符串里扒数据从假 list到一堆 Document的两种正确姿势Python 数据处理实战笔记。明明看着像列表、像对象用起来却只是个字符串两道题教你安全、规范地把它变回真正的数据。我们常遇到这种场景日志、接口返回、LangChain 的Document对象被print或str()变成了一长串看起来像 Python 对象、但本质还是字符串的文本。直接当对象用会报错硬着头皮正则又容易翻车。下面两题给一套可复用的解法。第一题把像 list的字符串变成真正的 list输入就是一个字符串内容是[qwen-turbo,qwen-plus,deepseek]。❌ 错误直觉直接当 list 用它现在类型是str做for x in raw_str会一个字一个字地遍历而不是按元素遍历。✅ 正确做法一json.loads最推荐只要字符串是标准 JSON 格式双引号、合法的数组/对象用json.loads最稳importjson raw_str[qwen-turbo,qwen-plus,deepseek]resultjson.loads(raw_str)# result [qwen-turbo, qwen-plus, deepseek]类型已经是 list✅ 正确做法二ast.literal_eval更宽容字符串是Python 字面量风格比如用了单引号、或含True/None这类 JSON 不支持的写法时用ast.literal_eval。它只解析字面量不执行代码比eval安全得多importast raw_str[qwen-turbo,qwen-plus,deepseek]resultast.literal_eval(raw_str)# 同样是真正的 list且能识别单引号、True/False/None 等⚠️为什么不要用 evaleval(raw_str)确实能用但它会执行字符串里的任意代码。一旦这段字符串来自外部接口、文件、用户就等于敞开了一道任意代码执行后门。能用json.loads/ast.literal_eval就绝不用eval。✅结论标准 JSON →json.loadsPython 风格字面量 →ast.literal_eval两者都能把假 list变成真 list。第二题从一堆 Document(…) 里提取每个 page 并拼接内容这次不是普通列表而是一整串Document(metadata{...}, page_content...)被包在[ ]里。我们要拿到每个 Document 的page再把page_content按页拼接成长文。核心思路把它当成Python 表达式来解析而不是当文本硬切整段字符串其实是一个合法的 Python 表达式一个列表里面是多个对Document(...)的调用。我们用ast.parse(..., modeeval)解析它的语法树再逐层取出每个调用里的关键字参数最后用ast.literal_eval把metadata和page_content还原成真实数据。importast raw_docs[Document(metadata{pk: 12, page: 2}, page_content...), Document(metadata{pk: 27, page: 2}, page_content...), Document(metadata{pk: 14, page: 3}, page_content...)]defparse_documents(raw):treeast.parse(raw,modeeval)# 解析成语法树docs[]ifisinstance(tree.body,ast.List):foreltintree.body.elts:# 遍历列表里的每个 Document(...)ifisinstance(elt,ast.Call):d{}forkwinelt.keywords:# metadata / page_contentd[kw.arg]ast.literal_eval(kw.value)docs.append(d)returndocs docsparse_documents(raw_docs)拿到数据后做什么# 1) 提取每个 Document 的 pagepages[d[metadata][page]fordindocs]# pages [2, 2, 3]# 2) 按 page 排序拼接 page_content 还原成完整文档docs_sortedsorted(docs,keylambdad:d[metadata][page])full_text\n.join(d[page_content]fordindocs_sorted)# 3) 去重本例中第 2 页出现了两次(pk12 和 pk27)按 page 去重避免重复seen,uniqueset(),[]fordindocs_sorted:pd[metadata][page]ifpnotinseen:seen.add(p);unique.append(d)full_text_dedup\n.join(d[page_content]fordinunique)为什么要按 page 排序 去重真实场景里一个 PDF 可能被切成了多块、多份召回结果里同一页会重复出现。先按page排序保证正文顺序正确再按page去重拼接出的才是干净、连续、不重复的全文。备选正则适合结构极其规整的简单场景如果你只想快速拿到 page 数字正则一行就能解决importre pages[int(x)forxinre.findall(rpage:\s*(\d),raw_docs)]# [2, 2, 3]⚠️正则的坑一旦page_content里出现单引号、转义字符或嵌套结构正则很容易提前截断或匹配错位。所以——只要想拿完整内容优先用上面的ast方案正则只适合只取几个简单数字的临时查看。两题的共通心法场景本质推荐工具千万别用像 list 的字符串JSON / Python 字面量json.loads或ast.literal_evaleval一堆 Document(…)Python 表达式列表调用ast.parseast.literal_eval盲目正则 /eval✅ 一句话记住看到像代码的字符串先别急着切先用ast把它当代码解析——安全、准确、还能顺带拿到嵌套结构里的任何字段page、pk、content 随便取。