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 pillowPaddleOCR 首次运行会自动下载中文识别模型(约 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 images2. 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 在设计阶段指出的。
审查能力速查表
| 需求 | 用什么 | 模型 | 适合阶段 |
|---|---|---|---|
| 代码快速自查 | /simplify | sonnet | 写完代码后 |
| 深度代码审查 | code-reviewer | opus | 提交 PR 前 |
| 安全漏洞扫描 | security-reviewer | sonnet | 上线前 |
| 方案设计评审 | critic | opus | 动手写代码前 |
| GitHub PR 审查 | 直接贴 PR 链接 | 自动路由 | Code Review |
把 review 嵌入开发流程,而不是写完再补,代码质量会好很多。
总结
Word 文档的 RAG 处理不能只靠文本提取。加入 PaddleOCR 后,图片中的中文信息也能被检索和理解,对于企业知识库场景是必要的一环。
核心组合:python-docx(结构解析) + PaddleOCR(图片 OCR) + Markdown 格式化(表格保结构) + 语义分块(检索友好)。
写完代码后,用 OMC 的 /simplify、code-reviewer、security-reviewer 做多维度审查,确保代码质量和安全性。
代码已开源,完整实现见 GitHub 仓库。