Word 文档里有文字、有表格、有截图,传统文本解析只能拿到一半信息。加上 PaddleOCR,图片里的中文也能被检索到。

问题

做 RAG 系统时,知识库里最常见的就是 Word 文档。但 Word 不是纯文本——它是一个 zip 包,里面塞着:

  • 正文段落 — 直接能提取的文字
  • 表格 — 结构化数据,直接 split 会丢失行列关系
  • 嵌入图片 — 流程图、截图、扫描件,里面的中文完全是”隐形”的

如果只用 python-docx 提取文字段落,表格和图片里的信息就全丢了。用户问”那张架构图里的数据库用的什么?“,系统完全答不上来。

整体架构

Word 文档 (.docx)
    │
    ├─ python-docx ──→ 段落文本
    │
    ├─ python-docx ──→ 表格 ──→ Markdown 格式化
    │
    └─ python-docx ──→ 提取嵌入图片
                           │
                           └─ PaddleOCR ──→ 图片中的中文文本
    │
    ▼
  合并所有内容 → 分块(chunking) → 向量化 → 存入向量数据库
    │
    ▼
  用户提问 → 检索相关 chunks → LLM 生成回答

核心实现

1. 环境准备

pip install python-docx paddleocr paddlepaddle pillow

PaddleOCR 首次运行会自动下载中文识别模型(约 100MB),后续使用本地缓存。

2. 提取 Word 中的段落文本

from docx import Document
 
def extract_paragraphs(docx_path: str) -> list[str]:
    """提取所有非空段落"""
    doc = Document(docx_path)
    return [p.text.strip() for p in doc.paragraphs if p.text.strip()]

这一步最简单,但只能拿到纯文本段落,表格和图片里的内容拿不到。

3. 提取并格式化表格

表格是 RAG 中容易被忽略的部分。直接把表格拍平成一行文字会丢失结构,检索效果很差。更好的做法是转成 Markdown 表格格式:

def extract_tables(docx_path: str) -> list[str]:
    """将每个表格转为 Markdown 格式的字符串"""
    doc = Document(docx_path)
    tables_md = []
 
    for table in doc.tables:
        rows = []
        for row in table.rows:
            cells = [cell.text.strip() for cell in row.cells]
            rows.append("| " + " | ".join(cells) + " |")
 
        if len(rows) >= 2:
            # 插入 Markdown 表头分隔符
            header_sep = "| " + " | ".join(["---"] * len(table.rows[0].cells)) + " |"
            rows.insert(1, header_sep)
 
        tables_md.append("\n".join(rows))
 
    return tables_md

转成 Markdown 后,LLM 能理解表格结构,检索时也能匹配到表格内的具体内容。

4. 提取图片并用 PaddleOCR 识别中文

这是最关键的一步。Word 文档中的图片存储在 word/media/ 目录下,python-docx 可以直接访问:

import io
from PIL import Image
from paddleocr import PaddleOCR
 
# 初始化 PaddleOCR,use_angle_cls 开启方向分类,处理旋转文字
ocr = PaddleOCR(use_angle_cls=True, lang='ch', show_log=False)
 
def extract_images_text(docx_path: str) -> list[str]:
    """提取 Word 中所有图片,用 OCR 识别其中的中文"""
    doc = Document(docx_path)
    image_texts = []
 
    for rel in doc.part.rels.values():
        if "image" in rel.reltype:
            image_data = rel.target_part.blob
            image = Image.open(io.BytesIO(image_data))
 
            # 保存临时文件供 PaddleOCR 使用
            tmp_path = "/tmp/ocr_temp.png"
            image.save(tmp_path)
 
            result = ocr.ocr(tmp_path, cls=True)
            if result and result[0]:
                lines = [line[1][0] for line in result[0]]
                text = "\n".join(lines)
                image_texts.append(f"[图片内容]\n{text}")
 
    return image_texts

几个关键点:

  • use_angle_cls=True — 开启文字方向检测,处理旋转或倒置的文字
  • lang='ch' — 使用中文模型,对中英混排也有很好的支持
  • cls=True — OCR 时启用方向分类器

5. 合并所有内容

def parse_word_document(docx_path: str) -> str:
    """解析 Word 文档,合并段落、表格、图片 OCR 结果"""
    sections = []
 
    # 段落
    paragraphs = extract_paragraphs(docx_path)
    if paragraphs:
        sections.append("## 正文内容\n\n" + "\n\n".join(paragraphs))
 
    # 表格
    tables = extract_tables(docx_path)
    for i, table in enumerate(tables, 1):
        sections.append(f"## 表格 {i}\n\n{table}")
 
    # 图片 OCR
    image_texts = extract_images_text(docx_path)
    for i, text in enumerate(image_texts, 1):
        sections.append(f"## 图片 {i}\n\n{text}")
 
    return "\n\n---\n\n".join(sections)

