查看: 66|回复: 0

Python批量调整Excel格式并导出PDF:win32com解决xls合并单元格与样式丢失问题

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
在日常办公中,经常需要批量处理Excel文件——调整列宽、字号、自动换行、行高等格式,然后导出为A4横向PDF。最近遇到一个实际需求:处理63个.xls格式的个人所得税扣缴申报表,每个表有51列,包含三级合并表头、大量数据行和合计行,必须将所有列缩放到一页宽,并保持原有边框和背景色。如果手动操作,工作量巨大且容易出错。本文将介绍三种技术方案的对比,重点分享基于win32com(Excel COM接口)的成功方案,以及开发中遇到的五个关键问题与解决方法。

需求梳理
原始表格结构:Row 1-4为标题/公司信息;Row 5-7为三级合并表头(跨行跨列合并);Row 8为列序号行;Row 9起为数据行(每文件1~47行不等);Row N为合计行;Row N+1起为声明/签章等。格式要求包括:固定列宽(如序号2.25、姓名4.5等,单位字符)、动态列宽(根据合计行值的字符长度映射,例如字符数4对应宽度3,字符数5对应4.18等,合计值为“-”时取数据行最长值)、字号9号加自动换行(姓名、身份证件类型等列,仅数据行)、行高数据行33.8磅、合计行21磅,导出PDF要求A4横向、所有列缩放至一页宽、左右边距极限最小。

技术选型三方案对比
方案一:xlrd + xlwt(失败)
这是处理.xls老格式的经典组合,但遇到两个致命问题。首先,xlrd读取的Font对象与xlwt的Font对象属性不兼容,例如xlrd的Font有charset属性,xlwt的Font没有;underline_type与underline命名不同等。更严重的是,样式复制极其困难——合并单元格的边框、背景色、对齐方式等极易丢失,报错如“AttributeError: 'Font' object has no attribute 'charset'”。对于带有三级合并表头和大量边框的报表,样式丢失不可接受,因此放弃。

方案二:openpyxl(不可行)
openpyxl仅支持.xlsx格式,而源文件是.xls,需先转换为.xlsx。转换过程本身可能丢失格式,且引入额外步骤,不符合零损失保真的要求。

方案三:win32com(Excel COM接口)(成功)
最终采用pywin32调用Excel应用程序的COM接口,本质上是用Python操控Excel应用程序本身,等于手动操作Excel。原生支持.xls,格式零丢失,可以直接控制列宽、字号、自动换行等属性,并调用ExportAsFixedFormat导出PDF。核心代码极简:
  1. import win32com.client as win32
  2. excel = win32.Dispatch('Excel.Application')
  3. excel.Visible = False
  4. excel.DisplayAlerts = False
  5. wb = excel.Workbooks.Open(r'D:\path\to\file.xls')
  6. ws = wb.Worksheets(1)
  7. # 设置列宽
  8. ws.Cells(1, col).EntireColumn.ColumnWidth = 4.5
  9. # 设置字号和自动换行
  10. cell.Font.Size = 9
  11. cell.WrapText = True
  12. # 导出PDF
  13. ws.ExportAsFixedFormat(Type=0, Filename=pdf_path)
复制代码

开发过程中遇到的关键问题
问题1:UsedRange在合并单元格上翻车
使用ws.UsedRange.Rows.Count获取行数时,合并单元格导致返回值不准甚至报错。解决方案:采用Cells(65536, 1).End(-4162).Row(相当于Ctrl+↑)或直接遍历查找合计行(列A含“合”和“计”)。函数示例:
  1. def get_total_row(ws):
  2.     for row in range(1, 100):
  3.         val = ws.Cells(row, 1).Value
  4.         if val is not None and '合' in str(val) and '计' in str(val):
  5.             return row
  6.     return None
复制代码

问题2:DisplayAlerts在某些Excel版本上报错
设置excel.DisplayAlerts = False部分环境下抛异常。解决:用try/except包裹,不影响核心功能。

问题3:PDF只导出了第一页(最隐蔽的Bug)
多页Excel导出PDF后发现只有第一页内容。原因是在ExportAsFixedFormat中错误地指定了From=1和To=1参数。正确做法是去掉这两个参数,默认导出全部页面:
  1. ws.ExportAsFixedFormat(
  2.     Type=0,
  3.     Filename=pdf_path,
  4.     IgnorePrintAreas=False,
  5.     OpenAfterPublish=False
  6. )
复制代码

问题4:.bat启动脚本的中文乱码
为方便双击运行,编写.bat文件指定已安装pywin32的Python路径。但.bat文件若包含中文注释或echo,Windows cmd默认GBK编码下UTF-8中文会变成乱码当作命令执行。解决方案:.bat文件只使用纯英文/ASCII字符,彻底避免编码问题。

问题5:合计行值的类型不统一
同样数字列,有的合计值为float(如10754.93),有的是str(如'10754.93'),还有“-”字符串。需统一处理才能正确计算字符长度。自定义函数compute_width:对数字格式化两位小数,对空值或“-”则查数据行最长值。
  1. def compute_width(total_val, ws, col_1based, data_rows):
  2.     s = str(total_val).strip()
  3.     if s == '' or s == '-':
  4.         max_len = max(len(str(ws.Cells(r, col_1based).Value or ''))
  5.                       for r in data_rows)
  6.     else:
  7.         formatted = f'{float(total_val):.2f}'
  8.         char_count = len(formatted)
  9.     return WIDTH_MAP.get(char_count, 3)
复制代码

经验总结
场景推荐方案:简单读写xlsx不需保留样式用openpyxl;简单读写xls不需保留样式用xlrd+xlwt;需精确保留样式+修改格式或导出PDF,首选win32com(COM接口)。核心心得:选对工具比写对代码更重要,COM接口本质是Excel自己在操作,保真度高;.bat文件慎用中文;PDF导出必须验证多页场景,避免From/To参数的误解。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-18 11:01 , Processed in 0.034426 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部