Excel 表格进知识库,为什么不能按行硬切
按行读 Excel 只能拿到值,结构、公式和边界全丢了。有谷大脑用六段确定性流水线——空行切块、反差认表头、可引用渲染——让模型能说「据 Q4!B6」,而不是「据我看到的表格」。
把一张销售报表 .xlsx 丢进知识库,然后问:「摄像头 10 月卖了多少?」
如果入库时只是把每个单元格的值拼成文本,或者按固定 token 数硬切,常见的结果是这样的:检索命中了 88000 和 91000 两个数字,模型不知道哪个是摄像头、哪个是 10 月——因为 chunk 里既没有列名,也没有行号,更没有 Excel 坐标。
Excel 和 PDF、Word 不一样。PDF 的问题是「图里藏着字」;Excel 的问题是信息一大半不在值里,而在值与值的排布关系里——空行划边界、合并单元格标分组、公式串写因果、冻结窗格声明表头。按行读只取走了值,把关系全丢了。
有谷大脑对 Excel 的处理,不走「LibreOffice 转 PDF 再 OCR」这条路,而是走专用的表格语义解析流水线——底层基于开源项目 excel-parser,全程不调模型、不做猜测,同一份文件重跑永远得到同一批 chunk。
按行读,丢的是什么
打开一张普通的 Q4 销售报表:第 1 行是标题,第 2 行是制表人和日期,第 3 行空着,第 4 行才是真正的列名,往下是数据行和合计行。再往下空两行,藏着第二张小表「假设参数」。
用最常见的 openpyxl 按行读:
for row in sheet.iter_rows(values_only=True):
print(row)
你会得到 15 个元组,但没有任何一处告诉你「这里其实是两张表」、「第 4 行才是列名」、=SUM(B5:D5) 算出来的是季度合计。公式读出来是 None——业务逻辑跟着一起消失了。
这四类丢失,正好对应解析器要做的四件事:
| 丢失 | 后果 | 解析器怎么补 |
|---|---|---|
| 结构 | 分不清哪行是列名 | 认表头 + 切块 |
| 公式 | =SUM 变空 | 保留公式串 + 建依赖图 |
| 出处 | 拿到 138000 说不出在哪个格 | 渲染时带行号、列字母 |
| 边界 | 两张表被当成一片 | 空行切块 + 修补 |
六段流水线,各管一件事
一份 .xlsx 从字节到检索单元,中间经过六段处理:
- 读取 — 把网格搬进内存,同时抓住值、公式、加粗、合并区域、隐藏行、冻结窗格
- 切块 — 一张 sheet 上有几张独立的表,各自占哪个矩形
- 认表头 — 哪一行才是真正的列名
- 渲染 — 写成模型读得懂、能引用的文本
- 算依赖 — 从公式串里扫出「谁算出了谁」
- 封装 — 变成带 metadata 的 chunk,大表超预算时分片
第 2 段和第 3 段是全部难度所在。其余是水到渠成。
同一张 Q4 表跑完,产出 4 个 chunk:说明区、销售表、假设参数表、汇总表。销售表渲染出来长这样:
[Q4!A4:E8] (table) cols: A=产品线, B=10月, C=11月, D=12月, E=季度合计
| row | 产品线 | 10月 | 11月 | 12月 | 季度合计 |
|-----|------|--------|--------|--------|-------------|
| 5 | 智能门锁 | 120000 | 138000 | 151000 | =SUM(B5:D5) |
| 6 | 摄像头 | 88000 | 91000 | 105000 | =SUM(B6:D6) |
对比开头那 15 个元组:表头认对了、公式留住了、边界切准了、行号和列字母都在。用户问「摄像头 10 月卖了多少」,模型能从 cols: 查到「10月」是 B 列,从行号栏查到「摄像头」在第 6 行,拼出 Q4!B6 = 88000。
切块:先粗暴地切,再有条件地粘回去
一张 sheet 上可能有一张表,也可能有五张表加说明文字。切块要回答:有几个独立区域,各自占哪个矩形。
第一刀:空行空列
最可靠的边界信号是空白。人在 Excel 里排版,两张表之间总会留空行。
算法很直白:找出所有有内容的行,行号之间隔了空行就断开;每个行组内部再按列间隙切。默认阈值是 1——隔一个空行/空列就断。极密的表(密度 > 0.9,典型数据导出)放宽到隔 2 行才断,但列间隙从不放宽——空列几乎总是真实边界。
第一刀切完,常见两处「受伤」:
- 标题块:只有标题和制表信息,没有数据,单独作为检索单元毫无意义
- 孤儿续表:表中间有空行时,后半段有数据但没有表头——检索命中它,模型看到一列数字,不知道是哪个月
所以第一刀之后必须修补。
修补一:并标题
把孤零零的标题块折进下面的数据块。但不能无脑合并——两个各三行的独立小表会被互相粘死。
设了严格的准入条件:标题块不超过 3 行、没有公式、不是 Excel 正式表、必须紧贴在数据块上方、下面那块必须更高、列范围有重叠。只把小的折进大的。
修补二:缝表身
从一个带表头的「锚块」出发,往下扫。遇到的多列、列范围重叠、自己没有表头的块——吞掉,锚块范围往下延伸。遇到单列的小行(比如「华北区」这样的分组标签)——先挂起,后面真有续表才一起并入;如果后面停了,就留作独立块。
判断一个东西是什么,有时候要看它后面跟着什么。 这个思路在 Excel 解析里反复出现。
认表头:加粗不可靠,第一行更不可靠
两个直觉都是错的:
- 「表头是加粗的那行」——合计行也加粗,很多表头反而不加粗
- 「表头是第一行」——上面往往压着标题、制表信息、单位说明
真正可靠的信号是反差:逐列比较「这一行这个格子是不是文字」和「它下面整列是不是数据」。两者同时成立,这一列算一次反差;累计 2 列以上,这行就是表头。
对 Q4 表第 4 行:10月 下面是 120000/88000/46000,11月、12月、季度合计 同理——五列全中。这个判断跟加粗完全无关。
怎么跳过标题行?
- 第 1 行横跨全表的合并单元格 → 横幅,不是表头
- 第 2 行「制表人: 张伟 制表日期: 2025-01-08」→ 键值对说明行,跳过
找到锚点后还要向下收编多层表头,但不能把数据行吃进去。刹车机制叫「体征签名」:给每行算类型指纹(文字/数字/日期/空),签名跟下方数据行一致的行——即使加粗——也是数据,band 在此停住。
还有一个「作者优先」规则:如果作者冻结了窗格,band 越过冻结线时会被裁回去。冻结窗格是作者在说「这几行要一直钉在上面」,优先于任何启发式。
find_header_span() 这个函数被三处共用:切块时判断有没有表头、渲染时决定 Markdown 表头行、大表分片时决定每片重复哪一行。三处共用同一个判断,才能保证一张表切成 5 片后,每片的表头完全一致。
渲染:写给模型看,不是写给人看
渲染文本有四个关键部件:
- 坐标括号行 —
[Q4!A4:E8] (table),标明范围和块类型 - 列字母对照 —
cols: A=产品线, B=10月, ...,建立列名与 Excel 列字母的映射 - 行号栏 — 每行钉上真实 Excel 行号,隐藏行标
[hidden] - 原始值 — 写
1272而不是1,272.00,因为用户在检索框里打的是1272
列字母不放在表头行里——表头行留给真实列名。配上行号栏,模型能反推出任意数字的完整地址,回答里写「据 Q4!B6」,而不是「据我看到的表格」。
隐藏行不删,打标记。审计的人需要知道「这份表藏了三行」。整个项目的立场是:尽量不丢信息,把判断权交给下游。
公式依赖:=SUM 里藏的业务逻辑
=SUM(B5:D5) 里面藏的信息是:E5 这个数字是由 B5、C5、D5 加出来的。把整个工作簿的这类关系连起来,就得到一张有向图——这份表的业务逻辑。
解析器对公式做的唯一一件事,是扫出里面所有的引用,不求值——直接读 Excel 缓存进文件里的计算结果。自己实现几百个 Excel 函数是另一个量级的工程,而且没必要。
每个 chunk 会附一份依赖摘要。检索命中销售表时,模型顺带知道:这块的数字被汇总表的两个格子引用着。这是纯值读取给不了的上下文。
大表分片:不能把表头切丢
默认预算是每片 2000 token,小表原样保留。只有超预算的表才切。
切的时候,每片重复表头,并声明自己是第几片:
[PART 2/5 of table 明细!A1:H301 — rows 72–141 of 2–301; header repeated below]
三个细节:
- 每片重复的表头,是同一个
find_header_span()判出来的,5 片列名保证一致 - 片的坐标只覆盖数据行,重复的表头算「渲染时加的糖」,不算进坐标
- 标题只在第 1 片保留,避免每片都顶着同一句报表标题
每片装多少行,不能把表头开销按平均行数摊薄——每片都要重复付一次表头、cols: 映射、横幅的开销,固定开销要先扣掉再算能装几行。
chunk_id 由文件哈希 + 表名 + 块类型 + 坐标确定性算出,不含时间戳、不含随机数。同一份文件解析一千次,得到的 id 完全相同——去重、版本比对、复现调试都免费到手。
和通用 chunk 的实际差异
同一份 300 行 8 列的订单明细表:
- 通用 512-token 切割:约 40–60 个 chunks。大量 chunk 从表中间切断,第 2 片没有表头,检索命中时模型看到
199.5只能猜是单价还是金额 - 表格语义切割:按 sheet 上的独立区域切,销售表、假设参数表、说明区各成一块;超预算的大表按行分片,每片带完整表头。每个 chunk 是可引用的矩形区域,向量表示聚焦
还有一个常见路径的对比:LibreOffice 转 PDF 再 OCR。表格变成图片,列对齐、公式、隐藏行全部丢失,OCR 还容易把数字读错。专用 Excel 解析保留结构语义,这是表格类文档 RAG 准确率的基础。
什么时候 Excel RAG 最容易出问题
以下几种情况是重灾区:
问具体格子的值:「华东区 Q4 摄像头 11 月销售额」——需要列名 + 行号 + 坐标,通用文本 chunk 给不了。
问公式算出来的数:「季度合计怎么来的」——需要公式串和依赖关系,=SUM 变 None 就答不完整。
一张 sheet 多张表:主表和假设参数表混在一起切,问毛利率时检索到销售数据。
大表中间段:300 行明细被硬切,中间某片没有表头,数字失去了列语义。
跨表引用:汇总表引用销售表的合计格,chunk 之间没有依赖信息,模型不知道两个表的关系。
典型使用场景
财务和业务分析:把季度报表、预算模型、经营仪表盘传进知识库,直接问「哪个产品线 Q4 增长最快」「假设参数里的毛利率是多少」,不用每次打开 Excel 找对应 sheet。
采购和供应链:价格表、BOM、供应商报价单通常是多 sheet、多表混排。表格语义切块让每张价目表成为独立检索单元。
审计和合规:隐藏行打标记而不是删除,公式依赖保留,回答能追溯到具体单元格——「据 Q4!E8」比「据表格底部那个合计」可验证得多。
Excel 是企业知识库里最高频的结构化数据源之一,却长期被当作「转 PDF 处理」的二等公民。把表格当成表格来切——而不是当成一段普通文本——是让它在 RAG 里真正好用的前提。