使用Tabula转换PDF为CSV时特殊字符乱码、列提取异常问题咨询
问题根因
现有代码无法正常输出结果的核心原因有两个:
- 编码配置错误:代码中指定的
cp1252是西欧字符集,不支持土耳其语特殊字符(ğ、ü、ç、İ、ş等),必然出现乱码。 - 识别逻辑缺陷:Tabula默认开启自动表格检测,会被对账单顶部的姓名/账期文本、底部页脚备注、跨页重复表头干扰,导致列识别偏移;另外代码中CSV输出路径后缀错写为
.pdf,本身文件格式就不符合要求。
修正后的Tabula实现方案
针对格式固定的银行对账单,不要依赖自动检测,手动指定编码、表格区域、列坐标即可解决问题,完整代码如下:
import tabula import pandas as pd import re # 基础路径配置 PDF_FILE_PATH = "C:/Users/Desktop/abstract/abstract.pdf" CSV_SAVE_PATH = "C:/Users/Desktop/abstract/transaction.csv" # 土耳其语文档使用cp1254编码,兼容所有土语特殊字符 DOC_ENCODING = "cp1254" # 四个目标列的X轴坐标,可通过Tabula桌面预览工具快速测量后微调 COLUMN_POSITIONS = [122, 278, 447, 532] # 交易表格在页面中的边界坐标,格式为[上, 左, 下, 右],排除顶部账户信息区、底部页脚区 TABLE_BOUNDARY = [118, 18, 782, 585] # 第一步:提取全页文本,单独获取姓名、账期信息 full_page_text = tabula.read_pdf( PDF_FILE_PATH, encoding=DOC_ENCODING, pages="all", output_format="text", silent=True ) account_name = re.search(r"Müşteri Adı\s*:\s*(.+)", full_page_text).group(1).strip() statement_period = re.search(r"Dönem\s*:\s*(.+)", full_page_text).group(1).strip() print(f"账户姓名:{account_name}") print(f"对账单账期:{statement_period}") # 第二步:按指定区域提取交易表格 page_tables = tabula.read_pdf( PDF_FILE_PATH, encoding=DOC_ENCODING, pages="all", area=TABLE_BOUNDARY, columns=COLUMN_POSITIONS, lattice=True, # 带实线边框的表格开启该参数,识别准确率更高 pandas_options={"header": None}, multiple_tables=True, silent=True ) # 合并跨页表格,过滤重复表头、空行 transaction_list = [] for table_df in page_tables: table_df.columns = ["İşlem tarihi", "Açıklama", "Tutar", "Bankomat Para"] # 剔除重复的表头行、全空行 table_df = table_df[table_df["İşlem tarihi"] != "İşlem tarihi"].dropna(how="all") transaction_list.append(table_df) final_transaction_df = pd.concat(transaction_list, ignore_index=True) # 保存为CSV,使用utf-8-sig编码保证Excel直接打开无乱码 final_transaction_df.to_csv(CSV_SAVE_PATH, index=False, encoding="utf-8-sig") print(f"交易记录已成功存储至:{CSV_SAVE_PATH}")
更优替代方案
如果Tabula调整参数后仍存在列偏移问题,可换用pdfplumber库,对银行类带边框固定格式表格的识别精度比Tabula更高,字符提取容错性更好,参考实现如下:
import pdfplumber import pandas as pd import re PDF_FILE_PATH = "C:/Users/Desktop/abstract/abstract.pdf" CSV_SAVE_PATH = "C:/Users/Desktop/abstract/transaction.csv" account_name = "" statement_period = "" transaction_records = [] with pdfplumber.open(PDF_FILE_PATH) as pdf: for page_idx, page in enumerate(pdf.pages): # 第一页提取顶部账户信息 if page_idx == 0: page_text = page.extract_text() account_name = re.search(r"Müşteri Adı\s*:\s*(.+)", page_text).group(1).strip() statement_period = re.search(r"Dönem\s*:\s*(.+)", page_text).group(1).strip() # 按固定线位提取交易表格 transaction_table = page.extract_table(table_settings={ "vertical_strategy": "lines", "horizontal_strategy": "lines", "explicit_vertical_lines": [122, 278, 447, 532, 590], "explicit_horizontal_lines": [118, 782] }) # 逐行清洗数据 for row in transaction_table: if not row[0] or row[0] == "İşlem tarihi": continue transaction_records.append({ "交易日期": row[0].strip(), "交易说明": row[1].strip(), "交易金额": row[2].strip(), "赠金(Bankomat Para)": row[3].strip() }) # 存储结果 pd.DataFrame(transaction_records).to_csv(CSV_SAVE_PATH, index=False, encoding="utf-8-sig")
注意事项
- 处理土耳其语等非西欧语言文档时,必须先匹配对应语言的编码,禁止直接默认使用
cp1252/gbk等不兼容字符集。 - 固定格式的文档不要依赖工具的自动识别能力,手动指定表格边界、列坐标可以把识别准确率提升到100%,避免反复调试。
- CSV存储优先使用
utf-8-sig编码,兼容Windows系统下Excel直接打开的场景,不会出现二次乱码。 - 跨页表格必须手动过滤重复表头、页脚混入的非交易行,不能直接拼接工具输出的原始表格结果。
内容的提问来源于stack exchange,提问作者user14178341
相关产品推荐
相关产品推荐

