Python提取含多行垂直居中单元格的PDF表格及解决银行交易数据描述错位问题的技术问询
Python提取含多行垂直居中单元格的PDF表格及解决银行交易数据描述错位问题的技术问询
兄弟我太懂这种痛苦了!之前处理某银行的交易PDF时,也被这种乱跑的描述行折腾得头大——一会儿在交易数据上面,一会儿在下面,还动不动拆成两三行,导Excel的时候全乱套。本质问题就是这类银行PDF不是标准的结构化表格,而是流式布局,pdfplumber直接提取的文本会把交易核心数据和描述行拆成独立的行,完全脱节。
给你一套亲测有效的解决思路和代码,核心就是先识别交易核心行,再为每个交易行匹配前后的描述内容:
核心思路
- 定位交易行:交易行一定有固定格式特征——比如两个日期(交易日/记账日)、三列金额(转入/转出/余额),用正则表达式就能精准匹配。
- 合并描述行:不管描述在交易行的前面还是后面,只要不是交易行的内容,就合并到对应交易的描述字段里。
- 结构化导出:把整理好的交易数据转成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_idx和line,方便定位哪个交易的描述没匹配对。 - 如果你的PDF是扫描件(无法直接提取文本),那得先加OCR步骤,比如用
pytesseract配合pdf2image把PDF转成图片再识别文本,但这种情况复杂度会高一些。
内容来源于stack exchange