6. 分块策略

合并后的文本需要分块才能入库。对于图文混合的 Word 文档,推荐按语义边界分块:

from langchain.text_splitter import RecursiveCharacterTextSplitter
 
def chunk_document(full_text: str, chunk_size=500, overlap=50) -> list[str]:
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=overlap,
        separators=["\n\n---\n\n", "\n\n", "\n", "。", ";", " "]
    )
    return splitter.split_text(full_text)

分隔符的优先级很重要:

  • 先按 --- 分割(段落/表格/图片之间的边界)
  • 再按段落分割
  • 最后按句子分割

这样能保证表格不会被切断,图片 OCR 内容也保持完整。

实际效果

用一份包含架构图和配置表的内部文档测试:

内容类型传统解析加入 PaddleOCR
正文段落100% 提取100% 提取
表格内容丢失结构Markdown 保留结构
图片中文字完全丢失92%+ 识别率
检索命中率~60%~85%

PaddleOCR 对印刷体中文的识别率很高,手写体和低分辨率图片会有下降,但对于大多数企业文档场景已经够用。

踩坑记录

1. 图片提取不完整

python-docx 通过 rels 关系提取图片,但有些图片是通过 VML 或 DrawingML 嵌入的,直接遍历 rels 可能遗漏。更保险的做法是直接解压 docx 文件读取 word/media/ 目录:

import zipfile
 
def extract_all_images(docx_path: str) -> list[bytes]:
    images = []
    with zipfile.ZipFile(docx_path, 'r') as z:
        for name in z.namelist():
            if name.startswith('word/media/'):
                images.append(z.read(name))
    return images

2. PaddleOCR 内存占用

PaddleOCR 模型加载后占用约 1-2GB 内存。批量处理大量文档时,建议:

  • 复用同一个 PaddleOCR 实例,不要反复初始化
  • 处理完一批文档后手动释放:del ocr; gc.collect()

3. 表格合并单元格

Word 表格经常有合并单元格,python-docx 会返回重复的 cell 引用。需要去重处理,否则同一内容会出现多次。

4. 图片顺序与上下文

当前方案把图片 OCR 结果放在文档最后,丢失了图片在文档中的位置信息。如果需要保留位置关系,要解析 document.xml 中的 <w:drawing> 标签位置,将 OCR 结果插入对应段落之间。

用 OMC 多智能体审查代码质量

写完代码不算完,还要 review。我用 oh-my-claudecode (OMC) 的多智能体能力对这套实现做了审查,这里分享一下 OMC 的 review 体系,写完代码可以直接用。

/simplify — 快速审查已改动的代码

这是最轻量的方式。执行 /simplify,OMC 会自动扫描最近修改的代码,检查复用性、质量和效率问题并直接修复。适合写完代码后的第一遍自查。

code-reviewer — 深度代码审查

需要更严格的 review 时,用 code-reviewer agent(opus 模型驱动):

用 code-reviewer 审查 src/word_parser.py

它会输出结构化的审查报告,包括:

  • 严重性分级(critical / major / minor)
  • 逻辑缺陷检测
  • SOLID 原则检查
  • 性能和风格建议

比如在这个项目中,code-reviewer 指出了图片提取通过 rels 遍历会遗漏 VML 嵌入图片的问题,也就是踩坑记录第 1 条。

security-reviewer — 安全审查

处理用户上传的 Word 文档时,安全问题不能忽视:

用 security-reviewer 检查这个项目的安全性

它专注 OWASP Top 10、敏感信息泄露和不安全模式。对于文件解析场景,会检查 zip 炸弹防护、路径穿越、临时文件清理等问题。

critic — 方案设计评审

在动手写代码之前,可以先让 critic 评审你的设计方案:

用 critic 审查我的 RAG 系统设计方案

它会从多个视角挑战你的方案,找出架构盲点。比如”图片 OCR 结果放在文档最后会丢失位置信息”这个问题,就是 critic 在设计阶段指出的。

审查能力速查表

需求用什么模型适合阶段
代码快速自查/simplifysonnet写完代码后
深度代码审查code-revieweropus提交 PR 前
安全漏洞扫描security-reviewersonnet上线前
方案设计评审criticopus动手写代码前
GitHub PR 审查直接贴 PR 链接自动路由Code Review

把 review 嵌入开发流程,而不是写完再补,代码质量会好很多。

总结

Word 文档的 RAG 处理不能只靠文本提取。加入 PaddleOCR 后,图片中的中文信息也能被检索和理解,对于企业知识库场景是必要的一环。

核心组合:python-docx(结构解析) + PaddleOCR(图片 OCR) + Markdown 格式化(表格保结构) + 语义分块(检索友好)。

写完代码后,用 OMC 的 /simplifycode-reviewersecurity-reviewer 做多维度审查,确保代码质量和安全性。

代码已开源,完整实现见 GitHub 仓库