You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python提取PDF中上标格式小数金额并保留负号的稳健方案

问题描述

我用Python处理一批PDF文件,需从中提取金额数据写入Excel。PDF里的金额小数以逗号跟上标数字形式呈现(如310,⁷⁶⁹⁷、−25,³⁴),实际为普通货币金额而非指数,要转换成310.7697、-25.34。

当前使用工具:

  • PyMuPDF(fitz)读取PDF文本,必要时用Tesseract OCR兜底
  • 正则表达式+字符串清洗提取金额
  • openpyxl写入Excel

现存问题:

  1. 小数转换不稳定,比如310,⁷⁶⁹⁷有时会变成310.0或3107697,而非预期的310.7697
  2. 负数金额偶尔丢失负号,比如−25,³⁴变成正数25,34

预期结果:
解析后得到浮点值[310.7697, -25.34],写入Excel后显示为:

310,7697 + -25,3400
总计:285,4297

解决方案

针对核心问题,通过优化字符处理逻辑、正则匹配和错误修复,实现稳健的金额解析:

关键修复点

  1. 修复函数拼写错误(amouts→amounts),避免执行报错
  2. 改用最后一个逗号/点作为小数分隔符,规避千分位分隔符干扰
  3. 提前捕获负号状态再移除所有负号字符,避免后续处理时丢失负号
  4. 补全全角、罕见上标字符映射,覆盖OCR可能识别的所有上标类型
  5. 预处理时统一移除空格,清理无效字符,确保数字提取准确
  6. 扩展正则的上标字符范围,确保金额部分完整捕获

修改后完整代码

import re

def clean_and_convert_amount(amount_str: str) -> float:
    """
    清洗带小数上标的金额字符串(如'310,⁷⁶⁹⁷'、'−25,³⁴'),转换为浮点值,保留负号和小数精度
    """
    if not amount_str:
        return 0.0

    raw = str(amount_str).strip()

    # 1. 处理负号:先标记是否为负数,再移除所有负号字符避免干扰后续处理
    minus_chars = {'−', '–', '-'}
    is_negative = any(char in raw for char in minus_chars)
    raw = ''.join([c for c in raw if c not in minus_chars])

    # 2. 上标数字映射:覆盖所有可能的上标字符
    superscript_map = {
        # 基础上标数字
        '⁰': '0', '¹': '1', '²': '2', '³': '3', '⁴': '4',
        '⁵': '5', '⁶': '6', '⁷': '7', '⁸': '8', '⁹': '9',
        # 替代编码上标
        '\u00B9': '1', '\u00B2': '2', '\u00B3': '3',
        # 全角上标数字
        '₀': '0', '₁': '1', '₂': '2', '₃': '3', '₄': '4',
        '₅': '5', '₆': '6', '₇': '7', '₈': '8', '₉': '9',
        # 罕见上标变体
        'ⁱ': '1', '⁲': '2', '⁳': '3'
    }

    def normalize_char(c: str) -> str:
        if c in superscript_map:
            return superscript_map[c]
        if c.isdigit():
            return c
        if c in {',', '.'}:
            return c
        return ''

    # 3. 预处理:清洗字符、移除空格
    processed = ''.join([normalize_char(c) for c in raw]).replace(' ', '')

    # 4. 定位小数分隔符:取最后一个逗号/点,避免千分位干扰
    comma_pos = processed.rfind(',')
    dot_pos = processed.rfind('.')
    sep_pos = -1
    if comma_pos != -1 and dot_pos != -1:
        sep_pos = max(comma_pos, dot_pos)
    elif comma_pos != -1:
        sep_pos = comma_pos
    elif dot_pos != -1:
        sep_pos = dot_pos

    # 5. 分割整数和小数部分
    if sep_pos == -1:
        integer_part = processed.strip() or '0'
        decimal_part = '0'
    else:
        integer_part = processed[:sep_pos].strip() or '0'
        decimal_part = processed[sep_pos+1:].strip() or '0'

    # 确保仅保留数字
    integer_part = ''.join([c for c in integer_part if c.isdigit()]) or '0'
    decimal_part = ''.join([c for c in decimal_part if c.isdigit()]) or '0'

    # 6. 转换为浮点值
    try:
        value = float(f"{integer_part}.{decimal_part}")
    except ValueError:
        return 0.0

    return -value if is_negative else value


def extract_amounts_from_period(text: str) -> list[float]:
    """
    从'din perioada ... în cuantum de <amount>'格式的句子中提取金额,排除包含'a fost acoperita suma de'的上下文
    """
    if not text:
        return []

    # 优化正则:覆盖所有上标字符,确保负号捕获
    pattern = re.compile(
        r"din\s+perioada\s+[^\n\r]+\s*(?:\n|\r\n)?\s*(?:în|in)\s+cuantum\s+de?\s*"
        r"([-−–]?\s*[0-9\s,.⁰¹²³⁴⁵⁶⁷⁸⁹\u00B9\u00B2\u00B3₀₁₂₃₄₅₆₇₈₉]+)",
        flags=re.IGNORECASE
    )

    lower_text = text.lower()
    amounts = []

    for match in pattern.finditer(text):
        amount_str = match.group(1)
        start_pos = match.start(1)

        # 跳过已覆盖金额的上下文
        context_window = lower_text[max(0, start_pos - 150):start_pos]
        if "a fost acoperita suma de" in context_window or "a fost acoperită suma de" in context_window:
            continue

        amounts.append(clean_and_convert_amount(amount_str))

    return amounts  # 修复拼写错误


# 测试示例
sample_text = """
din perioada ianuarie-februarie 2024 în cuantum de 310,⁷⁶⁹⁷
din perioada martie-aprilie 2024 în cuantum de −25,³⁴
din perioada mai-iunie 2024 în cuantum de 1 068,²⁷⁵⁶
"""

extracted = extract_amounts_from_period(sample_text)
print(f"提取金额:{extracted}")  # 输出:[310.7697, -25.34, 1068.2756]

# 准备Excel写入内容
amounts_str = " + ".join(f"{v:.4f}".replace(".", ",") for v in extracted)
total_str = f"{sum(extracted):.4f}".replace(".", ",")

print(f"Excel显示字符串:{amounts_str}")
print(f"总计:{total_str}")

内容的提问来源于stack exchange,提问作者Larisa Palimaru

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 00:07:02