惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
O
OpenAI News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
Webroot Blog
Webroot Blog
GbyAI
GbyAI
S
SegmentFault 最新的问题
Cyberwarzone
Cyberwarzone
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
J
Java Code Geeks
Google DeepMind News
Google DeepMind News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 【当耐特】
S
Secure Thoughts
酷 壳 – CoolShell
酷 壳 – CoolShell
AWS News Blog
AWS News Blog
Engineering at Meta
Engineering at Meta
S
Security Affairs
H
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
D
DataBreaches.Net
云风的 BLOG
云风的 BLOG
Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
Spread Privacy
Spread Privacy
T
Threatpost
Forbes - Security
Forbes - Security
C
Cisco Blogs
Scott Helme
Scott Helme
Attack and Defense Labs
Attack and Defense Labs
Simon Willison's Weblog
Simon Willison's Weblog
腾讯CDC
The Last Watchdog
The Last Watchdog
Cloudbric
Cloudbric
Last Week in AI
Last Week in AI
Recorded Future
Recorded Future
小众软件
小众软件
V
Vulnerabilities – Threatpost
美团技术团队
人人都是产品经理
人人都是产品经理
有赞技术团队
有赞技术团队
Apple Machine Learning Research
Apple Machine Learning Research
Hacker News - Newest:
Hacker News - Newest: "LLM"
I
Intezer
月光博客
月光博客
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园 - 司徒正美
C
Cybersecurity and Infrastructure Security Agency CISA
Martin Fowler
Martin Fowler
博客园 - 聂微东

博客园 - 伍华聪

Cursor 里开发一个“一个后端 + 多个前端”项目的时候,推荐的项目目录结构组织方式 使用Cursor实现管理系统的主界面布局的Vue3前端开发 使用Cursor实现管理系统登录界面的快速开发 多租户系统框架的界面分析设计 多租户系统框架的基础模块设计和分析设计 多租户下的系统业务开发过程探讨 多租户下的ERP系统的仓储管理模块分析设计 多租户下的系统基础表设计 基于SqlSugar开发框架的基础上快速开发H5端的移动应用 在Python+FastAPI项目中使用SqlAlchemy操作数据的几种常见方式 在PySide6/PyQt6的项目中封装一些基础类库,包括文件对话框、字体对话框、颜色对话框、消息对话框等内容 在PySide6/PyQt6的项目中实现样式切换处理 如何在Python开发中实现无代码、纯配置的业务界面展示和常规数据操作的处理分析过程 在Winform开发中,动态增加标签分组,实现数据记录的快速录入 在基于FastAPI的Python开发框架后端,增加阿里云短信和邮件发送通知处理 介绍一个医疗物质数目清点系统的实现过程 在PySide6/PyQt6的开发框架中,增加对表格多种格式录入的处理,以及主从表的数据显示和保存操作。 使用PySide6/PyQt6实现自定义窗口布局,实现类似FluentWindow效果 WxPython跨平台开发框架之主从表展示和录入的界面处理--产品报价单和明细记录的处理 Vue3+ElementPlus的BS端主从表的快速开发 使用PySide6/PyQt6实现全国省市区的级联选择组件 基于 SocketIO 消息协议设计报文规范,构建FastAPI上的SocketIO 应用 使用PySide6/PyQt6实现系统图标的展示和选择处理
基于Python的FastAPI后端开发框架如何使用PyInstaller 进行打包与部署
伍华聪 · 2025-10-11 · via 博客园 - 伍华聪

我在随笔《WxPython跨平台开发框架之使用PyInstaller 进行打包处理》中介绍过如何使用PyInstaller 进行打包处理的一些过程和事项。我们基于Python的FastAPI后端应用,在实际开发的时候,直接运行main.py 进行调试即可,但是部署的时候,我们就需要把它们进行打包处理,这里首选PyInstaller 进行打包。本文详细介绍了 如何使用 PyInstaller 对基于 Python 的 FastAPI 后端项目进行打包与部署,使其能够在目标环境中以独立可执行文件的形式运行,无需安装 Python 解释器或额外依赖。文章面向希望将 FastAPI 服务打包为独立运行服务的开发者,特别适用于企业内部系统或需要简化部署的场景。

一、背景与目标

  • 说明为什么需要将 FastAPI 项目打包为可执行文件。

  • 对比传统部署方式(如 uvicorn main:app)与 PyInstaller 打包方式的区别。

  • 适用场景:企业内网部署、Windows 服务、无 Python 环境的服务器等。

