将python项目(django/flask)打包成exe和安装包

  • Post author:
  • Post category:python


  1. Python是解释型语言,Flask或Django项目如果部署,源码可能会泄露,因此可以把项目打包成exe,来保护源码。
  2. 需要用到的工具:

    • pyinstaller:把python项目打包成不同平台的可执行文件
    • nsis:NSIS(Nullsoft Scriptable Install System)是一个开源的 Windows 系统下安装程序制作程序,它提供了安装、卸载、系统设置、文件解压缩等功能。这如其名字所指出的那样,NSIS 是通过它的脚本语言来描述安装程序的行为和逻辑的



打包Flask项目



写一个简单的Flask项目

在这里插入图片描述



下载pyinstaller

pip install pyinstaller
可选参数 示例 说明

-F

pyinstaller -F demo.py
只在dist文件夹中生成一个程序demo.exe文件,适用于一个模块没有多依赖.py文件

-D

pyinstaller -D demo.py
默认选项,除了主程序demo.exe外,还会在在dist文件夹中生成很多依赖文件,推荐使用这个

-c

pyinstaller -c demo.py
默认选项,只对windows有效,使用控制台

-w

pyinstaller -w demo.py
只对windows有效,不使用控制台

-p

pyinstaller -p D:\project\demo.py
设置导入路径

-i

pyinstaller -i D:\demo.ico demo.py
给生成的demo.exe文件设置一个自定义的图标



进入到项目路径下,执行

# run.py 是flask项目的执行文件,app.run所在py文件
	pyinstaller -D run.py    
	
# 可以看到项目路径下有
	-build文件夹
	-dist文件夹:重要,下有run文件夹(py文件名字),有个run.exe(py文件的名字)
	-run.spec
  
# 把pro_flask这个文件夹,拷贝到dist下的run文件夹
	-因为flask项目有静态文件和html文件,如果不拷贝过去,静态文件和html文件会找不到
	-我们为了隐藏代码,可以把所有python的代码都删除,只留static和templates文件夹

在这里插入图片描述



运行exe,测试

# 到dist/run文件夹下,双击 run.exe,启动
# 浏览器访问

在这里插入图片描述



使用nsis把文件夹打包成windows的安装包



下载安装nsis

nsis:NSIS(Nullsoft Scriptable Install System)是一个开源的 Windows 系统下安装程序制作程序,它提供了安装、卸载、系统设置、文件解压缩等功能。这如其名字所指出的那样,NSIS 是通过它的脚本语言来描述安装程序的行为和逻辑的
  
利用 nsis 把刚刚的dist的run文件夹打包成windows的安装包

在这里插入图片描述



把dist文件夹下的run文件夹压缩成zip

在这里插入图片描述



使用nsis把压缩包,做成windows安装文件

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



打包Django项目



写一个简单的django项目,准备静态文件

# 在django项目的配置文件中修改settings.py
STATIC_ROOT = os.path.join(BASE_DIR, 'static', 'static_root')

# 修改urls.py
from django.conf.urls import static
from django.conf import settings
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.index),
]
urlpatterns += static.static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

# 执行 静态文件收集
python manage.py collectstatic  # 会把所有静态文件收集到项目的static路径下的static_root文件夹下



使用 pyinstaller打包

# 进入到项目路径下
pyinstaller -D manage.py

# 生成dist文件夹,build文件夹和manage.spec文件



执行打包成的exe

# 此时在项目路径下会生成dist文件夹,内部manage文件夹下有manage.exe

manage.exe runserver 

# 运行服务是会提示No module named XXX
执行会报错,提升缺少模块,我的提示少app01.apps


#########
这是因为Django有些module不会自动收集,需要手动添加
解决方法:在manage.spec文件中修改hiddenimports=[]为hiddenimports=['app01.apps','users.apps',],提示缺少什么module就在此处添加什么。
#########

修改manage.spec,hiddenimports=['app01.apps',]

# 再重新编译一下,运行

pyinstaller manage.spec



把templates和static文件夹copy到dist/manage文件夹下



运行项目

