新增 research-wiki/scratch/html-table-real-data-analysis-2026-08-21.md: 按展开 span 后行列一致性给 2474+356+1879 个顶层表格分类(剔除空 tr 后 简单表 53% / 合并合并表 8% / 损坏表 40%),定位标签不闭合、丢失 colspan 标注、无表头三类主因。README 当前阶段补对应条目。
9.0 KiB
HTML 表格真实数据结构分析:45 份 GovDoc 测试 Markdown
状态:只读分析记录,尚未进入任何 design。文中"建议"部分是候选方向,不是批准的方案。
分析日期:2026-08-21。
定位:用真实数据回答"我们的 HTML 表格到底长什么样、损坏在哪",为未来表格清洗 design 提供测量依据, 并修正
html-table-cleaning-ecosystem-research-2026-08-21.md第 8 节中可以用数据回答的待决问题。
1. 结论先行
按"展开 rowspan/colspan 后每行列数是否一致"给全部顶层表格分类,剔除空 <tr></tr> 之后的分布:
| 分类 | 定义 | 数量 | 占比 |
|---|---|---|---|
| A 简单矩形表 | 无合并单元格、每行列数一致、无嵌套、无游离内容 | 2474 | 53% |
| B 合法合并表 | 有 rowspan/colspan,展开后仍是完整矩形 | 356 | 8% |
| C 损坏表 | 参差网格、标签截断、占位冲突等 | 1879 | 40% |
四个改变预期的发现:
- 一半的"损坏"是空
<tr></tr>造成的假象——剔除后 C 类从 54% 降到 40%; - 标签不闭合会吞掉半篇文档——最极端的一个未闭合表格吞了 2.79MB、1294 个表格;
- 参差网格与合并单元格强相关——六成以上参差表带 span 属性,指向转换器丢失 colspan 标注;
- 无表头是常态(A 类中 96%),表格内图片为零,单元格内管道符为零。
2. 数据范围与方法
- 输入:
/home/lihaoze/gov_test_data/compare/*/uploads/*.md,45 份,全程只读; 本文只含聚合数字和结构事实,不含任何原文片段; - 分析脚本在会话级临时目录
/tmp/table-analysis/analyze.py,未入库(仓库处于文档治理阶段,无源码目录); 本文第 2.1 节的规则描述是复现依据; - 解析器:lxml
HTMLParser(recover=True)。本地未装 html5lib。
2.1 测量规则
-
代码围栏遮蔽:先标记
```/~~~围栏内的位置,围栏里的<table>不计入; -
片段切分:对围栏外的
<table/</table>事件按嵌套深度配对出顶层片段;悬空的</table>(无对应开启)单独计数并忽略;到文件尾仍未闭合的片段标记truncated; -
结构分析:每个片段单独喂给 lxml 容错解析;对每个
<table>展开 rowspan/colspan 建二维占位网格, 检测占位冲突,计算每行展开后的列数,收集表头、嵌套、游离文本、块级子标签、空单元格、超长单元格、 退化重复等特征; -
分类谓词(
truncated为片段级标记,其余为表格级):A_simple : 非 truncated 且 span_cells=0 且无嵌套 且每行展开列数一致 且无占位冲突 且无游离文本 且块级子标签 ⊆ {br} B_span_ok: 非 truncated 且无占位冲突 且每行展开列数一致 且无游离文本 C_broken : 其余全部 -
空行修复复核:把没有任何
td/th子元素的<tr>整行剔除后重新跑同一分类,观察迁移。
3. 总量与原始分类
- 45 份文档共 7550 个
<table开标签、7155 个闭标签,缺口 395 个; - 深度配对得到 4648 个顶层片段;容错解析后共 4709 个表格元素(缺口标签导致部分片段解析出多个表格);
parse_fail=0;lxml recover 模式记录 parser 错误的片段仅 30/4648——recover 解析器的错误日志是弱信号, 不能当损坏检测器用,损坏要靠网格校验发现。
原始分类(4709 个表格):
| 分类 | 数量 | 占比 |
|---|---|---|
| A_simple | 1921 | 41% |
| B_span_ok | 262 | 5% |
| C_broken | 2526 | 54% |
结构特征(占 4709 的比例):含 span 属性 1891(40%)、参差网格 2506(53%)、有 <th> 259(5.5%)、
单行表 157、截断片段 67、超长单元格(>500 字符)197、巨型片段(>10KB)70、退化重复内容 33、
游离文本 27、占位冲突 14、嵌套表格 5、含块级标签 5、含图片 0。
4. 发现一:空 <tr></tr> 是最大的单一"假损坏"来源
完全没有 td/th 子元素的空行标签在参差表里出现 3000+ 行次。它们把"列数一致的好表"撑成
"某些行 0 列"的参差表。
剔除空行后重新分类:
| 迁移路径 | 数量 |
|---|---|
| C_broken → A_simple | 553 |
| C_broken → B_span_ok | 94 |
即分类变为 A 2474(53%)/ B 356(8%)/ C 1879(40%),一条零风险修复救回 13% 的表。 空行不含任何内容,剔除是无损的。
空行出现的位置:表中间 1151 处、表尾 385 处(对修复后仍为 C 的表统计),没有出现在表头位置—— 符合"转换器输出残留"而非"表头占位"的形态。
5. 发现二:标签不闭合会吞掉后续正文
最极端案例 001-2/uploads/file_0_PDF.md:全文件 1315 开 / 1124 闭;从第 1315 行开始的一个未闭合
表格把后续 2,789,092 字符、内部含 1294 个表格的内容全部吞进一个"顶层片段"。
全库 395 个闭合缺口意味着:清洗的第一步不是处理表格,而是安全切分片段。深度配对在标签缺失时 会把正文和后续完整表格归并进一个巨型片段,后续所有基于片段的统计和修改都会失真。
切分策略的可用锚点:相邻顶层表格之间,2943 对隔着真实文本、1661 对只隔空行——块边界 (空行 + 后续正文)在实际数据中是可识别的。
6. 发现三:参差网格与 span 强相关,指向"丢 colspan 标注"
2506 个参差表中 1624 个(65%)带 span 属性——格子内容在、宽度信息没了,是转换器丢失 colspan 标注的形态,不全是真缺内容。
剔除空行后仍参差的 1851 个表,参差发生位置:
| 位置 | 数量 | 推断成因 |
|---|---|---|
| 中间各行乱 | 780 | 真·结构损坏或逐行丢标注 |
| 表头行比正文长 | 619 | 多级表头被拍平成一行、丢层级 |
| 首行短 | 239 | 表头/首行缺格 |
| 仅尾部截短 | 213 | 跨页截断尾巴 |
四类成因不同,修复策略应当不同,但都不能靠补空单元格自动修——那正是
../reference/GOVDOC_SAAS_CLEANING_SCOPE.md T002
明确反对、且 rumdl MD056 的 auto-fix 方向被本仓库否决的做法。
7. 发现四:无表头是常态,转换障碍集中在 <br>
对修复后仍是 A 类的 2474 个表,检查转 GFM 管道表格的内容障碍:
| 障碍 | 数量 | 占 A 类 |
|---|---|---|
无 <th> 表头 |
2393 | 96% |
单元格含 <br> |
501 | 20% |
| 单行表 | 176 | 7% |
| 超长单元格(>500 字符) | 54 | 2% |
单元格文本含 | |
0 | 0% |
表格内 <img> |
0 | 0% |
转义压力比预期小(管道符、图片都是零),压力集中在合成表头和 <br> 处理上。
8. 建议的处理策略(候选,未批准)
三步走,对齐审计 T001 的分流方向,用真实数据修正边界:
- 切分:容错定位
<table>片段;未闭合的在块边界截断并标记 truncated, 绝不让深度配对吞正文; - 无损修复:只做剔除空
<tr>这一级别的零风险修复(实测救回 13% 的表); - 分流:
- A 类(53%)→ 转 GFM:合成表头、
<br>转空格、管道符转义兜底; - B 类(8%)→ 保留 HTML,规范化输出;
- C 类(40%)→ 不自动修,保留原样 + 按第 6 节四类成因报告分类原因,交人工确认或回源。
- A 类(53%)→ 转 GFM:合成表头、
对生态调研第 8 节待决问题的数据回答:
- 问题 4(无表头表格怎么处理):数据表明无表头占绝对多数(96%),"补合成表头转 GFM" 是主流路径;空表头还是首行充当表头仍是 design 待决;
- 问题 1(简单/复杂分界线):分界线除了合并单元格,必须加"展开后网格是否矩形"—— 本数据中它是比 span 更强的损坏信号(53% 对 40%);
- 问题 2、3(规范 HTML 程度、JSON schema)本次数据没有新增证据,维持开放。
9. 局限
- 全部数字只来自这 45 份文档,不得外推为一般结论(CLAUDE.md 第 5 节约束);
- 53/8/40 依赖"剔除空
<tr>"这条规则被采纳;不采纳则为 41/5/54; - lxml recover 解析可能自行重排损坏片段,个别表格的行列统计是解析结果而非字节事实;
- 巨型片段内部的表格统计(如 001-2/file_0 被吞的 1294 个)已计入总数,但它们在原文中的 真实边界未经人工核对;
- 参差位置的四分类用的是简单规则(与列数众数比较),是启发式归类,不是语义判断。
10. 验证状态
- 第 3 至 7 节所有数字:2026-08-21 由只读脚本在真实数据上实际运行得出,脚本未入库;
- 分类谓词与第 2.1 节规则描述和脚本逻辑一致,可据此重建等价测量;
- 未验证:任何修复或转换策略的实际效果——A/B/C 分流、空行剔除、块边界截断都还没有实现, 需要等对应 design 批准后用受控样本测试。