二、环境准备

  • Python 版本要求(推荐 Python 3.12.4+)。

  • FastAPI 与依赖(fastapi, uvicorn, pydantic, sqlalchemy, 等)。

  • 安装 PyInstaller:

三、FastAPI 项目结构示例

展示一个典型的 FastAPI 项目结构:

并说明 main.py 中如何启动服务,例如:

import uvicorn
from app.main import app

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

四、使用 PyInstaller 打包

PyInstaller是目前最流行的Python打包工具之一。它可以将Python脚本打包成独立的可执行文件,支持Windows、Linux和macOS平台。

PyInstaller 有丰富的文档,提供了详细的使用说明和常见问题解答,你可以通过以下链接访问:

这些文档和资源能帮助你深入了解 PyInstaller 的使用方式,并解决在打包过程中可能遇到的问题。

打包后的可执行文件可以在没有 Python 环境的机器上运行。PyInstaller 会自动分析程序的依赖关系,并将所有必要的库和资源打包到一个文件或者一个文件夹中。

打包过程中,PyInstaller 会生成一个 .spec 文件。这个文件包含了 PyInstaller 的配置信息,其中包含了构建过程的所有配置信息。你可以修改这个文件来定制打包过程。

如果我们执行下面代码

或者指定更多的参数的代码

pyinstaller --onefile --icon=your_icon.ico main.py

PyInstaller 都会生成一个 .spec 文件,然后可以编辑 main.spec 文件,以便进行更好的控制管理打包文件。

虽然原则上.spec文件支持跨平台的配置,不过我们在实际中往往根据不同的平台配置特定的.spec文件。

你可以手动修改 .spec 文件来添加资源文件、修改导入模块、定制输出路径等。

你可以通过编辑.spec 文件,在EXE、COLLECT和BUNDLE块下添加一个name= ,为PyInstaller提供一个更好的名字,以便为应用程序(和dist 文件夹)使用。

EXE下的名字是可执行文件的名字,BUNDLE下的名字是应用程序包的名字。

import sys
import os
from pathlib import Path

# 本文件用于Window平台下打包整个项目,生成一个独立的exe文件,依赖文件松散组合
# 执行命令:pyinstaller main_my.spec
# 打包后生成文件:dist\fastapi_app\fastapi_app.exe
# 运行后,会在当前目录生成一个 dist 文件夹,里面有 fastapi_app.exe 文件,在命令行窗口运行该文件即可启动服务。


if sys.platform == "win32":
    icon = "app/images/app.ico"
elif sys.platform == "darwin":
    icon = "app/images/app.icns"

block_cipher = None

# 导入 PyInstaller 模块
from PyInstaller.building.build_main import Analysis
from PyInstaller.building.build_main import PYZ
from PyInstaller.building.build_main import EXE
from PyInstaller.building.build_main import COLLECT

# Analysis: PyInstaller Analysis object
a = Analysis(
    ["app/main.py"],
    pathex=[],
    binaries=[],
    datas=[
        ("app/uvicorn_config.json", "app"),
        ("app/.env", "."),
        ("app/images/*", "app/images"),
        ("app/templates/*", "app/templates"),
        ("app/uploadfiles/*", "app/uploadfiles"),
        ("app/logs/*", "app/logs"),
    ],
    hiddenimports=[
         "uvicorn", "fastapi", "pydantic", "aiomysql", 'asyncio',   # 确保依赖被正确包含
    ],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
    optimize=0,
)

# PYZ: PyInstaller PYZ object
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

修改完成后,执行以下命令来重新打包:

如果我们想在Windows平台生成的dist目录中生成一个启动exe,和其他相关的Lib依赖库目录,那么我们可以适当调整下.spec文件,让它可以生成松散结构的文件目录包。

exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,
    name="fastapi_app",
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,          # True = 有控制台输出(调试方便),False = 静默运行
    onefile=False,  # <-- False取消、True使用 onefile 模式
    icon=icon,  # <-- 图标路径
    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,
    name='fastapi_app'
)

相当于之前在exe包中的a.binaries 和 a.datas从EXE 构造函数中移到了Collect的构造函数里面了。这样会生成下面的目录结构。

image

其中_internal目录包含程序的相关依赖包和文件资源。

image

由于打包的.spec文件指定的目录结构为松散结构(使用了COLLECT构造),那么可以看到 _internal / app目录下有下面的目录结构。

