使用pyinstaller打包python程序并发布
Published in:2024-04-09 |
Words: 2.2k | Reading time: 9min | reading:

使用pyinstaller打包python程序并发布

简介

PyInstaller 将 Python 应用程序及其所有依赖项捆绑到单个软件包中。用户无需安装 Python 解释器或任何模块,即可运行打包后的应用程序。PyInstaller 支持 Python 3.8 及其更新版本,并能正确捆绑 numpy、matplotlib、PyQt、wxPython 等许多主流 Python 包。

PyInstaller 已经过 Windows、MacOS X 和 Linux 测试。不过,它并不是一个交叉编译器;要制作 Windows 应用程序就需要在 Windows 上运行 PyInstaller,要制作 Linux 应用程序就需要在 Linux 上运行它,依此类推。PyInstaller 已经成功地在 AIX、Solaris、FreeBSD 和 OpenBSD 上使用,但针对这些平台的测试并不是我们持续集成测试的一部分,开发团队也不能保证(这些平台的所有代码都来自外部贡献)PyInstaller 将能够在这些平台上运行,或得到持续支持。

  • upx

    UPX 是一款用于压缩可执行文件和库的免费工具。它适用于大多数操作系统,可用压缩大量可执行文件格式。

当 UPX 可用时,PyInstaller 会用它来单独压缩每个收集的二进制文件(可执行文件、共享库或 Python 扩展)以减小冻结应用程序(单文件夹捆绑的目录或单文件可执行文件)的整体大小。冻结应用程序的可执行文件本身并没有经过 UPX 压缩(不管是单文件夹模式还是单文件模式),因为其文件大小中的大部分都已经是由包含单独压缩文件的嵌入式压缩包构成。

PyInstaller 会在标准可执行路径(由 PATH 环境变量定义)或通过 –upx-dir 命令行选项指定的路径中查找 UPX。如果找到,就会自动使用。使用 –noupx 命令行选项可用完全禁用 UPX。

安装

  • 使用以下命令安装
    1
    pip install pyinstaller
  • 版本升级
    1
    pip install --upgrade pyinstaller
  • 常用command option
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    参数	说明
    -F # 产生单个的可执行文件
    -D # 产生一个目录(包含多个文件)作为可执行程序
    -a # 不包含 Unicode 字符集支持
    -d # debug 版本的可执行文件
    -w # 指定程序运行时不显示命令行窗口(仅对 Windows 有效)
    -c # 指定使用命令行窗口运行程序(仅对 Windows 有效)
    -o # 指定 spec 文件的生成目录。如果没有指定,则默认使用当前目录来生成 spec 文件
    -p # 设置 Python 导入模块的路径(和设置 PYTHONPATH 环境变量的作用相似)。也可使用路径分隔符(Windows 使用分号,Linux 使用冒号)来分隔多个路径
    -n # 指定项目(产生的 spec)名字。如果省略该选项,那么第一个脚本的主文件名将作为 spec 的名字

发布

举例 1

1
pyinstaller -F -w -i favicon.ico test.py

举例 2(利用spec配置文件打包)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# -*- mode: python ; coding: utf-8 -*-
# test.spec

block_cipher = None

a = Analysis(['test.py'],
pathex=['F:\\PythonCool\\pyinstaller'],
binaries=[],
datas=[], # 这里带上资源文件地址
# datas=[('C:\\Users\\Gdc\\anaconda3\\envs\\env_test\\Lib\\site-packages\\data\\data','data')], # 这里带上资源文件地址
hiddenimports=[], # 动态引入的库或模块
# hiddenimports=['palettable'], # 动态引入的库或模块
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,
a.binaries,
a.zipfiles,
a.datas,
[],
name='test',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True , icon='icon.ico')
  • 编写上述配置文件后,命名为test.spec,使用以下命令打包:
    1
    pyinstaller -D test.spec  

应用

demo举例

  • spec文件举例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    # -*- mode: python ; coding: utf-8 -*-


    block_cipher = None

    a = Analysis(
    ['ui_main.py'],
    pathex=[],
    binaries=[],
    datas=[
    ('C:\\Users\\ad\\PycharmProjects\\pro\\src\\file','file')
    ],
    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,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='test',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon='test.ico'
    )

扩展

  • 可通过bat或sh脚本执行打包,举例如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @echo off
    setlocal

    REM 删除dist和build目录
    if exist "dist" (
    rmdir /s /q "dist"
    )
    if exist "build" (
    rmdir /s /q "build"
    )

    REM 运行pyinstaller main_sis.spec进行打包
    pyinstaller main_sis.spec

    endlocal
  • sh
    1
    pyinstaller main_sis.spec

spec解析

详细如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# -*- mode: python ; coding: utf-8 -*-
# script: 写所有的py文件
# pathex:写项目的地址,以及自定义库的地址
# datas:静态文件数据的地址
# binaries:二进制文件地址,如果有报错,或者,需要用的时候再添加


block_cipher = None