manage.exe runserver 0.0.0.0:8080



注意一些坑

# 问题1:运行服务提示No module named XXX

这是因为Django有些module不会自动收集,需要手动添加
解决方法:在manage.spec文件中修改hiddenimports=[]为hiddenimports=['users','users.apps',],提示缺少什么module就在此处添加什么。


# 问题2:打开网页出现TemplateDoesNotExist 错误

解决方法:把项目中的模板文件templates拷贝到dist下的manage文件夹,刷新页面即可。

# 问题3:静态文件找不到

1、首先在项目中的settings文件中添加如下代码,其中static是项目中的静态文件位置,static_root是static下的一个空文件夹,然后执行python manage.py collectstatic命令将静态文件收录到static_root中

STATIC_ROOT = os.path.join(BASE_DIR, 'static', 'static_root')
2、然后在urls.py中添加如下代码:

from django.conf.urls import static
from project_1 import settings
urlpatterns += static.static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)



打包其他项目(小工具)



准备一个多文件的小工具源码

在这里插入图片描述

注意路径处理,使用

__file___

的地方,需要改成

"./"

,否则目录会创建到AppData的临时目录中,AppData是windows规定给软件倒垃圾的地方,但这与需求不符合。

# 目录
input_excel_folder = os.path.join("./", "in", "excel")
input_word_folder = os.path.join("./", "in", "word")
# 模板目录
template_folder = os.path.join("./template")
# 文件输出目录
out_folder = os.path.join("./", "out")
# 临时目录
tmp_folder = os.path.join("./", "out", "tmp")



执行打包程序生成.spec配置文件


pyinstaller -F main.py



修改.spec配置文件

执行

main.exe

文件,提示缺少什么就在spec中加什么。

a = Analysis(
    ['main.py'],
    pathex=[],
    binaries=[],
    # 我的项目导入了runner,interface文件夹下面的包,需要在此处添加
    datas=[("runner/*","runner"),("interface/*","interface")],
    # 我的项目导入了第三方包,需要在此添加
    hiddenimports=["runner","docx","docxtpl","xlrd","tkinter","jinja2","matplotlib"],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)



重新打包


pyinstaller main.spec

最终生成的包大小为:

在这里插入图片描述

将exe文件和模板文件单独放到一个文件夹,此时已经可以正常使用。(打印help乱码不影响使用)

在这里插入图片描述



.spec脚本参数介绍

# -*- mode: python ; coding: utf-8 -*-


block_cipher = None


a = Analysis(
    ['main.py'],
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name='main',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)
coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='main',
)

生成的脚本就是这样的,包含以下主要配置参数:

参数 含义
a Analysis类的实例,主要分析.py文件的依赖项,如第三方库以及import模块。a中内容主要包括以下四部分:scripts,放入.py文件。;pure,程序代码文件中的纯Python模块,包括程序的代码文件本身;binaries,需要的二进制文件。;datas,非二进制文件。
pyz PYZ的实例,是一个.pyz文件,包含了所有pure中的所有Python模块。
exe EXE类的实例,处理Analysis和PYZ的结果的,用来生成最后的exe可执行程序。
coll COLLECT类的实例,用于创建输出目录。只有-D命令下才会实例化,-F不会生成目录。
block_cipher 加密密钥(一般无加密需求,可不设置)

而我们一般在配置时大多数情况下只涉及a,也就是Analysis类,下面逐一分析a里面的参数

Analysis的参数 简介以及可能存在的问题
scripts

首先是一个列表(list),应该存放与打包文件相关的所有.py文件。如果不存放的话会出现import错误 no

module

的问题。

pathex

默认有一个spec的目录,记得把用到的模块的路径添加到这个list变量里。

默认的路径是你打包的.py文件的同级路径。

存在问题:当引入自己的文件时添加此路径并不起作用。仍会报错

ModuleNotFoundError: No module named 'core'
     
     

datas 将资源文件或文件夹,复制到打包后的目录中,而datas中必须以元组形式否则会出现下面错误。注意一点放到这里面的文件不会被编译,而是复制,所以重要的文件放到这里很容易泄露。