image

也就是我们前面通过 Analysis 模块指定的datas集合路径的内容。

解决常见问题

    • 缺少依赖库:如果打包后运行时出现缺少模块的错误,可以尝试将缺少的模块加入到 hiddenimports 中,或者通过 --hidden-import 选项指定:

    • 大文件:如果使用 --onefile 时打包后的文件太大,考虑使用 --onedir 或通过压缩文件等方法进行优化。
    • 处理资源文件:如果你的应用程序包含非 Python 代码的资源(如图像、配置文件、数据文件等),你需要通过 --add-data 选项指定资源文件的路径,或者在 .spec 文件中修改 datas 选项。
    • 动态链接库,如果你的应用程序依赖于特定的动态链接库(如 DLL 文件或 .so 文件),你需要将这些库包含到打包中。可以在 .spec 文件的 binaries 选项中指定:

    • 多平台支持:PyInstaller 支持 Windows、Linux 和 macOS 等多个平台,但需要在相应的平台上打包。例如,如果你要为 Windows 用户创建可执行文件,最好在 Windows 上运行 PyInstaller 来生成 Windows 的 .exe 文件。如果在 macOS 上打包,生成的文件只能在 macOS 上运行。

在使用 PyInstaller 打包 FastAPI(或其他 Python 应用)时,两个最常见、最容易混淆的参数就是:

  • --add-data(或 .spec 文件中的 datas

  • --hidden-import(或 .spec 文件中的 hiddenimports

当 PyInstaller 打包时,它默认只会分析 Python 代码的依赖模块,而不会自动包含图片、HTML 模板、配置文件等静态资源。
这时,就需要用 --add-data(或在 .spec 文件的 datas 中定义)告诉它要额外打包哪些文件或目录。

这里不介绍命令行的方式,只介绍.spec 文件写法:

image

PyInstaller 在打包时,会分析你的 Python 源代码(AST)来判断使用了哪些模块。但有些模块是动态导入的(例如通过 importlib 或字符串导入),它就可能漏掉。

解决办法:用 --hidden-import 或者.spec文件中指定 hiddenimports 集合,告诉 PyInstaller 把这些模块也打包进去,如上所示。

总结起来就是:

  • datas = “我还有额外的文件要带上”。

  • hiddenimports = “我还有额外的模块要带上”。

五、FastAPI 项目打包的处理

前面介绍了一个简单的fastapi的项目结构和启动,一般我们在开发的时候,启动fastapi,直接调用python解析器运行main.py文件即可启动,常规来说,main.py的启动部分函数代码如下。

if __name__ == "__main__":

    # 日志配置路径
    config_path = resource_path("app/uvicorn_config.json")
    # 运行 uvicorn
    try:
        config = uvicorn.Config(
            app = socket_app,
            reload=True,
            host=settings.SERVER_IP,
            log_config = config_config,  # 日志配置
        )
        server = uvicorn.Server(config)
        server.run()
    except Exception as e:
        raise e

上面就是我实际项目简化版本的main.py函数的启动内容,正常开发环境,测试是正常的。但是通过pyinstall打包完成,并运行fastapi_app.exe的时候,提示找不到配置文件uvicorn_config.json。

FileNotFoundError: [Errno 2] No such file or directory: 'app/uvicorn_config.json'

这个原因是打包后执行exe文件的当前路径改变了,打包进去的 exe 并没有找到这个文件。首先:修改 .spec 文件,确保文件被打包进去,在 datas 里加这一行 👇
(假设文件路径是 app/uvicorn_config.json

datas = [
    ("app/uvicorn_config.json", "app"),
    ("app/templates/*", "app/templates"),
    ("app/static/*", "app/static"),
    ("app/images/*", "app/images"),
]

这一步确保 exe 中确实包含了你的 uvicorn_config.json 文件。

其次:在 main.py 中使用通用的路径函数resource_path:

def resource_path(relative_path: str) -> str:
    """
    获取资源文件真实路径,支持:
    - 开发模式
    - PyInstaller onefile 模式
    - PyInstaller COLLECT (_internal) 模式
    """

    if hasattr(sys, '_MEIPASS'): # onefile 模式
        # exe 解压临时目录
        base_path = sys._MEIPASS
    else:
        # 在松散模式下,_internal 目录才是真正的数据存放处
        base_path = os.path.dirname(sys.executable) # exe 所在目录
        internal_path = os.path.join(base_path, "_internal")
        if os.path.exists(internal_path):# 松散打包目录
            base_path = internal_path
        else:
            # 直接开发运行时
            base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)

然后修改你的 uvicorn.Config 代码

替换硬编码路径为:

import uvicorn

config_path = resource_path("app/uvicorn_config.json")

config = uvicorn.Config(
    app=socket_app,
    host=settings.SERVER_IP,
    port=settings.SERVER_PORT,
    log_config=config_path,  # ✅ 动态获取正确路径
)

server = uvicorn.Server(config)
server.run()

上面启动后,fastapi 配置文件定位到了,但是可能还会产生新的问题

你可能会发现 app/uvicorn_config.json 里面配置的日志文件路径和实际不对。

image

FileNotFoundError: [Errno 2] No such file or directory: '.../app/logs/log.log'

Uvicorn 在加载 uvicorn_config.json 时的日志路径是 相对进程工作目录
而不是相对 uvicorn_config.json 文件本身的路径 ——
这正是为什么你配置 "filename": "app/logs/log.log" 仍然报错的根本原因。

我们需要,在运行前动态修正 log_config.json 内部的路径

我们在加载 JSON 后,动态修改其中 "filename" 字段的路径为打包后正确的绝对路径。

修正代码后如下所示。

if __name__ == "__main__":

    # 动态解析日志配置路径
    config_path = resource_path("app/uvicorn_config.json")
    
    # 加载并修改日志配置,主要对日志文件路径进行修正
    with open(config_path, "r", encoding="utf-8") as f:
        log_config = json.load(f)

    # 找到其中的 file handler,改写 filename 为绝对路径
    for handler in log_config.get("handlers", {}).values():
        if "filename" in handler:
            log_file = handler["filename"]
            abs_log_path = resource_path(log_file)
            os.makedirs(os.path.dirname(abs_log_path), exist_ok=True)
            handler["filename"] = abs_log_path  # 替换为绝对路径

    # 运行 uvicorn(传入已修改的 log_config dict)
    try:
        config = uvicorn.Config(
            app = socket_app,
            reload=True,
            host=settings.SERVER_IP,
            log_config = log_config,  # 日志配置,修正方式见上
        )

        server = uvicorn.Server(config)
        server.run()
    except Exception as e:
        raise e

至此,所有问题都顺利解决,能够正常运行起来了,我们来看看FastAPI顺利启动后的效果。复制松散文件夹到服务器上双击运行即可,需要也可以修改配置文件.env实现相关修改。

image

✅ 如果运行打包的exe 提示Missing command. 

其实是 uvicorn 的提示,不是 PyInstaller 本身的报错。可能是你的app设置上的问题,你在 main.py 里可能用了这种启动方式:

uvicorn.run("app.main:app", host="0.0.0.0", port=8000)

解决方法 改成直接传入 app 对象,而不是字符串路径:

    # ✅ 改成直接传 app 对象
    uvicorn.run(app, host="0.0.0.0", port=8000)

这样 uvicorn 就不会去找字符串形式的 module:app,而是直接运行你传进去的 FastAPI 实例。 打包后的 exe 就能正常运行。

✅ 如果提示No module named 'aiomysql'

这个问题其实是 PyInstaller 没有把 aiomysql 打包进去,因为它是动态导入的,PyInstaller 静态分析不到。

方法 A:命令行添加 hidden-import

pyinstaller --onefile --name fastapi_app --hidden-import aiomysql app/main.py

方法 B:在 .spec 文件里加 hiddenimports

找到 .spec 文件里的 Analysis,改成:

a = Analysis(
    ['app/main.py'],
    pathex=[],
    binaries=[],
    datas=datas,
    hiddenimports=[
        "uvicorn",
        "fastapi",
        "pydantic",
        "aiomysql"   # 👈 加上这里
    ],
    hookspath=[],
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=block_cipher,
    noarchive=False,
)

FastAPI + 数据库常用依赖很多(如 sqlalchemy[asyncio]asyncpgaiomysql 等),有些也可能被漏掉。做法同样:把缺失的库加到 hidden-import

hiddenimports=[
    "uvicorn",
    "fastapi",
    "pydantic",
    "aiomysql",
    "asyncpg",
    "sqlalchemy.ext.asyncio",
]

✅ Data内容的写法

("app/images/*", "app/images")

会把 app/images 下的所有文件 放到 exe 解压后的目录里,路径是 app/images/...

如果代码里是这样写的: