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 , in __init__
File "ctypes\__init__.py", line , in __init__
OSError: [WinError ] 找不到指定的模块。 During handling of the above exception, another exception occurred: Traceback (most recent call last):
File "lib\site-packages\pyzbar\zbar_library.py", line , in load
File "lib\site-packages\pyzbar\zbar_library.py", line , in load_objects
File "lib\site-packages\pyzbar\zbar_library.py", line , in <listcomp>
File "ctypes\__init__.py", line , in LoadLibrary
File "lib\site-packages\PyInstaller\loader\pyiboot01_bootstrap.py", line , 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 , in __init__
File "ctypes\__init__.py", line , in __init__
OSError: [WinError ] 找不到指定的模块。 During handling of the above exception, another exception occurred: Traceback (most recent call last):
File "tel_Fee_Invoice_Info_Extract.py", line , in <module>
import pyzbar.pyzbar as pyzbar
File "<frozen importlib._bootstrap>", line , in _find_and_load
File "<frozen importlib._bootstrap>", line , in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line , in _load_unlocked
File "d:\settlement_env\venv\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line , in exec_module
exec(bytecode, module.__dict__)
File "lib\site-packages\pyzbar\pyzbar.py", line , in <module>
File "<frozen importlib._bootstrap>", line , in _find_and_load
File "<frozen importlib._bootstrap>", line , in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line , in _load_unlocked
File "d:\settlement_env\venv\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line , in exec_module
exec(bytecode, module.__dict__)
File "lib\site-packages\pyzbar\wrapper.py", line , in <module>
File "lib\site-packages\pyzbar\wrapper.py", line , in zbar_function
File "lib\site-packages\pyzbar\wrapper.py", line , in load_libzbar
File "lib\site-packages\pyzbar\zbar_library.py", line , in load
File "lib\site-packages\pyzbar\zbar_library.py", line , in load_objects
File "lib\site-packages\pyzbar\zbar_library.py", line , in <listcomp>
File "ctypes\__init__.py", line , in LoadLibrary
File "lib\site-packages\PyInstaller\loader\pyiboot01_bootstrap.py", line , 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.
[] 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打包问题,不妨参照上面的方法试一试!