ValueError: too many values to unpack (expected 2)

     
     

binaries 添加二进制文件,也是一个列表,定义方式与datas参数一样。
hiddenimports 隐式导入的模块,比如在__import__、imp.find_module()、exec、eval等语句中导入的模块,这些模块PyInstaller是找不到的,需要手动指定导入,
hookspath 指定额外hook文件(可以是py文件)的查找路径。
runtime_hooks 指定自定义的运行时hook文件路径(可以是py文件)
excludes 指定可以被忽略的可选的模块或包。



其他打包工具(cx_Freeze,PyOxidizer,Py2exe,Briefcase)

  1. PyInstaller:PyInstaller 是一个流行的 Python 打包工具,可以将 Python 应用程序打包成单个可执行文件。PyInstaller 会自动识别和打包 Python 应用程序的所有依赖项,并支持多平台,包括 Windows、Linux 和 macOS。它使用 Python 语言编写,易于安装和使用。此外,PyInstaller 还提供了一些高级选项,如优化生成的可执行文件的大小和性能等。

  2. cx_Freeze:cx_Freeze 是另一个流行的 Python 打包工具,可以将 Python 应用程序转换为可执行文件或打包成 ZIP 文件。与 PyInstaller 不同,cx_Freeze 可以生成 Windows、Linux 和 macOS 上的可执行文件。cx_Freeze 使用 Python 语言编写,易于安装和使用。它可以自动识别和打包 Python 应用程序的所有依赖项,并支持多个 Python 版本。

  3. PyOxidizer:PyOxidizer 是一个新兴的 Python 打包工具,它使用 Rust 编写,旨在提供高度优化的可执行文件。PyOxidizer 具有良好的跨平台支持,并支持将 Python 代码打包成单个可执行文件或动态链接库。PyOxidizer 还提供了一些高级选项,如动态链接库预热、运行时优化等。PyOxidizer 不仅支持 Python 语言,还支持 Rust 和 C 语言。

  4. Py2exe:Py2exe 是一个古老但仍然有用的 Python 打包工具,可以将 Python 应用程序转换为 Windows 可执行文件。Py2exe 可以自动识别和打包所有依赖项,并提供一些选项来优化生成的可执行文件的大小和性能。Py2exe 由于只支持 Windows 平台,因此在 Windows 上表现最佳。Py2exe 使用 Python 语言编写,易于安装和使用。



pyinstaller

优点:

  1. PyInstaller 具有广泛的操作系统支持,可以在 Windows、Linux 和 macOS 上打包应用程序。
  2. PyInstaller 可以自动识别和打包 Python 应用程序的所有依赖项,从而减少了配置和管理的工作量。
  3. PyInstaller 可以生成单个可执行文件,这使得分发和安装 Python 应用程序变得简单和方便。
  4. PyInstaller 支持多个 Python 版本,并且易于安装和使用。
  5. PyInstaller 提供了许多选项,如优化生成的可执行文件的大小和性能等。

缺点:

  1. 生成的可执行文件的体积可能较大。
  2. 在某些情况下,PyInstaller 可能无法识别 Python 应用程序的所有依赖项,从而导致运行时错误。
  3. 需要在不同的操作系统上生成不同的可执行文件,这可能增加了开发和测试的工作量。



cx_Freeze

优点:

  1. cx_Freeze 支持多个操作系统,包括 Windows、Linux 和 macOS。
  2. cx_Freeze 可以将 Python 应用程序转换为可执行文件或打包成 ZIP 文件,这使得分发和安装 Python 应用程序变得简单和方便。
  3. cx_Freeze 可以自动识别和打包 Python 应用程序的所有依赖项,从而减少了配置和管理的工作量。
  4. cx_Freeze 提供了许多选项,如优化生成的可执行文件的大小和性能等。
  5. cx_Freeze 支持多个 Python 版本,并且易于安装和使用。

