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

Python提取含多行垂直居中单元格的PDF表格及解决银行交易数据描述错位问题的技术问询

Python提取含多行垂直居中单元格的PDF表格及解决银行交易数据描述错位问题的技术问询

兄弟我太懂这种痛苦了!之前处理某银行的交易PDF时,也被这种乱跑的描述行折腾得头大——一会儿在交易数据上面,一会儿在下面,还动不动拆成两三行,导Excel的时候全乱套。本质问题就是这类银行PDF不是标准的结构化表格,而是流式布局,pdfplumber直接提取的文本会把交易核心数据和描述行拆成独立的行,完全脱节。

给你一套亲测有效的解决思路和代码,核心就是先识别交易核心行,再为每个交易行匹配前后的描述内容

核心思路

  1. 定位交易行:交易行一定有固定格式特征——比如两个日期(交易日/记账日)、三列金额(转入/转出/余额),用正则表达式就能精准匹配。
  2. 合并描述行:不管描述在交易行的前面还是后面,只要不是交易行的内容,就合并到对应交易的描述字段里。
  3. 结构化导出:把整理好的交易数据转成DataFrame,再导出Excel,彻底解决错位问题。

具体代码实现

import re
import pandas as pd
import pdfplumber

def extract_bank_transactions(fpath):
    # 这里的正则要根据你的银行PDF格式调整!
    # 示例规则:匹配 交易日期 记账日期 转入金额 转出金额 余额 的行
    # 你可以先打印提取到的lines,看交易行的具体格式再修改正则
    transaction_pattern = re.compile(
        r'(\d{2}/\d{2}/\d{4})\s+(\d{2}/\d{2}/\d{4})\s+([\d,.+-]*)\s+([\d,.+-]*)\s+([\d,.+-]+)'
    )
    all_transactions = []
    
    with pdfplumber.open(fpath) as pdf:
        for page in pdf.pages:
            # 用layout=True保留原始布局,减少文本错位
            text = page.extract_text(layout=True, y_tolerance=3)
            if not text:
                continue
            
            # 拆分文本为行,清理空行和多余空格
            lines = [line.strip() for line in text.split('\n') if line.strip()]
            current_desc_buffer = []
            current_transaction = None
            
            for line_idx, line in enumerate(lines):
                # 检查当前行是否是交易核心行
                trans_match = transaction_pattern.match(line)
                if trans_match:
                    # 先处理之前缓存的描述
                    if current_desc_buffer:
                        if current_transaction:
                            # 缓存内容属于上一个交易的后置描述,先合并上一个交易
                            current_transaction['description'] += ' ' + ' '.join(current_desc_buffer)
                            all_transactions.append(current_transaction)
                            current_transaction = None
                        # 缓存内容属于当前交易的前置描述
                        desc = ' '.join(current_desc_buffer)
                        current_desc_buffer = []
                    else:
                        desc = ''
                    
                    # 提取交易核心数据
                    trans_date, value_date, money_in, money_out, balance = trans_match.groups()
                    current_transaction = {
                        'transaction_date': trans_date.strip(),
                        'value_date': value_date.strip(),
                        'money_in': money_in.strip().replace(',', '') if money_in.strip() else '0',
                        'money_out': money_out.strip().replace(',', '') if money_out.strip() else '0',
                        'balance': balance.strip().replace(',', ''),
                        'description': desc
                    }
                    
                    # 检查后续行是否是当前交易的后置描述
                    next_line_idx = line_idx + 1
                    while next_line_idx < len(lines):
                        next_line = lines[next_line_idx]
                        if not transaction_pattern.match(next_line):
                            current_desc_buffer.append(next_line.strip())
                            next_line_idx += 1
                        else:
                            break
                else:
                    # 非交易行,先缓存起来,等遇到交易行再匹配归属
                    current_desc_buffer.append(line.strip())
            
            # 处理最后一个交易的后置描述
            if current_transaction and current_desc_buffer:
                current_transaction['description'] += ' ' + ' '.join(current_desc_buffer)
                all_transactions.append(current_transaction)
    
    # 转成DataFrame,导出Excel
    if all_transactions:
        df = pd.DataFrame(all_transactions)
        df.to_excel('bank_transactions_cleaned.xlsx', index=False)
        print(f"成功提取{len(all_transactions)}条交易数据,已导出到Excel")
        return df
    else:
        print("未提取到任何交易数据,请检查正则表达式是否匹配你的PDF格式")
        return None

# 调用示例
# extract_bank_transactions("your_bank_statement.pdf")

关键细节说明

  • 正则表达式调整:这是最关键的一步!你要先打印lines变量,看你的PDF里交易行的具体格式,比如日期是MM/DD/YYYY还是DD-MM-YYYY,金额带不带货币符号(比如$、€),然后修改正则的匹配规则。比如如果金额带£,可以把正则里的[\d,.+-]+改成£?[\d,.+-]+
  • 缓存描述行:用current_desc_buffer暂存非交易行,遇到交易行时,判断这些缓存的行是属于当前交易的前置描述,还是上一个交易的后置描述,然后合并。
  • 清理金额格式:把金额里的逗号去掉,方便后续做数值计算(如果需要的话)。
  • pdfplumber参数优化:可以调整y_tolerance参数(比如设为3或5),让垂直方向上接近的行更容易被识别为相关内容,但不要设太大,否则会把不相关的行合并。

额外优化建议

  • 如果有些交易的描述行特别多(比如3行以上),可以在正则里加一些排除规则,比如排除包含“Balance”“Total”这类汇总行的内容。
  • 可以加调试日志,比如在处理每一行时打印line_idxline,方便定位哪个交易的描述没匹配对。
  • 如果你的PDF是扫描件(无法直接提取文本),那得先加OCR步骤,比如用pytesseract配合pdf2image把PDF转成图片再识别文本,但这种情况复杂度会高一些。

内容来源于stack exchange

火山引擎 最新活动