# 这一部分负责收集你的脚本需要的所有模块和文件。的;hiddenimports 参数可以指定一些 PyInstaller 无法自动检测到的模块。
a = Analysis(
['hello.py'], # 指定要打包的 Python 脚本的路径(可以是相对路径)
pathex=[], # 用来指定模块搜索路径
binaries=[], # 包含了动态链接库或共享对象文件,会在运行之后自动更新,加入依赖的二进制文件
datas=[], # 列表,用于指定需要包含的额外文件。每个元素都是一个元组:(文件的源路径, 在打包文件中的路径)
hiddenimports=[], # 用于指定一些 PyInstaller 无法自动检测到的模块
hookspath=[], # 指定查找 PyInstaller 钩子的路径
hooksconfig={}, # 自定义 hook 配置,这是一个字典,一行注释写不下,此处先不讲
runtime_hooks=[], # 指定运行时 hook,本质是一个 Python 脚本,hook 会在你的脚本运行前运行,可用于准备环境
excludes=[], # 用于指定需要排除的模块
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
# 除此之外,a 还有一些没有列出的属性:
# pure 是一个列表,包含了所有纯 Python 模块的信息,每个元素是一个元组,包含了:模块名, pyc路径, py 路径,这些模块会被打包到一个 .pyz 文件中。
# scripts 是一个列表,包含了你的 Python 脚本的信息。每个元素是一个元组,其中包含了脚本的内部名,脚本的源路径,以及一些元数据。这些脚本会被打包到一个可执行文件中。

# pyz 是指生成的可执行文件的名称。它是由 PyInstaller 用来打包 Python 程序和依赖项的主要文件。

# 创建 pyz 文件,它在运行时会被解压缩到临时目录中,然后被加载和执行。它会被打包进 exe 文件
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)


# 创建 exe 文件
exe = EXE(
pyz, # 包含了所有纯 Python 模块
a.scripts, # 包含了主脚本及其依赖
[], # 所有需要打包到 exe 文件内的二进制文件
exclude_binaries=True, # 若为 True,所有的二进制文件将被排除在 exe 之外,转而被 COLLECT 函数收集
name='hello', # 生成的 exe 文件的名字。
debug=False, # 打包过程中是否打印调试信息?
bootloader_ignore_signals=False,
strip=False, # 是否移除所有的符号信息,使打包出的 exe 文件更小
upx=True, # 是否用 upx 压缩 exe 文件
console=True, # 若为 True 则在控制台窗口中运行,否则作为后台进程运行
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)


# 这个对象包含了所有需要分发的文件
# 包括 EXE 函数创建的 exe 文件、所有的二进制文件、zip 文件(如果有的话)和数据文件
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='hello', # 生成的文件夹的名字
)

问题

1.pyinstaller 打包后无法通过反射访问py文件

  • 解决方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    from crawlers.base import BaseCrawler
    from crawlers.public.daili66 import Daili66Crawler
    from crawlers.public.data5u import Data5UCrawler
    from crawlers.public.docip import DocipCrawler
    from crawlers.public.fatezero import FatezeroCrawler
    from crawlers.public.geonodedaili import GeonodeCrawler
    from crawlers.public.goubanjia import GoubanjiaCrawler
    from crawlers.public.ihuan import IhuanCrawler
    from crawlers.public.ip3366 import IP3366Crawler
    from crawlers.public.iphai import IPHaiCrawler
    from crawlers.public.jiangxianli import JiangxianliCrawler
    from crawlers.public.kuaidaili import KuaidailiCrawler
    from crawlers.public.seofangfa import SeoFangFaCrawler
    from crawlers.public.taiyangdaili import TaiyangdailiCrawler
    from crawlers.public.uqidata import UqidataCrawler
    from crawlers.public.xiaoshudaili import XiaoShuCrawler
    from crawlers.public.xicidaili import XicidailiCrawler
    from crawlers.public.xiladaili import XiladailiCrawler
    from crawlers.public.yqie import YqIeCrawler
    from crawlers.public.zhandaye import ZhandayeCrawler

    # 获取模块中所有的类
    crawlers_cls = [
    base,
    Ip89Crawler,
    Daili66Crawler,
    Data5UCrawler,
    DocipCrawler,
    FatezeroCrawler,
    GeonodeCrawler,
    GoubanjiaCrawler,
    IhuanCrawler,
    IP3366Crawler,
    IPHaiCrawler,
    JiangxianliCrawler,
    KuaidailiCrawler,
    SeoFangFaCrawler,
    TaiyangdailiCrawler,
    UqidataCrawler,
    XiaoShuCrawler,
    XicidailiCrawler,
    XiladailiCrawler,
    YqIeCrawler,
    ZhandayeCrawler
    ]

    crawlers_cls = [cls for cls in crawlers_cls
    if isinstance(cls, type) and issubclass(cls, BaseCrawler) and cls is not BaseCrawler
    and not getattr(cls, 'ignore', False)]

    # 调用
    self.crawlers = [crawler_cls() for crawler_cls in self.crawlers_cls]

2.打包后无法访问指定数据

  • 解决办法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @logger.catch
    def get_data_file(filename):
    """

    获取数据文件的路径,无论是直接运行还是通过 PyInstaller 打包 抑或nuitka打包
    :param filename
    :return
    """
    # pyinstaller 打包
    if getattr(sys, 'frozen', False):
    # 如果程序是“冷冻的”,即打包后的 exe
    # nuitka 打包
    if run.__compiled__:
    basedir = os.getcwd()
    else:
    basedir = sys._MEIPASS
    else:
    # 如果程序是直接运行的,即没有打包
    basedir = os.path.dirname(__file__)

    return os.path.join(basedir, filename)

See

Prev:
python使用setuptools和nsis工具打包nuitka工程
Next:
使用nuitka打包python为exe并发布