刚入行的时候,我也遇到过这个让人头疼的问题。项目写好了,功能也跑通了,高高兴兴用PyInstaller打包。一打开文件夹,嘿,三百多兆。发给别人用,对方第一反应是“你这写的啥软件,比整个游戏还大”。那种尴尬,做开发的人都懂。
后来我盯着别人写的小工具看了好久。一样是Python写的,人家的exe才十几兆,有的甚至几兆。差距到底在哪?我花了一个周末反复测试,把踩过的坑和摸索出来的经验写下来。这些法子没有高深理论,全是能立刻上手的。
第一步得搞清楚打包时哪些东西是真正需要的。很多人一上来就用默认参数打包。默认模式会把整个Python解释器和一堆标准库都塞进去。你明明只用了个requests库,它连unittest、xml、email这些八竿子打不着的模块全带上了。打开PyInstaller生成的warn文件看一眼就知道有多浪费。用--exclude-module参数可以指定不需要的模块。比如pyinstaller --exclude-module unittest --exclude-module email your_script.py。一条一条加,能砍掉不少体积。
用虚拟环境能省下一大半的体积。很多人开发机上的Python环境装了几十个包。打包的时候PyInstaller会扫描整个site-packages目录,把能关联上的都打包进去。哪怕你代码里只用了三个包,它也可能把相关的依赖全拉进来。正确的做法是创建一个干净的虚拟环境,只用pip安装必要的包。在这个环境里打包,生成的exe会小很多。我试过一次,直接从两百多兆减到了五十兆以内。
选对打包模式也很关键。默认的onefile模式会把所有文件解压到一个临时目录再运行。这看起来方便,实际上会额外多出临时文件占用的空间。如果用户不介意文件夹形式,用onedir模式能省掉压缩和解压的开销。onedir模式打包出来的目录看起来文件多,但总大小通常比onefile小百分之二十到三十。而且启动速度更快,因为不用先解压。
还有个容易被忽略的地方是资源文件。很多人在代码里写死了图片、字体、数据文件的路径。打包的时候把这些资源全部放进去。其实有些图片可以换成矢量格式,或者直接在代码里用base64编码。小图标用PNG格式,能压缩到几KB。字体文件更是大头,中文字体动辄十几兆。可以用字体子集化工具,只保留用到的几个汉字,能缩减到几十KB。我见过最极端的情况,一个日志分析工具,因为嵌了个完整的楷体字体包,体积暴涨了三十兆。
用UPX压缩壳能让exe体积缩到一半。UPX是个免费的工具,能对exe进行压缩。PyInstaller本身就支持集成UPX。把upx.exe放在系统路径里,打包时加上--upx-dir参数就行。UPX压缩对纯逻辑代码效果很好,但如果exe里已经包含了大量压缩过的图片或二进制数据,压缩效果会打折扣。不过大部分工具类的代码,用UPX都能省掉百分之四十到五十的体积。
还有一点经验是处理第三方库。有些库的导入方式会导致PyInstaller错误地打包大量无关文件。比如openpyxl库,如果你只用到读写Excel的功能,它会把整个内置的图表引擎、图像处理模块都带进来。这时候可以用hook文件手动指定哪些模块不打包。在项目目录下建一个文件夹叫hooks,里面写个hook-openpyxl.py,告诉PyInstaller排除某些子模块。网上有现成的hook模板,拿来改改就能用。
打包完成后,记得把生成的exe拖进PE文件分析工具里看一眼依赖的DLL。有些DLL是系统自带的,完全不用打包。PyInstaller有时候会误把系统DLL也复制进来。手动删掉这些多余的DLL,又能省掉几兆。前提是你得确认这个DLL在目标用户的系统里存在。Windows 10以上版本的常见系统DLL基本都有保障。
最后一步是把exe用7z极限压缩。这个操作不会影响运行,纯粹是为了分发方便。有些压缩软件对exe的压缩率很高。一个五十兆的exe,用7z极限模式能压到十几兆。发给别人的时候,打包成自解压单文件,看起来还是单个exe,但体积小得多。用户双击自解压文件,它会自动解压到临时目录再运行。虽然启动慢了半秒,但下载速度快了十倍。
这些法子都是我亲手试过的。从三百兆减到三十兆,不是吹牛。你可以先拿一个小工具练手,把打包流程走熟。每个步骤省下一点,总效果就很明显。最关键的还是心态,别觉得打包是最后一步随便糊弄就行。它跟写代码一样,值得认真对待。