首页 / 资讯中心 / 文章详情

RAG数据导入实战:txt如何清洗切分成可控的Markdown

RAG数据导入实战:txt如何清洗切分成可控的Markdown ★ FEATURED ARTICLE
先讲一个很常见的现象RAG项目上线跑了一周用户反馈检索结果总是文不对题。很多人第一反应是换 embedding 模型加 reranker甚至怀疑大模型能力有问题。但我的排查顺序里第一刀永远砍在数据导入那一环——因为绝大多数“效果差”的根源不在检索和生成而在数据进知识库之前就已经被糟蹋了。这篇就聊 RAG 数据导入与解析里最基础也最容易被忽视的一段怎么把 txt 这类纯文本经过清洗、切分、结构标注最终变成 Markdown 格式的可控内容。不是讲理论是讲我实际跑过的处理流程、踩过的坑以及一套可以照着抄的通用方案。适合正在自建知识库、处理大量长文档、或者觉得 RAG 效果不稳定但不知道问题出在哪儿的开发者。1. 把数据喂给 RAG 之前先想明白三件事1.1 数据链路里真正决定成败的是这一段RAG 的完整链路大概是文档采集 → 解析与清洗 → 切分 → 向量化 → 检索 → 生成。多数人把精力花在模型选型和提示词调优上但这条链路的“上游水质”几乎决定了下游检索的天花板。为什么这么说embedding 模型做的只是“把一段文本映射成向量”它不负责理解“这段文本是不是一个完整的意思”。如果上游喂进来的文本是半截句子、乱序列表、混在一起的表格项模型再强也救不回来。检索阶段拿用户问题去匹配向量时匹配到的是一个语义不完整的碎片reranker 只能在这个碎片里挑相对相关的挑来挑去都是错的。所以数据导入这一步不是说把文件放进去就行而是要做“解析”——把不可控的原始文本改造成可以稳定消费的语义单元。我见过不少项目文档是PDF转出来的 txt里面带着页码、页眉、换行符、乱码符号直接拿来切分入库。最后检索出来的内容里全是半句话甚至还有“第x页共x页”这种噪音。用户问的是业务逻辑系统返回的是扫描残影效果自然一塌糊涂。1.2 三种文件来源“性格”完全不同不是所有 txt 都一样。我按来源把文本文件分成三类处理重点完全不同来源类型典型特征主要风险处理重点纯手工/编辑器生成的 txt格式规整段落分明信息密度低无层级语义块划分、标题补全从 PDF/Word 转换来的 txt有大量换行、页眉、目录噪音文本撕裂、乱码、表格错位清洗、段落合并、表格重建从网页/抓取内容保存的 txt带链接、标签残余、排版符号内容重复、噪声符号多去噪、提取正文、结构还原这三类文本如果都用同一个流程处理一定会有问题。我自己的习惯是第一步先做分类批量跑一个文件头检测脚本看看前 50 行里有没有“页眉-页码-页脚”这种文章碎片特征有没有“http://”“https://”链接残留。分好类之后再决定清洗正则的强度。这里有个容易被忽略的点txt 不一定是“无结构”的。很多 txt 内部其实有隐含结构比如空行分隔的段落、用“一、二、三”手写的章节号、用“”分隔的指标项。解析的目的就是把活页夹里这些隐性结构挖出来变成显性的 Markdown 标题、列表和表格。2. 格式信息没有“丢了可惜”一说关键看怎么保留2.1 为什么我坚持用 Markdown而不是 HTML 或纯文本有段时间我图省事直接拿清洗后的纯文本去切分。后来发现一个致命问题纯文本把所有格式信息都抺平了表格变成一行行散落的单元格标题和正文完全没有区分度。检索时用户问“2024年Q3的营收结构”系统匹配到的是一堆数字但不知道这些数字对应表格的哪一行哪一列。换 Markdown 之后相当于给文本加了“骨骼”。#代表一级章节|代表表格-代表列表项LLM 和 embedding 模型看到这些符号就能理解文本的内部结构。实测下来同样一批文档用 Markdown 保留结构再切分检索命中率比纯文本高出一截尤其在表格和层级标题这一类内容上提升最明显。有人会问那直接用 HTML 不是结构更丰富吗理论上是的但 HTML 噪音太大标签、样式、属性一大堆对 embedding 模型来说全是干扰项。而且很多切分工具对 HTML 的处理并不友好容易把标签和正文混在一起切。Markdown 的优势是轻量、人类可读、能被 LLM 直接理解又足够表达大多数文档的层次关系所以它在 RAG 数据管线里几乎是性价比最高的中间格式。2.2 三类信息三种保留策略解析 txt 的时候我会把文本里的信息分成三类分别处理。第一类是语义块就是一段完整描述某个主题的文字。这类信息核心是“完整”不做切割保留段落边界。比如一段介绍业务背景的段落应该整体作为一个单元用空行或标题把它与前后文区分开。第二类是格式标记包括标题层级、列表顺序、表格行列。这类信息的价值在于表达“关系”——谁是上级、谁是下级、哪些数据是一组。处理策略是转换成 Markdown 对应的语法让关系显性化。举个例子原来 txt 里写的一、项目背景 1.1 业务痛点 用户反馈检索准确率低 数据分散在多个系统转成 Markdown 就是# 项目背景 ## 业务痛点 - 用户反馈检索准确率低 - 数据分散在多个系统这种转换看着简单但对后续切分的影响非常大。切成 chunk 的时候有了标题层级就可以把一级标题作为上下文前缀跟每个子块捆绑检索时模型至少知道“这段话属于项目背景下的业务痛点”而不是一段无头无尾的散装文字。第三类是关系数据比如表格里的指标、键值对、人员名单等。这类信息最容易在被拍平成纯文本后丢失关系。处理策略是尽量保留为 Markdown 表格或者转成结构化的键值描述。比如这一段原始内容张三 项目负责人 2024-01 李四 技术负责人 2024-03如果直接清洗成纯文本切分后检索时模型根本分不清谁是谁。转成表格之后姓名角色入职时间张三项目负责人2024-01李四技术负责人2024-03检索“项目负责人是谁”时命中的 chunk 里直接带出完整表格结构答案就有了。这里我的原则是语义优先格式辅助。不要为了“结构化”而把每个段落都拆成列表那会让文本变得碎片化。该是段落的保持段落该是表格的才转表格两类信息的处理逻辑完全不同。3. txt 到 Markdown 的解析落地我常用的六步流程3.1 第一步给文件分类别让 txt、md、csv 混杂入库我在前面说过分类的重要性这一步实际执行起来很简单。我一般会在项目目录下建一个input文件夹把来源混杂的文件丢进去然后跑一个简单的分类脚本按扩展名和文件头部内容把文件分成plain手工纯文本、pdf-converted从 PDF 转出来的、web-dump网页保存的三类。分类之后还要做一件事全量查看几个代表文件的前 100 行。这一步千万别跳因为清洗规则的制定必须基于真实数据不能凭空想象。你看到的 txt 长什么样决定了下一条正则该怎么写。3.2 第二步全量清洗正则替换是主力清洗环节我主要用正则表达式工具上 VSCode 的全局搜索替换就足够没必要为了清洗单独写一套工具。先把这几类噪音处理掉多余的换行符PDF 转换来的文本经常每个物理行一个换行但语义段落根本没换。处理方式是先把所有换行统一成\n再根据句末标点判断是否合并。页眉页脚和页码形如“第 x 页 / 共 x 页”、“公司内部资料”这类重复出现的行直接删除。全角半角混乱中文场景特别常见全角括号、逗号、空格混杂统一转换。特殊符号残留PDF 转换常见的就是“□”“■”“”这类乱码字符清洗掉避免污染 embedding。一个比较通用的正则顺序是这样# 合并仅有一个换行且前后不是段落边界的情况先做基础清洗 # 删除页眉页脚页码 /^第\s*\d\s*页[,、]?\s*共\s*\d\s*页.*$/gm /^共\s*\d\s*页$/gm # 删除网址与邮箱对 web-dump 类文件至关重要 /https?:\/\/[^\s]/g /[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,}/g # 全角转半角只处理符号中文内容不受影响 /\uFF00-\uFFEF/g清洗完一定抽几段人工确认看看有没有把正常内容误删。这类工具操作看着简单翻车次数一点不少宁可多查几遍再进下一步。3.3 第三步语义块识别从“按字数硬切”转向“边界软切”清洗完的文本还是一整块第二步是识别语义块的边界。常见的边界信号有三个空行、章节标题、主题词。空行是最直观的信号两个空行之间通常是一个完整段落。章节标题的判断可以靠正则匹配“第x章”“一、”“1.1”“##”这类模式。主题词信号更像是辅助比如列表里连续出现的“”结尾短句往往意味着一个统计指标的描述。这一步我最想强调的是不要一上来就按固定字数切分。固定字数切分是省事但代价是切出来的 chunk 里经常横跨两个主题检索时就出现了“匹配到了但答非所问”的现象。我现在的做法是“软切”先按语义边界切出粗块比如每个二级标题下的整段内容作为一个粗块再检查粗块长度。如果超过设定上限再在内部按空行或句号做二次分割。3.4 第四步结构标注把层级和关系写进文本语义块识别做完就要把前面说的格式标记转换成 Markdown。这一步有两种做法手动和半自动。手动做法适合文档量不大、但内容质量要求高的场景。比如把“一、二、三”的章节标题手动改成#和##把“”分隔的指标项改成表格。半自动做法是我实际用得更多的先用一个转换脚本把明显的层级标题、列表符号识别出来批量替换成 Markdown 语法再人工检查一遍。一个典型的批量替换逻辑长这样import re def txt_to_markdown_structure(text): # 把 一、xxx 改成一级标题 text re.sub(r^[一二三四五六七八九十]、(.)$, r# \1, text, flagsre.MULTILINE) # 把 1.1 xxx 改成二级标题 text re.sub(r^(\d\.\d)\s(.)$, r## \2, text, flagsre.MULTILINE) # 把 - xxx 或 * xxx 保留为列表 text re.sub(r^[-*]\s(.)$, r- \1, text, flagsre.MULTILINE) return text这个脚本只处理显性结构处理完一定人工过一眼。因为现实里的文档不会严格按这个模式写总有个别例外比如条款编号“1.2.3”不能被误判成二级标题序号列表“1、 2、”和标题“1.1”要区分开。3.5 第五步切分参数给 embedding 模型留够上下文结构标注完成之后才轮到切分策略。我常用的参数逻辑是chunk_size 在 800 字符左右overlap 留 10% 到 15%。这个值不是拍脑袋定的中文一个 token 大约对应 0.6 个汉字800 字符大约是 800 到 1200 token适配多数 embedding 模型的输入上限又能保留足够上下文。overlap 的作用是防止语义断裂。比如一个 chunk 结尾正好落在某个句子的中间下一个 chunk 的开头从后半句开始如果没有重叠检索时两个 chunk 都对不上完整语义。设了 overlap就能让前后 chunk 之间有交叉地带代价是向量数量增加、存储成本略涨。我实际用下来10% 的 overlap 对检索召回率的提升已经足够再往上加收益很小。还有一个细节切分时不要把标题单独切出去。很多工具默认把标题作为一个独立的块这样做检索时模型拿不到标题的上下文。我的做法是把一级标题作为每个 chunk 的前缀跟着内容一起切。比如“# 项目背景”这个标题出现在它下面所有 chunk 的前部。这样检索时每个 chunk 都带着自己的“出身”语义定位清楚很多。3.6 第六步质检抽几段看看“可读性”最后一步是抽检。入库前我把切分好的 chunk 随机抽个 10%按三条标准检查有没有半句残文、有没有表格被拍平、有没有噪音字符残留。半句残文最典型的表现是 chunk 以“的”“了”“并且”这类词结尾说明切分点没落在语义边界上。表格拍平的表现是 chunk 里出现一串数字但没有表头说明表格结构没被识别。噪音字符就是清洗环节漏掉的那类往往是特殊中文标点。抽检这一步看着笨但它是防止“批量处理事故”的最后一道闸。我遇到过一次全量入库后才发现某个文件夹的文档页眉没清干净几百个 chunk 里都带着“内部资料请勿外传”检索时这些 chunk 严重干扰相关性排序最后只能重跑清洗流程浪费时间也浪费向量存储。4. 实操演示会议纪要从 txt 变成可检索的 Markdown4.1 拿到手的 txt 长什么样讲理论不如走一遍。我拿一个典型的会议纪要 txt 做例子原始内容是这样的会议纪要Q3产品规划评审 时间2024年9月15日 参会人张三 李四 王五 议题一智能客服模块上线计划 张三预计10月15日完成开发 李四需要提前确认API接口文档 议题二知识库检索优化 王五当前RAG检索准确率87%目标92% 待办事项 1. 张三 完成接口文档 9月20日 2. 王五 提交优化方案 9月25日 备注下次评审时间10月10日这份 txt 看起来信息完整但直接切分入库的话问题很明显整个内容没有层级“议题”“待办”全是平铺的表格信息参会人、时间这类键值对被拍平切分时如果按 800 字符切前半段和后半段会混在一起。4.2 六步流程走一遍我来走一遍前文的流程。清洗环节这份 txt 没有页眉页码最大的问题是没有段落空行所有内容挤在一起。先按语义补空行把“议题”“待办”“备注”这些区块分开。语义块识别时能明显看出三个区块会议基础信息、两个议题的讨论内容、待办事项。这三个区块性质不同第一个适合转成表格第二个适合保留段落加标题第三个适合转成列表。结构标注就是把层级显性化# 会议纪要Q3产品规划评审 ## 会议信息 | 项目 | 内容 | |------|------| | 时间 | 2024年9月15日 | | 参会人 | 张三、李四、王五 | ## 议题一智能客服模块上线计划 - 张三预计10月15日完成开发 - 李四需要提前确认API接口文档 ## 议题二知识库检索优化 王五当前RAG检索准确率87%目标92% ## 待办事项 1. 张三完成接口文档9月20日 2. 王五提交优化方案9月25日 ## 备注 下次评审时间10月10日这份会议纪要总共不过几百字但转成 Markdown 后每个语义块都独立且自洽。检索“智能客服开发完成时间”时命中“议题一”整个块模型能看到完整的讨论上下文。如果还是原始 txt 的平面结构命中点可能落在“张三预计10月15日完成开发”这一行上既没有议题标题也没有参会人的身份信息回答时大概率只能复述这一句话。4.3 入库时我额外做的两件事这份纪要转化成 Markdown 之后入库前我还会做两件事。第一件是给文档加 front matter 元数据。在 Markdown 最顶部用 YAML 格式写清楚文档的 title、date、source、type入库时这些字段可以直接作为附加的 metadata 字段存进向量库。检索时如果向量数据库支持元数据过滤就可以实现“只搜某类文档”或者“只搜某个月份的纪要”在很多业务场景下非常实用。第二件是确保表格在切分时不被拆散。Markdown 表格一旦跨 chunk检索时就只剩表头或者只剩几行数据关系就断了。我的做法是切分时把表格识别为不可分割块整个表格作为一个 chunk 的一部分。实际操作中可以在切分逻辑里加一条判断如果当前 chunk 遇到|开头且连续的行就强制留到下一个 chunk。入库配置上我通常用的是轻量本地方案先转成 JSON 行格式每条记录包含 text 和 metadata 两个字段text 是切分好的 Markdown 内容metadata 是标题、来源、页码等附加信息。这样无论后端接哪个向量库导入脚本都可以复用不会绑死在某个框架上。5. 常见问题排查与避坑笔记5.1 我踩过的坑按“印象深刻程度”排序第一个坑是“表格被踩平”。有一批销冠数据类的 txt里面全是销售业绩表我当初图省事直接跑了纯文本切分没做表格识别。入库后检索“华南区Q3业绩”系统返回的 chunk 里只有一堆“120万”“98万”的数字没有任何区域和季度的关联信息。后来回去补做了表格识别把行列关系转成 Markdown 表格检索质量才起来。第二个坑是“标题重复污染”。有一份长文档一级标题出现在每个二级标题上方切分时我又把标题作为前缀跟每个 chunk 捆在一起。结果所有 chunk 都带上了同一个一级标题检索时这个一级标题的权重被放大导致不同章节的内容都被同一个标题“拉偏”。后来的做法是一级标题只出现在它管辖范围内的 chunk 前部跨章节时重新计算不搞无限前挂。第三个坑是“清洗过度”。我早期处理一批含代码示例的 txt为了让文本更“干净”把符号基本都删了结果代码块的缩进、箭头、花括号全没了代码变成一坨乱文。后来专门调整清洗规则先判断文本块是否属于代码示例代码块内部只处理明显的乱码保留基础符号。5.2 问题排查速查表整理一个速查表放在这里遇到问题直接对照排查症状可能原因排查路径检索结果全是半句话切分边界落在句中了检查 chunk 尾部是否以连接词/标点结束表格检索时丢失列关系表格被拍平成纯文本检查 chunk 中是否有 大量 chunk 内容重复清洗时没去除页眉页脚检查 chunk 首尾是否有重复段落标题信息没体现在检索中切分时标题被独立成块检查标题是否作为前缀跟随内容同一主题被切散在多个 chunk固定长度切分跨越主题边界改为语义边界切分按空行/标题划分特殊符号污染向量清洗正则漏掉全角符号检查 chunk 是否含乱码字符每个症状对应的处理方案都在前面的章节里展开过。实际上这些坑大多数在“第六步质检”里就能被发现问题是很多项目根本没做这一步上了生产才暴露。我现在的习惯是任何一批新文档入库先挑 10 个文件走完整流程质检全绿之后再放量批量处理。5.3 没工具时的保底方案半自动脚本加人工兜底有些读者会说团队没有专门的解析平台只有一台电脑和一批 txt怎么办我的保底方案一直很简单VSCode 全局搜索替换加多光标编辑配合一个小型转换脚本最后人工抽检。工具层面不需要复杂设施真正花时间的是“清洗规则的制定”和“语义块边界的确认”这两件事都是经验活不是工具能替代的。一个实用的小技巧是善用 VSCode 的正则替换。比如批量把一、开头的行转成#标题用多光标同时选中多处待办列表行前插入-。对于一次性文本量在几十个文件以内的情况手动加正则的组合拳比写完整解析程序更快更稳。Python 脚本方面我会写一个辅助函数做批量批注但重点是辅助而不是全自动# 辅助脚本批量给 Markdown 标题加层级前缀 # 只做机械的事语义判断交给人工 import re def promote_headings(text_lines): lines [] for line in text_lines: if re.match(r^#[^#], line): # 一级标题保持不变 lines.append(line) elif re.match(r^##[^#], line): # 二级标题前面补一级标题占位符 lines.append(# 章节上下文\n line) else: lines.append(line) return lines这个脚本的作用不是自动判断语义而是把机械的“标题层级补全”工作批量做掉。真正的语义判断——哪个一级标题该配上哪段内容——始终需要人过一遍。我一直觉得原始文本的解析处理过度自动化反而容易出问题关键环节做“半自动加人工确认”才是稳的。最后再分享一个小经验做 RAG 数据导入这段时间我最大的体会是手动干一次比调一万次参数更有价值。这不是喊口号是真的经历过——当我把一批原始 txt 亲手清洗、标注、切分成 Markdown 之后再入库我对这批数据能检索出什么、检索不出什么心里是有底的。后续调模型、调提示词都有明确的对照。这篇写的六步流程是我现在处理文本类文档的固定动作不同项目的文档格式可能不同但“分类 - 清洗 - 识别语义块 - 结构标注 - 切分 - 质检”这条主线基本通用。下一步我可以再展开聊聊表格类文档的结构化解析那又是另一层麻烦等实操积累够了再继续写。
阅读完成 · 觉得有帮助?
咨询建站