pyinstaller是python下目前能打包py文件為windows下的exe文件的一個非常友好易用的庫!但是,小爬每次用pyinstaller打包時也總是遇到一些難題,有時網上搜了一圈,也沒看到合適的答案。小爬因此決定把我的問題和后來的解決思路都寫出來,供后來者參考!
事情是這樣的,小爬最近編寫了一個發票PDF文件的識別腳本:1、用到PyMuPDF中的fitz模塊來提取發票的二維碼圖片元素;2、用到pyzbar來提取二維碼信息;3、用pdfplumber(該庫依賴於pdfminer.six庫)來提取PDF文件中的文本和表格數據;4、用Pillow庫對處理圖像對象。
腳本寫完后,可以正常地在Visual Studio Code下跑出結果,符合預期。用pyinstaller打包為單個exe文件的過程看上去很“完美”,但是封裝后的exe文件每次執行都閃退,錯誤信息如下:
Traceback (most recent call last): File "lib\site-packages\PyInstaller\loader\pyiboot01_bootstrap.py", line 149, in __init__ File "ctypes\__init__.py", line 348, in __init__ OSError: [WinError 126] 找不到指定的模塊。 During handling of the above exception, another exception occurred: Traceback (most recent call last): File "lib\site-packages\pyzbar\zbar_library.py", line 58, in load File "lib\site-packages\pyzbar\zbar_library.py", line 52, in load_objects File "lib\site-packages\pyzbar\zbar_library.py", line 52, in <listcomp> File "ctypes\__init__.py", line 426, in LoadLibrary File "lib\site-packages\PyInstaller\loader\pyiboot01_bootstrap.py", line 151, in __init__ __main__.PyInstallerImportError: Failed to load dynlib/dll 'libiconv.dll'. Most probably this dynlib/dll was not found when the application was frozen. During handling of the above exception, another exception occurred: Traceback (most recent call last): File "lib\site-packages\PyInstaller\loader\pyiboot01_bootstrap.py", line 149, in __init__ File "ctypes\__init__.py", line 348, in __init__ OSError: [WinError 126] 找不到指定的模塊。 During handling of the above exception, another exception occurred: Traceback (most recent call last): File "tel_Fee_Invoice_Info_Extract.py", line 13, in <module> import pyzbar.pyzbar as pyzbar File "<frozen importlib._bootstrap>", line 971, in _find_and_load File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 665, in _load_unlocked File "d:\settlement_env\venv\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 627, in exec_module exec(bytecode, module.__dict__) File "lib\site-packages\pyzbar\pyzbar.py", line 7, in <module> File "<frozen importlib._bootstrap>", line 971, in _find_and_load File "<frozen importlib._bootstrap>", line 955, in _find_and_load_unlocked File "<frozen importlib._bootstrap>", line 665, in _load_unlocked File "d:\settlement_env\venv\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 627, in exec_module exec(bytecode, module.__dict__) File "lib\site-packages\pyzbar\wrapper.py", line 143, in <module> File "lib\site-packages\pyzbar\wrapper.py", line 136, in zbar_function File "lib\site-packages\pyzbar\wrapper.py", line 115, in load_libzbar File "lib\site-packages\pyzbar\zbar_library.py", line 60, in load File "lib\site-packages\pyzbar\zbar_library.py", line 52, in load_objects File "lib\site-packages\pyzbar\zbar_library.py", line 52, in <listcomp> File "ctypes\__init__.py", line 426, in LoadLibrary File "lib\site-packages\PyInstaller\loader\pyiboot01_bootstrap.py", line 151, in __init__ __main__.PyInstallerImportError: Failed to load dynlib/dll 'C:\\Users\\newjune\\AppData\\Local\\Temp\\_MEI164962\\pyzbar\\libiconv.dll'. Most probably this dynlib/dll was not found when the application was frozen. [18280] Failed to execute script tel_Fee_Invoice_Info_Extract
該traceback看是在說 缺少"ctypes\__init__.py" 模塊,實際上,經過它的提示,我們能在對應的路徑下找到該模塊,並不能發現什么異常。這段報錯信息的倒數第二行
”Failed to load dynlib/dll 'C:\\Users\\newjune\\AppData\\Local\\Temp\\_MEI164962\\pyzbar\\libiconv.dll'. Most probably this dynlib/dll was not found when the application was frozen.“
,似乎在暗示該exe文件,每次執行的時候,會在計算機本地的Temp文件下生成一個臨時文件夾,其中,要調用的動態鏈接庫文件”libiconv.dll“找不到,改庫是二維碼解析庫 pyzbar要運行的必要依賴文件。
我們在venv的虛擬環境下對應的文件夾”\venv\Lib\site-packages\pyzbar“ 下可以找到它:
小爬這次決定試試pyinstaller的打包為文件夾功能,不再打包為單個exe文件:使用pyinstaller -D your.py 語句即可。果不其然,該方法下打包也很順利,但是生成的文件夾下,我們可以看到很多的依賴文件和pyd文件。
最重要的一點,對應的exe文件執行時候報錯跟上面的traceback錯誤信息是一樣的。這個生成的文件夾下已經包含了諸多的windows下需要的dll文件,但是並沒有我們要的”pyzbar\\libiconv.dll",小爬決定將上文找到的文件夾整個復制到該工具目錄下。再次運行,此時程序可以正常運行,但是在提取pdf文件的文本信息時,濾掉了所有的中文字符,只能顯示字母、數字和特殊符號。小爬馬上聯想到,這是缺少PDF中文字符的解碼包。聯想到 我使用的pdfplumber 庫是基於 pdfminer.six庫 二次開發。我們再次找到 pdfminer.six所在的文件夾:
該子文件夾”cmap“中存儲着大量的PDF字符解碼包:
小爬猜想這就是我們程序需要的,pyinstaller在打包的過程中再次漏掉了引用這些解碼包。小爬再次將整個pdfminer文件夾拷貝到 程序的根目錄下,這次,再次運行exe文件,已經可以完美執行,大功告成。小爬猜想,pyinstaller只是沒有成功封裝 這些第三方依賴文件,但是py文件應該已經封裝過。為此,小爬刪除了pdfminer文件夾和pyzbar文件夾下的所有py文件,再次運行工具,依舊完美執行!
照理說,此時,該工具已經達到了預期的目的,但是小爬還是想把整個含exe核心文件的文件夾打包成一個單獨的exe可執行文件,有沒有辦法做到呢?辦法當然有,我們需要用到 ”Enigma Virtual Box“該文件。它能幫我們很方便的實現該功能:
注意事項就一點:該工具支持直接將我們的目錄拖拽到virtual box Files 樹內,一鍵生成目錄樹,不需要手工創建節點!
至此,大功告成,如果您也遇到和小爬一樣的pyinstaller打包問題,不妨參照上面的方法試一試!