基于Python的FastAPI后端開發框架如何使用PyInstaller 進行打包與部署
我在隨筆《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:
pip install 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 官方文檔:https://pyinstaller.readthedocs.io
- GitHub 代碼庫:https://github.com/pyinstaller/pyinstaller
這些文檔和資源能幫助你深入了解 PyInstaller 的使用方式,并解決在打包過程中可能遇到的問題。
打包后的可執行文件可以在沒有 Python 環境的機器上運行。PyInstaller 會自動分析程序的依賴關系,并將所有必要的庫和資源打包到一個文件或者一個文件夾中。
打包過程中,PyInstaller 會生成一個 .spec 文件。這個文件包含了 PyInstaller 的配置信息,其中包含了構建過程的所有配置信息。你可以修改這個文件來定制打包過程。
如果我們執行下面代碼
pyinstaller main.py
或者指定更多的參數的代碼
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)
修改完成后,執行以下命令來重新打包:
pyinstaller main_my.spec
如果我們想在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的構造函數里面了。這樣會生成下面的目錄結構。

其中_internal目錄包含程序的相關依賴包和文件資源。

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

也就是我們前面通過 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 文件寫法:

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 里面配置的日志文件路徑和實際不對。

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實現相關修改。

? 如果運行打包的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]、asyncpg、aiomysql 等),有些也可能被漏掉。做法同樣:把缺失的庫加到 hidden-import。
hiddenimports=[ "uvicorn", "fastapi", "pydantic", "aiomysql", "asyncpg", "sqlalchemy.ext.asyncio", ]
? Data內容的寫法
("app/images/*", "app/images")
會把 app/images 下的所有文件 放到 exe 解壓后的目錄里,路徑是 app/images/...
如果代碼里是這樣寫的:
專注于代碼生成工具、.Net/Python 框架架構及軟件開發,以及各種Vue.js的前端技術應用。著有Winform開發框架/混合式開發框架、微信開發框架、Bootstrap開發框架、ABP開發框架、SqlSugar開發框架、Python開發框架等框架產品。
??轉載請注明出處:撰寫人:伍華聰??http://www.iqidi.com?
????
浙公網安備 33010602011771號