缺点:

  1. 在某些情况下,cx_Freeze 可能无法识别 Python 应用程序的所有依赖项,从而导致运行时错误。
  2. 生成的可执行文件可能较大。
  3. cx_Freeze 缺少对一些高级功能的支持,如动态链接库预热和运行时优化等。



PyOxidizer

优点:

  1. PyOxidizer 提供了高度优化的可执行文件,这使得生成的应用程序在性能和体积方面表现优异。
  2. PyOxidizer 具有良好的跨平台支持,支持将 Python 代码打包成单个可执行文件或动态链接库。
  3. PyOxidizer 提供了一些高级选项,如动态链接库预热和运行时优化等。
  4. PyOxidizer 不仅支持 Python 语言,还支持 Rust 和 C 语言。
  5. PyOxidizer 可以自动识别和打包 Python 应用程序的依赖项,从而减少了配置和管理的工作量。

缺点:

  1. PyOxidizer 只支持 Python 3.6 或更高版本,不支持 Python 2.x。
  2. PyOxidizer 的配置比较复杂,需要使用 Rust 编程语言进行构建和配置。
  3. PyOxidizer 缺少一些常见的选项,如生成调试符号和管理包含资源的应用程序。



Py2exe

优点:

  1. Py2exe 支持将 Python 应用程序转换为 Windows 上的可执行文件。
  2. Py2exe 可以自动识别和打包 Python 应用程序的所有依赖项,从而减少了配置和管理的工作量。
  3. Py2exe 支持多个 Python 版本,并且易于安装和使用。
  4. Py2exe 提供了一些高级选项,如优化生成的可执行文件的大小和性能等。

缺点:

  1. Py2exe 只能在 Windows 上运行,并且只能将 Python 应用程序转换为 Windows 上的可执行文件。
  2. 生成的可执行文件可能较大。
  3. Py2exe 缺少对一些高级功能的支持,如动态链接库预热和运行时优化等。



Q&A



打包过程找不到自己自建模块


ModuleNotFoundError: No module named 'core'

可以将模块添加到datas里注意复制后的名字(‘core’,‘core’)最好是原名(推荐)。

还可以将自己的模块直接复制到site-packages 下面,再打包(不推荐)。



.spec文件

.spec文件是PyInstaller的配置文件,它包含了构建可执行文件时的各种配置选项。在PyInstaller打包的过程中,可以手动编辑.spec文件以更改某些选项。

使用以下命令生成.spec文件:


pyinstaller --name=myapp --onefile main.py


可以将–name选项替换为想要的应用程序名称,并将main.py替换为主脚本文件名。



Analysis Options

这一部分指定应用程序的入口点(通常是main.py文件),并包含一些其他配置选项,例如要添加的路径、要排除的模块等等。

a = Analysis(['main.py'],
             pathex=['/path/to/myapp'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)



Build Options

这一部分包含与构建可执行文件有关的选项,例如输出文件名、是否要创建调试版本等等。

exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='myapp',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          upx_include=[],
          console=True )



Deployment Options

这一部分包含与部署可执行文件有关的选项,例如生成的可执行文件是否要包含所有必需的依赖项、是否要创建单个文件等等。

pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)



一个应用有多个文件夹要如何打包成一个exe且不要多余的依赖文件

打包多个文件夹成一个exe文件,可以将多个文件夹中的Python文件和资源文件都复制到同一个目录下,然后使用PyInstaller打包该目录。

以下是一个示例.spec文件,假设有两个文件夹runner和helper都需要打包。

# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(['main.py'],
             pathex=['path/to/main.py'],
             binaries=[],
             datas=[('runner/*', 'runner'), ('helper/*', 'helper')],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='myapp',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True )

其中,datas参数指定了需要打包的文件夹及其对应的在exe文件中的路径,(‘runner/

‘, ‘runner’)表示将runner文件夹下的所有文件打包,并在exe文件中创建runner文件夹。同理,(‘helper/

’, ‘helper’)表示将helper文件夹下的所有文件打包,并在exe文件中创建helper文件夹。

使用命令pyinstaller yourapp.spec即可打包应用,并且不会有多余的依赖文件。



版权声明:本文为General_zy原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。