利用zip格式文件的更新功能来对xlsx文件做更改
前文说到,市面上同时对xlsx文件读写的第三方库很少见,即使有,也是将全部的内容读入内存,修改后全部写入,导致对多sheet大文件单个sheet少量的修改, 保存文件也需要很长的时间。
而xlsx文件本质上是ZIP 文件,有可能利用ZIP 文件的特点,减少多余的编码工作量,从而提升保存的速度。
ZIP 文件格式基本结构
ZIP 文件是一种常见的归档文件格式,其基本结构如下:
1. 核心组成部分
[Local File Header 1]
[File Data 1]
[Local File Header 2]
[File Data 2]
...
[Central Directory]
[End of Central Directory Record]
2. 关键结构说明
-
Local File Header:每个文件的本地头,包含文件名、压缩方法等元数据
- 签名:0x04034b50 (PK\x03\x04)
- 结构:30字节固定部分 + 文件名 + 额外字段
-
File Data:实际压缩后的文件内容
-
Central Directory:中央目录,包含所有文件的索引信息
- 签名:0x02014b50 (PK\x01\x02)
- 包含所有文件的完整路径、压缩信息、偏移量等
-
End of Central Directory Record:目录结束标记
- 签名:0x06054b50 (PK\x05\x06)
- 包含中央目录的位置和文件总数
注意到zip文件中的每个被压缩文件都有一个文件的元数据以及压缩后的数据,对于未修改的文件,这个部分是不变的,而重新写入的时候,这部分无需重新压缩, 只要把旧文件中不变的文件元数据以及压缩后的数据整体写入新文件,再压缩变更的部分,添加这部分以及中央目录即可。
实验也证明了这点,zip文件的更新比原始压缩快很多,不管是更新一个文件,删除一个文件,添加一个文件,均如此。如下所示
time zip -3 -r wps-3 x/*adding: x/[Content_Types].xml (deflated 71%)adding: x/_rels/ (stored 0%)adding: x/_rels/.rels (deflated 66%)
...adding: x/xl/worksheets/sheet1.xml (deflated 70%)adding: x/xl/_rels/ (stored 0%)adding: x/xl/_rels/workbook.xml.rels (deflated 61%)real 0m7.750s
user 0m5.491s
sys 0m0.210s
time zip -3 -u wps-3 x/[Content_Types].xml
updating: x/[Content_Types].xml (deflated 71%)real 0m3.003s
user 0m0.045s
sys 0m0.163s
root@DESKTOP-59T6U68:/mnt/c/d/10# time zip -3 -d wps-3 x/[Content_Types].xml
deleting: x/[Content_Types].xmlreal 0m1.594s
user 0m0.022s
sys 0m0.187s
time zip -3 -u wps-3 x/[Content_Types].xmladding: x/[Content_Types].xml (deflated 71%)real 0m2.943s
user 0m0.045s
sys 0m0.161s
再来观察xlsx文件增加一个sheet, 受影响的后台文件是哪些,
用两个简单的例子来说明,示例文件onesheet.xlsx是单sheet, 示例文件twosheet.xlsx是两个sheet,它们的xml文件的清单如下:
unzip -l onesheet.xlsx
Archive: onesheet.xlsxLength Date Time Name
--------- ---------- ----- ----1530 1980-01-01 00:00 xl/worksheets/sheet1.xml823 2025-08-23 09:19 [Content_Types].xml298 2025-08-23 09:19 _rels/.rels700 2025-08-23 09:19 xl/workbook.xml1247 2025-08-23 09:19 xl/styles.xml567 2025-08-23 09:19 xl/_rels/workbook.xml.rels421 1980-01-01 00:00 xl/sharedStrings.xml
--------- -------5586 7 files
unzip -l twosheet.xlsx
Archive: twosheet.xlsxLength Date Time Name
--------- ---------- ----- ----1530 1980-01-01 00:00 xl/worksheets/sheet1.xml671 1980-01-01 00:00 xl/worksheets/sheet2.xml959 2025-08-23 09:19 [Content_Types].xml298 2025-08-23 09:19 _rels/.rels767 2025-08-23 09:19 xl/workbook.xml1247 2025-08-23 09:19 xl/styles.xml708 2025-08-23 09:19 xl/_rels/workbook.xml.rels458 1980-01-01 00:00 xl/sharedStrings.xml
--------- -------6638 8 files
可见,除了新增的xl/worksheets/sheet2.xml, 其余7个文件中, xl/worksheets/sheet1.xml、_rels/.rels、xl/styles.xml这三个文件大小未变,用cmp比较以确认内容。
unzip onesheet.xlsx -d 1shunzip twosheet.xlsx -d 2shcmp -l 1sh/_rels/.rels 2sh/_rels/.rels
cmp -l 1sh/xl/styles.xml 2sh/xl/styles.xml
cmp 1sh/xl/worksheets/sheet1.xml 2sh/xl/worksheets/sheet1.xml
1sh/xl/worksheets/sheet1.xml 2sh/xl/worksheets/sheet1.xml differ: byte 1046, line 1
cmp -l 1sh/xl/worksheets/sheet1.xml 2sh/xl/worksheets/sheet1.xml
1046 64 63
1047 61 70
1048 66 71
1049 64 67
1050 63 67
1052 60 66
1053 63 66
结果后两个确实一致,因此它们无需重新压缩。而sheet1.xml不同,经核对,这是由于产生数据用了datetime.datetime.today()函数,这个函数并不是字面上的返回日期,而是返回带毫秒精度的日期和时间,
>>> import datetime
>>> print(datetime.datetime.today())
2025-08-23 10:06:42.466294>>> print(datetime.datetime.today())
2025-08-23 10:07:36.159872>>> print(datetime.datetime.strptime('2000-10-10','%Y-%m-%d'))
2000-10-10 00:00:00
将它改成常数后,两个xlsx文件中的两个sheet1.xml文件就完全一致了。
然后,重新分别解压缩,我们用2sh目录下的文件去更新onesheet.xlsx。
zip -3 -u -r ../onesheet.xlsx * -x xl/worksheets/sheet1.xml _rels/.rels xl/styles.xml
updating: [Content_Types].xml (deflated 69%)
updating: xl/workbook.xml (deflated 44%)
updating: xl/_rels/workbook.xml.rels (deflated 67%)adding: _rels/ (stored 0%)adding: xl/ (stored 0%)adding: xl/worksheets/ (stored 0%)adding: xl/worksheets/sheet2.xml (deflated 43%)adding: xl/_rels/ (stored 0%)unzip -l ../onesheet.xlsx
Archive: ../onesheet.xlsxLength Date Time Name
--------- ---------- ----- ----1520 1980-01-01 00:00 xl/worksheets/sheet1.xml959 2025-08-23 10:19 [Content_Types].xml298 2025-08-23 10:19 _rels/.rels767 2025-08-23 10:19 xl/workbook.xml1247 2025-08-23 10:19 xl/styles.xml708 2025-08-23 10:19 xl/_rels/workbook.xml.rels421 1980-01-01 00:00 xl/sharedStrings.xml0 2025-08-23 10:19 _rels/0 2025-08-23 10:19 xl/0 2025-08-23 10:19 xl/worksheets/671 1980-01-01 00:00 xl/worksheets/sheet2.xml0 2025-08-23 10:19 xl/_rels/
--------- -------6591 12 files
这个命令把空目录也当作文件添加进去了,需要排除,方法是添加-D选项,如下所示:
zip -3 -u -D -r ../onesheet.xlsx * -x xl/worksheets/sheet1.xml _rels/.rels xl/styles.xml
updating: [Content_Types].xml (deflated 69%)
updating: xl/workbook.xml (deflated 44%)
updating: xl/_rels/workbook.xml.rels (deflated 67%)adding: xl/worksheets/sheet2.xml (deflated 43%)
unzip -l ../onesheet.xlsx
Archive: ../onesheet.xlsxLength Date Time Name
--------- ---------- ----- ----1520 1980-01-01 00:00 xl/worksheets/sheet1.xml959 2025-08-23 10:19 [Content_Types].xml298 2025-08-23 10:19 _rels/.rels767 2025-08-23 10:19 xl/workbook.xml1247 2025-08-23 10:19 xl/styles.xml708 2025-08-23 10:19 xl/_rels/workbook.xml.rels421 1980-01-01 00:00 xl/sharedStrings.xml671 1980-01-01 00:00 xl/worksheets/sheet2.xml
--------- -------6591 8 filesunzip -l ../twosheet.xlsx
Archive: ../twosheet.xlsxLength Date Time Name
--------- ---------- ----- ----1520 1980-01-01 00:00 xl/worksheets/sheet1.xml671 1980-01-01 00:00 xl/worksheets/sheet2.xml959 2025-08-23 10:19 [Content_Types].xml298 2025-08-23 10:19 _rels/.rels767 2025-08-23 10:19 xl/workbook.xml1247 2025-08-23 10:19 xl/styles.xml708 2025-08-23 10:19 xl/_rels/workbook.xml.rels458 1980-01-01 00:00 xl/sharedStrings.xml
--------- -------6628 8 files
除了xl/sharedStrings.xml,都已经成功更新了。由于共享字符串不正确,sheet2无法读取自己的字符串,显示空白。
这个文件无法更新的原因在于,它的修改日期不正常,而xl/styles.xml是正常的。
stat xl/sharedStrings.xmlFile: xl/sharedStrings.xmlSize: 458 Blocks: 0 IO Block: 4096 regular file
Device: 52h/82d Inode: 2814749767192658 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2025-08-23 10:54:46.357751400 +0800
Modify: 1980-01-01 00:00:00.000000000 +0800
Change: 2025-08-23 10:19:43.671838700 +0800Birth: -
stat xl/styles.xmlFile: xl/styles.xmlSize: 1247 Blocks: 8 IO Block: 4096 regular file
Device: 52h/82d Inode: 9570149208233981 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2025-08-23 10:19:12.000000000 +0800
Modify: 2025-08-23 10:19:12.000000000 +0800
Change: 2025-08-23 10:19:43.649838700 +0800Birth: -
用touch命令修改文件日期后,就可以更新了
touch 2sh/xl/sharedStrings.xml
stat 2sh/xl/sharedStrings.xmlFile: 2sh/xl/sharedStrings.xmlSize: 458 Blocks: 0 IO Block: 4096 regular file
Device: 52h/82d Inode: 5910974510945701 Links: 1
Access: (0777/-rwxrwxrwx) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2025-08-23 11:32:13.877740100 +0800
Modify: 2025-08-23 11:32:13.877740100 +0800
Change: 2025-08-23 11:32:13.877740100 +0800Birth: -
cd 2sh
zip -3 -u -D -r ../onesheet.xlsx * -x xl/worksheets/sheet1.xml _rels/.rels xl/styles.xml
updating: xl/sharedStrings.xml (deflated 50%)
sheet2显示正确了。
在实际操作中,新的xl/sharedStrings.xml 是程序动态生成,而不是从xlsx中解压出来的,应该不存在日期不对的问题。