
1. RAG 数据导入为什么要从“解析”这一关开始较劲做 RAG 项目的人十有八九都经历过这样的场景知识库搭好了、向量库连通了、大模型也调通了结果一问一个不吱声。查来查去问题不在检索不在生成而在一开始的导入环节——原始文档压根儿没被正确地“读进去”。我最早做 RAG 时也犯过这个错误拿现成的解析库把 PDF、Word 一股脑往里塞向量化之后跑起来召回率看着还行可一旦细问某个具体条款模型就开始东拉西扯。后来我把中间解析产物打出来一看发现很多段落被截得七零八落标题层级完全丢失原本属于同一章节的内容被拆得到处都是。那个瞬间我才意识到RAG 这条链路上真正决定系统天花板高度的不是模型选得有多强而是数据进库之前那一步解析做得有多细。这个系列的第一篇我先聚焦最简单也最容易被轻视的两种格式纯文本 txt 和结构化文本 Markdown。别觉得它们“太基础”恰恰因为格式简单很多人反而懒得好好处理直接用默认参数切一遍就完事。但 txt 和 Markdown 恰好是 RAG 项目中语料最常以原始形态存在的两种格式而且它们的解析思路直接决定了后续处理 JSON、HTML甚至 PDF 转 Markdown 时的整体框架。这里有个很核心的认知需要先纠正解析不是为了“读出来”而是为了给后续的分块、向量化、召回提供一份高质量的结构化中间表示。如果你只是把整本小说当成一个超长字符串塞给 Embedding 模型那 RAG 的检索精度永远上不去。解析要解决的是两个问题一是文本本身的编码与切分二是文本内部的逻辑结构怎么被显式地保留下来。市面上很多教程喜欢一上来就讲 LangChain 的 TextSplitter 怎么配参数或者 LlamaIndex 的 Reader 怎么用但很少有人讲清楚背后的“为什么”。我这篇会从原理和实操两头一起讲把从 txt 到 Markdown 这条解析链路上该想的、该避的、该调的都捋一遍给正在搭 RAG 的朋友一套可以直接拿去用的思路。2. txt 文本解析的三个层次编码识别、分块策略与语义边界2.1 编码识别第一道暗坑txt 文件最大的特点是没有元信息它不像 PDF 那样自带字体和版面信息也不像 HTML 那样有标签可以提取。拿到一个 txt你首先面对的就是编码问题。UTF-8、GBK、GB18030、BIG5甚至 UTF-16识别错了就是满屏乱码直接导致后续所有环节全盘崩溃。我处理过一批从旧业务系统里导出的 CSV 转 txt 数据表面上看是简体中文实际上混着 GBK 和 GB18030 两种编码用 Python 的chardet做自动检测准确率不稳定偶尔会把 GB18030 文件误判成 KOI8-R那叫一个离谱。后来我改用charset-normalizer明显稳健很多再配合“检测结果置信度低于阈值就按 UTF-8 处理”的兜底逻辑才算把这道坑填平。实际项目里我建议不要只依赖单一策略而是三层并行判断BOM 头优先、编码检测库兜底、启发式规则补位。如果文件开头有 UTF-8 BOM直接按 UTF-8 读如果没有 BOM用charset-normalizer跑一遍置信度高于 0.8 就用检测结果低于 0.8 的话尝试 GB18030 和 GBK 解码看哪个不抛异常且乱码率更低。提示GB18030 是 GBK 的超集支持所有 Unicode 字符所以只要不是纯繁体中文环境gb18030通常比gbk更安全。纯繁体场景优先试big5。2.2 分块策略固定长度、递归切分与语义切分的取舍编码问题解决之后真正的重头戏是分块。文本分块这个操作表面上是“把长文本切短”实际上是在找一个平衡点块太小单块语义不完整向量化之后检索不到关键信息块太大塞进上下文窗口的成本高同时多主题混在一起会稀释向量表征的聚焦度。目前主流的分块策略有三类我用一张表把它们的特性对比一下策略原理适合场景需要警惕的问题固定长度切分按字符数或 Token 数硬切日志、代码、规则统一的结构化记录语义被拦腰截断一句话可能分到两段递归字符切分按分隔符优先级逐层切分通用文档、网页正文、混合内容依赖分隔符质量特殊文本可能切出无意义块语义切分按句子向量相似度或主题边界聚合长文、教材、论文、小说计算成本高边界不稳定需要后处理LangChain 里默认的RecursiveCharacterTextSplitter本质上就是第二种思路它按从大到小的分隔符优先级依次尝试\n\n、\n、。、、、句号、空格最终确保块大小不超过上限。这套思路的优点是通用性强缺点是它根本不懂语义面对没有明显分段符的 txt 时切出来的边界往往不理想。第三种语义切分听起来高大上但工程上落地成本不低。我在一个法律文档项目里实验过用 SBERT 对句子做聚类来定边界效果不错但处理一本书几千个句子光算相似度矩阵就要几分钟而且边界还会因为 Embedding 模型的更新而漂移。所以我目前的看法是语义切分适合做离线精处理不适合做通用型导入管线的默认方案。2.3 分块参数怎么定chunk_size 与 overlap 的经验值这一节直接给干货是我试过几十组参数之后沉淀下来的经验值。先说chunk_size的基准。先明确你的 Embedding 模型最大输入长度。OpenAI 的text-embedding-3-small是 8191 token开源常见的bge-large-zh是 512 token。主流说法是“不超过模型上限的 80%”但真正影响召回效果的其实是“单块语义密度”。我的经验是中文场景下chunk_size 设在 300500 个汉字之间检索效果最好。为什么因为中文一个汉字大约对应 0.60.8 个 token300500 汉字约等于 200400 token既能承载一个完整论点又不会拖慢检索速度。如果你用的是英文文档可以适当放大到 8001200 token因为英文按空格切分后 token 边界更自然。再说overlap。这个参数常被忽略但它直接决定跨块语义的延续性。我建议设置 chunk_size 的10%15%作为 overlap。比如 chunk_size 取 400 汉字overlap 取 5060 汉字也就是切完第一段之后下一段从前一段末尾的 50 字处开始。这样处理的好处是位于切分点附近的实体和关系不会因为硬切而丢失。有个进阶细节值得单独强调overlap 应该按“语义单元”对齐而不是硬按字符数对齐。最简单可靠的做法是在切分时不要取“前 N 个字符”而是取“上一个完整句子结束后的前 N 个字符”。具体实现就是在代码里向后多取一段再回退到最近的句号或换行符作为下一段的起点。这个小技巧能显著减少切分点正好落在长句中间的情况。2.4 语义边界识别的一些实用技巧纯粹的字符级分块满足不了高质量 RAG 的需求因为文本是有结构的结构本身就是语义的一部分。我处理小说和学术文献时用过一个很有效的技巧先标记“自然段边界”再在边界基础上做合并。具体做法分四步把 txt 按\r\n或\n拆分成行然后合并相邻的空行得到一个“段落列表”。识别段落级别特征段落首行缩进、章节标题模式如“第一章”“第 1 节”“Chapter 1”、数字编号列表等。计算每个段落的字符数将过短的段落标记为“可能属于上一段或下一段的延续”。以“章节标题”作为强分隔符先按章节切大块再在章节内部按段落合并成 300500 字的子块。这套流程的本质是把“分块”从纯长度问题升级为“边界问题”。一旦边界划对了后面向量化的质量会明显提升。我在处理一份几十万字的网文 txt 时验证过这套流程用段落边界感知的分块比纯递归字符切分命中率提升了不少。具体数据不方便贴但可以告诉大家一个直观感受检索结果从“大概相关”变成了“精准对应到指定角色的某段心理描写”这就是边界的力量。3. 从 txt 到 Markdown结构化索引才是 RAG 召回率的分水岭3.1 为什么是 Markdown而不是 HTML 或 PDF很多人会问既然都要做结构化解析为什么不直接上 HTML 或 PDF我的回答是Markdown 是“语义密度与解析成本的最佳折中点”。HTML 虽然标签完整但标签里夹杂着大量样式信息class、style、id解析时得做一层噪音清洗不然div套div能把向量化的注意力全部吸走。PDF 则更麻烦它本质上是排版文件段落顺序需要靠坐标推断表格和图片直接是图元不跑 OCR 或版面分析根本拿不出干净的文本流。而 Markdown 用少量标记就能表达标题、列表、表格、引用、代码块这些语义结构解析成本低Embedding 模型对它的理解也友好。更关键的一点是Markdown 本身已经是 RAG 生态的事实标准。很多前沿的 Agent 工具链都支持把网页正文一键转 MarkdownGitHub 上的主流文档几乎全是 Markdown 写的。把 txt 转成 Markdown本质上是把“无结构裸文本”升级成“带语义锚点的结构文本”这一步升级之后分块质量、检索精度、甚至大模型读上下文时的理解效率都会同步提升。3.2 用标题做层级锚点让向量检索“认路”Markdown 结构化最有价值的点就是用#、##、###把文本的层级树显式画出来。对于 RAG 来说标题不只是排版工具更是天然的“锚点”。为什么这很重要因为向量检索是按“语义相似度”匹配的它不知道“第三章第二节”和“第三章第一节”之间有并列关系。但如果每个分块上都带上标题路径比如### 3.2 RAG 系统中数据解析的常见误区检索时就算用户只问了某个细节向量表征里也天然包含了“这是在第 3 章、第 2 节”这个上下文。这比纯文本切块凭空多了一个“章节维度”的信号。实操中我强烈建议转 Markdown 时不要只保留标题那一行的文本而是把标题路径拼接进每个分块的头部。例如一个分块原本是“什么是向量数据库向量数据库是一种……”转换成 Markdown 分块后应该变成## RAG 基础概念 ### 什么是向量数据库 向量数据库是一种……这样每个分块在向量化时都会被加上“RAG 基础概念什么是向量数据库”的前缀信息检索时就算用户的问题里没出现原文中的词汇也能通过层级标题的语义搭上线。这个技巧实现成本低收益却非常直接。3.3 列表、表格、引用块的保真转换从 txt 到 Markdown最容易被粗暴处理掉的就是列表和表格。原因很简单txt 里没有真正的“表格”概念很多表格其实是用制表符或空格对齐的“伪表格”。而制表符对齐的文本如果直接按普通文本分块表格的一行会被拆进两个不同的块语义联系直接断裂。我的处理原则是能用正则识别出表格骨架的就转成 Markdown 表格识别不出来的至少把表格整体作为一个独立块禁止跨表格切分。具体实现上我维护了一个“表格边界检测器”遇到连续多行包含|、、-或制表符的行先判断它们是否为对齐结构是则整体打包再按|或制表符切分单元格最后输出为 Markdown 表格。列表的处理相对简单但也容易出问题。txt 里常见的列表形式是“1. 内容”或“- 内容”。转 Markdown 时注意保持嵌套层级识别缩进长度。核心思路是缩进越多列表层级越深。这里有个容易忽略的细节如果列表项文本太长建议把整个列表作为一个“列表块”不要把一个列表项单独切成一块否则检索到某一项时会丢失它前后项的语境信息。引用块blockquote在 txt 里通常表现为以开头的行。转 Markdown 时保留前缀即可。引用内容里的关键信息往往是用户决策依据保留其引用形式有助于区分“本文观点”和“外部引文”对 RAG 的回答可靠性很有帮助。3.4 一个可复现的最小实现思路说了这么多给一个可以直接参考的最小实现框架用 Python 描述核心逻辑约 60 行就能跑通从 txt 到 Markdown 的主流程import re from charset_normalizer import from_path def detect_encoding(file_path: str) - str: result from_path(file_path).best() return result.encoding if result else utf-8 def txt_to_markdown(text: str) - str: lines text.splitlines() md_lines [] in_code_block False table_buf [] def flush_table(): if not table_buf: return # 这里简化处理每行按制表符或竖线切分直接输出 md 表格骨架 md [| | .join(table_buf[0]) |, | | .join([---] * len(table_buf[0])) |] for row in table_buf[1:]: md.append(| | .join(row) |) table_buf.clear() return \n.join(md) \n for line in lines: stripped line.strip() # 代码块检测连续缩进或特定标记 if stripped.startswith(): in_code_block not in_code_block md_lines.append(line) continue if in_code_block: md_lines.append(line) continue # 表格行检测制表符或多空格分隔 if re.match(r^(\S\t|\S\s{2,})\S, line) and not stripped.startswith((#, -, )): cells re.split(r\t|\s{2,}, stripped) table_buf.append(cells) continue md_lines.append(flush_table(), end) # 略 return \n.join(md_lines)代码只是骨架真实项目里还需要处理标题层级识别、编码异常回退、超长行保护等问题。这个框架的意义在于它把所有 txt 解析的共性步骤集中到了一起编码检测、行切分、块类型识别、Markdown 组装。你可以在此基础上按项目需要挂载不同的解析器。4. 我踩过的坑表格、公式、代码块与图片“隐形丢失”4.1 表格在分块时被腰斩这是我踩过最疼的坑之一。当时处理一批金融公告文本大量使用制表符对齐的表格。用通用分块器一跑某个表格被从中间切开上半部分进了上一个块下半部分进了下一个块。结果用户问“某基金的管理费率、托管费率和销售服务费分别是多少”检索模型只召回了上半块下半块的关键数据直接丢失大模型编了一个数字出来。这个问题让我形成了一个铁律分块器必须感知表格边界遇到表格块时整体作为一个分块单元除非表格超过单块上限才按行拆成“表头 分段行”。按行拆的时候也要保证表头跟随每一段因为 Markdown 表格的表头是语义的一部分。处理“宽度过大的表格”还有一种思路在转 Markdown 时把表格拆成纵向分片每片保留表头列后续检索到任一片都能定位到原表的具体位置。这在 RAG 场景里尤其有用因为大模型读取长表格很容易晕纵向分片反而帮它缩小了注意力范围。4.2 数学公式从 txt 到 Markdown 的转写学术类文档里公式是重灾区。纯文本形式下公式可能长这样E mc^2或者x_1 y_2 z。有些激进的做法是直接当普通文本切分结果公式被切开语义就毁了。我的建议分两步走。第一步用正则识别公式区域例如以$包裹的文本、以\[和\]包围的块、或者连续的数学符号密集区域。第二步把识别到的公式转成 LaTeX 风格或不转义保留原文但强制标记为“公式块”在分块时不被切分。Markdown 本身支持数学公式插件社区里也有很多 Markdown 数学公式插件可以把$包裹的内容渲染成公式。所以在数据导入阶段就做好公式边界标记未来在前端渲染或模型阅读阶段就拥有了公式结构。这里有个容易忽视的问题部分公式字符和 Markdown 语法冲突。比如下划线_在 Markdown 里是斜体标记x_1会被错误渲染成斜体。解决方案有两种一是把公式统一用$...$包裹二是在公式块内关闭 Markdown 转义规则。后者工程上更难实现建议直接用前者。4.3 代码块在 RAG 检索中的“积木”问题技术类文档里代码块是重要资产但代码块天然不适合按字符分块。原因有二第一代码块经常跨行表达一个完整的逻辑单元比如一个函数定义连带注释第二代码的向量化语义和自然语言差异巨大模型对代码的理解方式更接近结构比对而不是语义匹配。我在处理 GitHub 类文档时总结出一套规则代码块按“逻辑缩进层级”而不是字符数切分。具体来说以函数定义def、function关键词作为强边界以空行作为弱边界尽量保证每个代码块都从一个完整的语义单元开始在一个完整语义单元结束。另一个经验是代码块内部不要套用任何语义切分模型直接原样保留。你的目标不是让代码块变得自然语言友好而是让代码块整体作为一个可检索的“结构罐头”。用户问“这个项目的接口是怎么定义的”检索到的是一个完整的接口定义块而不是半截代码。4.4 RAG 知识库到底能不能存图片——一个被反复问的问题这个系列是讲 txt 和 Markdown 的但每次聊 RAG 数据导入总有人问同一个问题RAG 知识库能存储图片吗这个问题的答案直接关系到“从 txt 到 Markdown”这一步的设计走向。如果你在 Markdown 里保留了图片引用那图本身并没有消失关键是你后续怎么处理它。现状是纯文本型 RAG 不“理解”图片内容但可以保留图片元信息与引用位置。也就是说图片的语义需要依靠两种方案之一来进入向量库多模态 Embedding 模型将图片直接映射为向量。适合图片本身就是核心内容的场景比如设计稿、架构图、产品截图。图片转描述文本用视觉语言模型把图“翻译”成文字描述再把描述当作普通文本块入库。适合图片起辅助说明作用的场景比如流程图、表格截图。实操中我的建议是做一个两级方案先用多模态模型给图片生成一段描述文本再把描述文本嵌入向量库同时保留原图路径。检索时如果命中了某图片对应的描述块可以顺带返回图片路径供前端展示。这样既保留了图片内容又没有把检索架构搞复杂。从 txt 到 Markdown 这一步重点就是给图片预留“锚点”把这个 Markdown 语法写对后续无论走哪条图片语义化路线都方便定位。5. 一套可以直接落地的通用解析链路与工具选型5.1 工具链的对比与选择市面上解析工具多如牛毛但真正靠谱且适合 RAG 导入管线的其实就那么几个。我把它们按定位分成三档方便大家按项目需求选。第一档轻量级文本处理库charset-normalizer编码检测首选比chardet准。python-frontmatter处理带 YAML 头部的 Markdown 文件很方便。markdownifyHTML 转 Markdown常用于网页正文抓取。第二档重量级文档解析框架unstructured目前开源生态里做文档结构化最积极的库之一支持 txt、HTML、PDF、Word能输出带元素类型的中间表示。LlamaIndex的LlamaParse偏 SaaS 化解析干净适合预算充足的项目。LangChain的UnstructuredFileLoader胜在生态整合但深度不够。第三档自制管线当上面的工具都不能满足需求时就该考虑自制管线了。自制不代表从零造轮子而是基于第一档库加上自己的业务规则。我的经验是RAG 解析管线的核心价值在于你的业务规则而不在于用了哪个知名库。比如处理法律文书时“第X条”是强结构化标记处理网文时“第一卷第X章”是强边界。这些规则通用库不会懂只有你的业务才懂。5.2 管线设计的三个关键节点基于我的实操经验一条通用 RAG 导入管线至少要包含三个关键节点编码归一、结构识别、质量校验。我把每个节点的设计要点拆开来说。编码归一节点所有入库文件无论原始格式是什么先统一解码为 UTF-8 文本流。这一步看起来简单但必须放在最前面。很多后续解析工具的坑根源都是前一步编码没处理好。结构识别节点这是整个管线的大脑。识别文本中的标题、列表、表格、引用、代码块、公式并输出带结构标记的中间表示。我的建议是在这个阶段不要急着向量化而是先输出一份“结构 JSON 预览”人工抽查几个文件确认结构识别是否符合预期再进入向量化流程。这个“人工抽查”步骤能省下你后期调试召回问题的大量时间。质量校验节点解析完之后自动检查每个分块的字符数、是否为空块、是否出现乱码字符、是否包含截断的 Markdown 标记。我写过一个三行正则就能捕获大部分异常未闭合的代码块、孤立的表格分割行、以非 ASCII 字符开头却包含乱码的部分。质量校验从来不是“锦上添花”而是“防止脏数据进入向量库”的最后一道防线。5.3 对“文档结构化解析”的进一步思考向量化之前的最后一公里聊完管线我想再把视角拉高一点。很多人以为结构化解析就是把文本转成 Markdown 就完事了但真正决定 RAG 质量的是“结构化解析的质量指标”是否可衡量。我建议每一个 RAG 项目都建立一套解析质量指标至少包括指标计算方法理想范围分块完整率完整分块数 / 总分块数95% 以上标题覆盖度检测到的标题数 / 人工标注标题数90% 以上表格完整率保留完整表格数 / 原始表格总数100%乱码率含替换字符的块数 / 总分块数接近于 0这些指标不需要做得很复杂关键是能够量化地告诉你“解析结果是否变好了”。我见过太多项目在调检索模型参数上花大力气却对解析质量一无所知这是本末倒置。还有一个值得注意的点从 txt 到 Markdown 的结构化解析应该和后续的“知识图谱抽取”ontology、KG 相关衔接起来。现在有很多项目在 RAG 基础上叠加知识图谱而图谱抽取的精度极大依赖输入文本的结构化程度。Markdown 的标题层级、列表结构、实体标注恰好是图谱抽取的天然知识锚点。我自己的实践是先用 Markdown 结构定位“每个章节讲了什么”再用大模型做细粒度的实体和关系抽取比直接用全文抽取稳定得多。5.4 验证解析效果的手段不靠感觉靠检索实验最后一个实操环节是怎么验证你的解析方案真的有效。别相信“看起来挺规整”这种直觉要用检索实验说话。我常用的验证手段是制作一个“黄金问答集”从原始文档中挑出 3050 个典型问题这些问题覆盖不同章节、不同表格、不同代码块每个问题都标注标准答案原文所在的段落。然后用不同的解析方案分别构建向量库跑同一套问答集对比命中率。命中率计算公式很简单命中率 命中标准答案所在分块的问题数 / 总问题数这个指标能直接反映“解析出的分块是否让答案更容易被检索到”。我拿同一份混合格式文档跑过对比用普通RecursiveCharacterTextSplitter方案命中率约 60%用本文说的“段落边界 标题锚点 表格保护”方案命中率提升到 80% 以上。差距就是这么明显。所以每当有人问我“RAG 效果不好怎么办”我的第一个反问永远是你的解析结果真的让你的向量库“读懂了”原始文档吗就我自己而言这个从 txt 到 Markdown 的解析阶段是整个 RAG 导入链路里最不该图省事的部分。前期在这里花的时间会在后面每一次检索质量排查中加倍赚回来。后面几篇我会继续拆解 HTML 网页正文、PDF 版面还原、以及图片表格的 OCR 转 Markdown 等内容有相同实战经历的朋友可以直接照着这套思路往自己的管线里试。