
本文介绍使用 python-docx 高效提取多语言 docx 议程文档的结构化文本,通过精准清洗、段落语义过滤、编号重序与布局对齐策略,解决姓名/时间残留、编号错乱及文本错位等常见问题。
本文介绍使用 python-docx 高效提取多语言 docx 议程文档的结构化文本,通过精准清洗、段落语义过滤、编号重序与布局对齐策略,解决姓名/时间残留、编号错乱及文本错位等常见问题。
在处理会议议程类 DOCX 文档(尤其是多语言、格式不统一的 Type 1 文档)时,仅靠基础文本提取和简单正则过滤往往导致结构丢失——例如“3. 审议预算”被拆成两行、“Dr. Smith, 2:30 PM”未被彻底清除,或因跳过整行造成后续编号错位(如原序 1→2→3 变为 1→3→4)。根本原因在于:删除 ≠ 清洗,跳过段落会破坏文档逻辑流与视觉层级。以下是经过生产环境验证的优化方案:
✅ 一、语义感知清洗:替换而非跳过
避免 should_skip_line 直接丢弃整段,改用「模式替换」保留段落骨架:
import re
from docx import Document
def clean_line(line: str) -> str:
"""安全清洗单行:移除干扰元素,但保留议程主干"""
# 替换称谓、时间、页码等为规范空白(避免断行错位)
line = re.sub(r'\b(?:mr|ms|dr|mrs|prof|senior)\b[.,\s]*', '', line, flags=re.IGNORECASE)
line = re.sub(r'\b\d{1,2}[:.]\d{2}\s*(?:am|pm|AM|PM)\b', '', line, flags=re.IGNORECASE)
line = re.sub(r'^Page\s+\d+\s*$', '', line, flags=re.IGNORECASE) # 清空整行但不删段
line = re.sub(r'[^\S\n]+', ' ', line) # 合并多余空白
return line.strip()
def extract_structured_agenda(filepath: str) -> list:
doc = Document(filepath)
cleaned_paragraphs = []
for para in doc.paragraphs:
text = para.text.strip()
if not text:
continue
cleaned = clean_line(text)
if cleaned: # 仅当清洗后仍有有效内容才保留
cleaned_paragraphs.append(cleaned)
return cleaned_paragraphs⚠️ 注意:
clean_line返回空字符串时才跳过该段,确保“1. 开场 → [空] → 2. 报告”不会变成“1. 开场 → 2. 报告”(编号仍连续),而“Dr. Lee, 10:00 AM → 议程项1”会被清洗为“议程项1”。
✅ 二、智能编号重建:基于语义前缀识别 + 顺序重赋
利用议程项常见前缀(如数字+点、罗马数字、带括号编号)自动识别并标准化编号:
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
立即学习“Python免费学习笔记(深入)”;
def normalize_agenda_items(paragraphs: list) -> list:
"""将原始段落映射为 (序号, 内容) 元组,并强制连续编号"""
agenda_items = []
item_pattern = r'^(\d+\.|\(?[ivxlcdm]+\)?\.?|\d+\)|\d+\s*[-–—])\s+(.+)$'
for para in paragraphs:
match = re.match(item_pattern, para.strip(), re.IGNORECASE)
if match:
# 提取内容主体,忽略原始编号(防止 3,6,4 混乱)
content = match.group(2).strip()
agenda_items.append(content)
else:
# 非编号段落(如标题、分隔线)暂存,后续可归类
if re.search(r'^[A-Z][a-z]+.*:$', para): # 如 "Discussion:"
agenda_items.append(f"[SECTION] {para}")
# 强制重编号:1. 内容1 → 2. 内容2 → ...
return [f"{i+1}. {item}" for i, item in enumerate(agenda_items)]
# 使用示例
raw_lines = extract_structured_agenda("agenda_en_zh.docx")
structured = normalize_agenda_items(raw_lines)
for item in structured[:5]:
print(item)
# 输出:
# 1. Approval of previous meeting minutes
# 2. Budget review and Q&A (中文:预算审议及问答)
# 3. New project timeline announcement✅ 三、对齐增强:结合段落样式与缩进分析
若仍存在“标题顶格、子项缩进丢失”问题,需读取 paragraph.style 和 paragraph.paragraph_format.left_indent:
def extract_with_format(filepath: str) -> list:
doc = Document(filepath)
result = []
for para in doc.paragraphs:
if not para.text.strip():
continue
# 判断是否为标题级段落(如 Heading 1 / Heading 2)
style_name = para.style.name if para.style else ""
is_heading = any(kw in style_name.lower() for kw in ["heading", "title"])
indent = para.paragraph_format.left_indent or 0
cleaned = clean_line(para.text)
if not cleaned:
continue
# 标记层级:0=主项,1=子项(缩进 > 180000 EMU ≈ 0.5英寸)
level = 1 if indent > 180000 else 0
result.append({
"text": cleaned,
"level": level,
"is_heading": is_heading,
"original_number": extract_raw_number(para.text) # 自定义辅助函数
})
return result? 总结建议
- 永远优先替换而非删除:用空格/占位符维持段落位置关系;
- 编号必须重生成:依赖原文编号极易受扫描件OCR错位或模板变更影响;
-
多语言兼容:正则中加入 Unicode 字母范围(如
[\u4e00-\u9fff]匹配中文),避免硬编码英文关键词; -
验证先行:对每份文档类型建立小样本清洗规则集,用
difflib.SequenceMatcher对比清洗前后相似度,确保关键信息无损。
通过以上三层处理(清洗→重序→对齐),可稳定输出符合 HTML 表格或 JSON API 要求的结构化议程数据,显著提升 Flask 后端解析鲁棒性与前端渲染一致性。

















