LLMNotes

第 07 章:文档解析

zjc 于 2026-01-07 发布

这是《LLM 应用开发 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 RAG 的质量上限常常在解析阶段就确定了。PDF、Word、HTML、扫描件和表格解析错误,会在后续切块、向量化、检索和生成中被放大。本章讲解析目标、工具选择和质量评估。

7.1 解析目标

文档解析不是只提取字符,而是尽量还原阅读结构:

标题层级
段落
列表
表格
代码块
图片 / OCR
页眉页脚
脚注
文档元数据

理想输出应保留顺序和结构:

{
  "doc_id": "policy-2026-01",
  "blocks": [
    {"type": "heading", "level": 2, "text": "退款政策"},
    {"type": "paragraph", "text": "标准订单可在签收后 7 天内申请退款。"},
    {"type": "table", "headers": ["商品", "时限"], "rows": [["食品", "不支持"]]}
  ]
}

7.2 文档类型挑战

类型 挑战 处理
PDF 双栏、表格、扫描、顺序错乱 版面分析 + OCR
Word 批注、样式、嵌套表格 结构化解析库
HTML 导航、广告、正文噪声 正文抽取
Markdown 相对链接、代码块 解析 AST
Excel 合并单元格、多 sheet 表格专用流程
扫描件 图片、倾斜、OCR 错误 预处理 + OCR 校验
邮件 引用链、附件 分段解析

7.3 PDF 解析

Python 示例:

from pypdf import PdfReader

def read_pdf(path: str):
    reader = PdfReader(path)
    pages = []
    for i, page in enumerate(reader.pages, start=1):
        text = page.extract_text() or ""
        pages.append({"page": i, "text": text.strip()})
    return pages

pypdf 适合文本层完整的简单 PDF。遇到双栏论文、复杂表格或扫描件,应使用版面分析或 OCR 工具。

7.4 Word 解析

from docx import Document

def read_docx(path: str):
    doc = Document(path)
    blocks = []
    for p in doc.paragraphs:
        if not p.text.strip():
            continue
        blocks.append({
            "type": "paragraph",
            "style": p.style.name,
            "text": p.text.strip(),
        })

    for table in doc.tables:
        rows = [[cell.text.strip() for cell in row.cells] for row in table.rows]
        blocks.append({"type": "table", "rows": rows})
    return blocks

注意 Word 的“标题”依赖样式定义,不同模板可能不一致。

7.5 HTML 正文抽取

import requests
from bs4 import BeautifulSoup

def read_html(url: str):
    html = requests.get(url, timeout=10).text
    soup = BeautifulSoup(html, "html.parser")

    for tag in soup(["script", "style", "nav", "footer"]):
        tag.decompose()

    title = soup.title.get_text(strip=True) if soup.title else ""
    text = "\n".join(
        p.get_text(" ", strip=True)
        for p in soup.find_all(["h1", "h2", "h3", "p", "li"])
        if p.get_text(strip=True)
    )
    return {"title": title, "text": text}

抓取外部网页前要遵守站点协议、版权和频率限制。

7.6 OCR 流程

图片/扫描 PDF
  -> 去斜 / 二值化 / 降噪
     -> 版面检测
        -> OCR
           -> 置信度过滤
              -> 表格还原
                 -> 人工抽检

OCR 错误示例:

原文:订单号 A10001
OCR:订単号 A1OOOI

处理策略:

  1. 保留 OCR 置信度;
  2. 关键字段用正则或字典校验;
  3. 合同号、金额、日期进入人工复核;
  4. 原图坐标保留,便于定位;
  5. 不同语言选择合适模型;
  6. 评估字符错误率。

7.7 表格解析

不要把表格直接压成无分隔文本。

推荐序列化:

表标题:商品发货时限
列:商品 | 时限 | 备注
行 1:食品 | 不支持 | 生鲜除外
行 2:数码 | 7天 | 需保留包装

复杂表格处理:

  1. 识别表头;
  2. 还原合并单元格;
  3. 拆分跨页表;
  4. 保持行列关系;
  5. 单元格为空时显式标记;
  6. 将表格和上下文说明放在同一 chunk 或可互相关联。

7.8 元数据设计

字段 示例
doc_id policy-2026-01
source /docs/refund.pdf
version v3
language zh-CN
effective_time 2026-01-01
owner 客服团队
acl cs_agent
page 12
checksum sha256

元数据会影响检索过滤、权限、新鲜度和去重,应尽早设计。

7.9 解析质量评估

指标 说明
提取成功率 解析未崩溃比例
字符准确率 OCR 质量抽样
结构准确率 标题表格是否正确
乱码率 不可读字符
空文档率 提取为空
平均处理时间 吞吐
人工修正率 需人工修复

建立样例集:

普通文档 60%
复杂表格 15%
扫描件 10%
多语言 10%
坏文件 5%

7.10 常见故障

现象 原因 处理
双栏 PDF 顺序错 未做版面分析 按栏切分
表格变成一行 文本提取 表格解析器
中文乱码 字体编码 OCR 或替换解析器
页眉重复 未清理 按页过滤
标题丢失 样式异常 字号和版面启发
扫描件为空 无文本层 OCR
旧版本被检索 元数据缺失 版本与生效时间

本章小结

文档解析决定 RAG 的数据质量。要根据格式选择解析策略,保留标题、表格、页码、权限和版本等元数据,对 OCR 和复杂版面建立抽样评估。解析层做好后,切块和检索的问题会明显减少。

思考题

  1. 为什么 PDF 提取文本不等于完成文档解析?
  2. 表格序列化时应保留哪些信息?
  3. 哪些元数据会直接影响检索正确性?
  4. OCR 低置信度字段应如何处理?
  5. 如何为一个企业文档系统设计解析抽样集?