本文最后更新于 over 4 years ago,文中所描述的信息可能已发生改变。
就在今天,我突然想用python编写一个明日方舟apk自动解包的程序。既然要解包,首先要下载包,众所周知在windows上运行py文件是不如直接运行exe方便的。这时就需要pyinstaller将py转换成exe。下面是本人在实际使用过程中的一些心得与踩坑,也欢迎大家留言交流。
什么是pyinstaller?
简单的说,它是一个能将你编写的py项目打包成可执行文件的python第三方模块~
环境准备
使用pipenv
[*] 如果在原生环境中使用pyinstaller,可能会造出一个巨大的文件。这是因为原生环境还包含你所安装的其他模块,这些模块未必在你的项目中所使用,但是pyinstaller可没有人那么智能,它会一股脑给你全打包带走,十分贴心(雾)
就拿download.py为例,源文件才4KB,硬生生给我干到了268MB,其效率之低,可想而知。
Pipenv 是一款管理虚拟环境的命令行软件,简单来讲,它可以创建一个只在某个目录下的局部 Python 环境,而这个环境是可以和全局环境脱离开的。
步骤如下:
- 安装 Pipenv
pip install pipenv
- 选一个好目录做我们的虚拟环境,然后在该目录下:
pipenv install --python 3.7
这样就可以在目录下创建一个局部的环境了,我这里设为 3.7 是因为我自己用的是 3.7,具体设什么根据自己的情况来定。
- 在命令行下激活环境
pipenv shell
输入这个命令,我们就进入到了新建的虚拟环境。如果你这时候使用命令 pip list
并发现里面只有很少的库,这就说明我们成功进入虚拟环境了(有点像 Conda)。
- 安装依赖的库
在虚拟环境下安装 Pyinstaller 和你自己的脚本依赖的第三方库,比如我的就是:
pipenv install pyinstaller
pipenv install setuptools==44.0.0 #避免使用最新
pipenv install urllib3
pipenv install tqdm
再次查看 pip list
时,如果都成功安装好了,我们就可以开始打包了。
- 进入你的项目目录,运行pyinstaller即可。
使用pipenv后的对比:
😀
使用pyinstaller打包单个文件
用上面的方法创建好打包环境后,现在可以开始打包你的py文件啦~
- Pyinstaller 用法很简单,在对应的主调 py 文件的目录下,运行:
pyinstaller [<args>] Target.py
介绍一下 Pyinstaller 常用的参数用法:
--distpath
: 打包到哪个目录下-w
: 指定生成 GUI 软件,也就是运行时不打开控制台-c
: 运行时打开控制台-i
: 指定打包后可执行文件的图标--clean
: 在构建之前清理PyInstaller缓存并删除临时文件
关于打包成什么样,有两种选择:
-D
: 创建包含可执行文件的单文件夹包,同时会有一大堆依赖的 dll 文件,这是默认选项-F
: 只生成一个 .exe 文件,如果项目比较小的话可以用这个,但比较大的话就不推荐
最后来看看我使用的参数:
pyinstaller -F -i A.ico download.py
若不指定distpath,则默认将exe文件保存在dist目录。
更多的用法可以自行百度
踩坑
生成的exe文件过大
- 上文已经说过,使用pipenv即可解决。
生成的exe文件无法运行
这个问题比较特殊,这里讲讲我遇到的错
能正常生成exe
如图所示,提示缺少模组,这里有两个思路:
成功生成exe文件时,也会生成已**.spec**为后缀的文件,这是用来指示pyinstaller如何打包的文件,在
hiddenimports
中添加需要忽略的包(注意引号),如下所示:再次尝试运行pyinstaller,若提示其他缺少的包,依次添加。
根据网络上的资料,造成这种兼容性问题的原因是
setuptools
的版本过高,建议使用44.0.0
版本,至于安装,想必不用再说了吧。总之推荐这种方法,前提是降级setuptools不会对你现有的程序造成错误。某天发现生成的exe又过大,这时请检查你的虚拟环境是否缺少包。在我的实践中发现,虚拟环境的包会在某些条件下变为最开始的状态。
不能正常生成exe
多半是源程序不合规范
标准格式要包含如下内容:
def main():
print('hello world!')
if __name__ == '__main__':
main()
download.py示例:
tips: 想让tqdm在同一行显示,需要注意
ncols=80
参数和miniters=0
,前者是行宽,后者是刷新频率
import urllib.request as ur
import tqdm
# 进度条类
class TqdmUpTo(tqdm.tqdm):
# Provides `update_to(n)` which uses `tqdm.update(delta_n)`.
last_block = 0
def update_to(self, block_num=1, block_size=1, total_size=None):
if total_size is not None:
self.total = total_size
self.update((block_num - self.last_block) * block_size)
self.last_block = block_num
def main():
link = "https://ak.hypergryph.com/downloads/android_lastest"
local = 'Arknights.zip'
try:
with TqdmUpTo(unit='B', unit_scale=True, unit_divisor=1024, miniters=0, desc="Arknights",
dynamic_ncols=False, ncols=80) as t: # 继承至tqdm父类的初始化参数
ur.urlretrieve(link, filename=local, reporthook=t.update_to, data=None)
input("按任意键继续...")
except KeyboardInterrupt:
t.close()
raise
t.close()