You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在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

火山引擎 最新活动