如何在Python中正确结构化并清理从DOCX提取的文本?
如何在Python中正确结构化并清理从DOCX提取的文本?
看起来你在开发Flask多语种议程文档处理应用时,遇到了文本过滤不彻底、议程项编号混乱以及输出对齐的问题——我之前处理过类似的会议文档提取需求,给你分享几个针对性的改进方案:
一、优化无关内容过滤,解决残留的名字/时间/页码问题
你当前的should_skip_line函数匹配规则偏宽泛,比如仅匹配Mr/Ms这类标题,没结合名字一起判断,导致可能漏掉带名字的行;另外也没覆盖24小时制时间、中文页码(如果是多语种文档)等场景。我们可以把规则调整得更精准:
import re from docx import Document from typing import List, Dict def should_skip_line(line: str) -> bool: """Skip unwanted lines containing names, timestamps, and other irrelevant data.""" skip_patterns = [ r'\b(?:Mr|Ms|Dr|Mrs)\.\s+\w+', # 精准匹配「标题. 名字」格式 r'\b(?:Mr|Ms|Dr|Mrs)\b\s+\w+', # 匹配不带点的标题+名字 r'\b\d{1,2}\s*(?:am|pm)\b', # 12小时制时间 r'\b\d{1,2}:\d{2}\b', # 24小时制时间 r'^Page\s*\d+$', # 英文页码 r'^第\s*\d+\s*页$', # 中文页码(适配多语种) r'^\s*$', # 空行/纯空白 r'^[A-Z\s]+会议$', # 重复出现的会议标题(示例,可根据你的文档调整) r'\b(?:Agenda|议程)\b', # 重复的「议程」标题 ] # 忽略大小写匹配,只要行内包含任意规则内容就跳过 return any(re.search(pattern, line, re.IGNORECASE) for pattern in skip_patterns)
二、修复议程项编号,生成连续有序的条目
原代码只是提取了过滤后的文本,但没处理原文档中混乱的编号(比如3、6、4这类无序编号)。我们需要先识别议程项的起始行,再重新生成连续编号:
def process_agenda_items(cleaned_text: str) -> List[Dict]: """将清理后的文本分组为完整议程项,并生成连续编号""" lines = cleaned_text.split('\n') agenda_items = [] current_item_content = [] # 匹配议程项起始行:支持「1. 内容」或「(1) 内容」格式 item_start_regex = re.compile(r'^(\d+|\(\d+\))\s+', re.IGNORECASE) for line in lines: line_stripped = line.strip() if not line_stripped: continue # 检测当前行是否是新议程项的开头 start_match = item_start_regex.match(line_stripped) if start_match: # 先保存上一个正在构建的议程项 if current_item_content: agenda_items.append(' '.join(current_item_content)) current_item_content = [] # 去掉原编号,保留核心内容 item_content = line_stripped[start_match.end():].strip() current_item_content.append(item_content) else: # 非起始行,作为当前议程项的补充内容(比如换行的多行描述) if current_item_content: current_item_content.append(line_stripped) # 保存最后一个议程项 if current_item_content: agenda_items.append(' '.join(current_item_content)) # 生成带连续编号的结构化数据 return [{"item_number": idx + 1, "content": item} for idx, item in enumerate(agenda_items)]
三、整合流程并输出结构化表格
把提取、过滤、编号处理整合起来,最终可以直接生成适合Flask渲染的结构化数据,方便输出为对齐的HTML表格:
def extract_and_process_agenda(filepath: str) -> List[Dict]: # 从DOCX提取过滤后的文本 doc = Document(filepath) filtered_lines = [] for para in doc.paragraphs: text = para.text.strip() if text and not should_skip_line(text): filtered_lines.append(text) cleaned_text = '\n'.join(filtered_lines) # 处理为带连续编号的议程项 return process_agenda_items(cleaned_text) # 示例:在Flask中渲染为HTML表格 # from flask import Flask, render_template # app = Flask(__name__) # # @app.route('/agenda/<path:file_path>') # def show_agenda_table(file_path): # structured_agenda = extract_and_process_agenda(file_path) # return render_template('agenda_table.html', agenda=structured_agenda)
对应的Flask模板agenda_table.html可以这样写,确保内容对齐:
<table style="border-collapse: collapse; width: 100%;"> <thead> <tr style="border: 1px solid #ddd;"> <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">序号</th> <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">议程内容</th> </tr> </thead> <tbody> {% for item in agenda %} <tr style="border: 1px solid #ddd;"> <td style="border: 1px solid #ddd; padding: 8px; width: 80px;">{{ item.item_number }}</td> <td style="border: 1px solid #ddd; padding: 8px;">{{ item.content }}</td> </tr> {% endfor %} </tbody> </table>
额外适配:处理DOC格式文件
如果你的应用需要支持旧版DOC文件,可以用win32com.client提取文本,再接入上述流程:
def extract_text_from_doc(filepath: str) -> str: import win32com.client word_app = win32com.client.Dispatch("Word.Application") word_app.Visible = False try: doc = word_app.Documents.Open(filepath) text = doc.Content.Text # 清理Word文档中的特殊换行符 text = re.sub(r'\r\n|\r', '\n', text) return text finally: doc.Close() word_app.Quit() # 整合DOC/DOCX提取 def extract_text(filepath: str) -> str: if filepath.lower().endswith('.docx'): doc = Document(filepath) return '\n'.join([para.text.strip() for para in doc.paragraphs if para.text.strip()]) elif filepath.lower().endswith('.doc'): return extract_text_from_doc(filepath) else: raise ValueError("仅支持DOC/DOCX格式文件")
备注:内容来源于stack exchange,提问作者Binal Dalia




