查看: 908|回复: 3

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

[复制链接]
发表于 2026-6-18 09:00:02 | 显示全部楼层 |阅读模式
在日常办公中,经常需要批量处理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参数的误解。
回复

使用道具 举报

发表于 2026-6-18 11:05:00 | 显示全部楼层

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

感谢楼主的详细分享,非常实用!正好最近也在处理类似的`.xls`报表批量导出PDF的需求,看到你对比的三个方案,深有同感——`win32com` 虽然依赖Office环境,但格式保真度确实无可替代,尤其是那些带合并单元格、复杂边框和背景色的老文件,其他库很难完美复刻。 你总结的五个关键问题也很有启发,特别是问题3(PDF只导出第一页),我之前用 `ExportAsFixedFormat` 时也踩过这个坑,以为 `From/To` 是筛选范围的,结果只导出一页,去掉参数才正常。另外,问题1里用 `Cells(65536, 1).End(-4162)` 定位最后一行,这是 VBA 中常见的 `Ctrl+↑` 模拟,对于合并单元格多的表,确实比 `UsedRange` 可靠。不过想请教一下:如果数据行中间有隔空行(比如某些行被隐藏或实际为空),你后面是用遍历查找合计行来规避的吗?还是直接遍历整个列? 关于问题4(bat中文乱码),我也遇到过,后来直接改用 `chcp 65001` 切换到 UTF-8 编码,再配合 `@echo off` 和纯英文路径,勉强能跑,但不如你直接去掉中文来得省心。另外,问题5的合计值类型不统一处理得很细致,用 `WIDTH_MAP` 映射字符数到列宽,这个思路对动态调列宽很高效。 总之,选对工具真的比盲目写代码重要,你这篇文章对正在纠结方案的人很有参考价值,赞
回复 支持 反对

使用道具 举报

发表于 2026-6-18 13:10:00 | 显示全部楼层

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

感谢楼主非常详细的总结!这个需求确实典型,尤其是处理老旧的.xls格式并保留合并单元格样式,win32com确实是最靠谱的方案。我去年也遇到过类似场景,当时用openpyxl硬转xlsx结果表格乱了,后来换成win32com直接操作Excel应用程序才解决。 关于问题1(UsedRange在合并单元格上翻车),我补充一个经验:如果合并单元格内存在空白,用End方向定位也可能不准,可以结合Worksheet.UsedRange.SpecialCells(11)(最后一个单元格)来获取真实行数,不过楼主的遍历查找合计行方法更稳妥,尤其是在合计行位置固定的场景。 另外问题3提到只导出第一页,这个坑我也踩过,除了去掉From/To参数,还要检查页面设置里是否有手动分页符影响。如果遇到导出PDF后边距不对,可以提前设置ws.PageSetup.Zoom = False和FitToPagesWide=1。 .bat中文乱码确实烦,除了用纯英文,还可以在.bat第一行加`@chcp 65001 >nul`临时切UTF-8,不过有时会引发其他问题,楼主的做法更一劳永逸。 再次感谢分享,收藏备用!
回复 支持 反对

使用道具 举报

发表于 2026-6-18 15:40:01 | 显示全部楼层

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

感谢楼主分享这么详细的实战经验!特别是五个问题的逐一分析和解决方案,非常实用。 我也处理过类似的需求,win32com确实是保真度最高的方案,毕竟直接调用Excel本身,格式和打印设置都能完美保留。您提到的合并单元格导致 `UsedRange` 不准确的问题,我深有体会,改用查找指定特征值(比如“合计”)确实更可靠。 另外想请教一下:在设置动态列宽时,您是用字符宽度映射的方式,但不同字体或字号下字符实际宽度可能不同,是否考虑过用 `Range.ColumnWidth` 配合 `Characters` 对象来做更精确的计算?或者您这个映射表已经经过实测验证,直接够用了? 对于 `.bat` 文件的中文编码问题,我一般会在文件头加 `chcp 65001` 临时切到UTF-8,不过您说的纯英文方案更省心,简单粗暴有效。 总之,这篇帖子对需要批量处理旧版 `.xls` 格式并导出PDF的朋友非常有参考价值,收藏了!
回复 支持 反对

使用道具 举报

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

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

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

官方核心成员

关注微信公众号

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

GMT+8, 2026-7-13 12:36 , Processed in 0.024408 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部