完全披露:这是我对一个重复的类似问题的回答的重新发布。我只是把它放在这里是为了可见性。这真正回答了所提出的问题;所以它是相关的。
您在构建独立程序时没有正确使用 Pyinstaller。以下是关于 Pyinstaller 工作原理的简短外行描述:Pyinstaller 捆绑了 Python 解释器、必要的 DLL(适用于 Windows)、项目的源代码以及 它可以找到的所有模块 放入文件夹或自解压可执行文件。 Pyinstaller 不包含在您运行 Pyinstaller 时生成的最终 .exe (Windows)、.app (macOS)、文件夹等中找不到的模块或文件。
所以,这就是发生的事情:
FileNotFoundError: [Errno 2] No such file or directory: '/tmp/_MEIbxALM3/scrapy/VERSION'
您运行了冻结/独立程序。一旦你这样做了,你的程序就会被“解压”到你计算机上一个新的临时文件夹/temp/_MEIbxALM3/。此文件夹包含 Python 解释器、程序的源代码和 Pyinstaller 设法找到的模块(以及一些其他必要的文件)。
Scrapy 模块不仅仅是一个模块。它是一个完整的框架。它有自己使用的纯文本文件(Python 文件除外)。而且,它本身会导入很多模块。
Scrapy 框架尤其不能与 Pyinstaller 相处,因为它使用许多方法来导入 Pyinstaller 无法“看到”的模块。此外,Pyinstaller 基本上不会尝试在最终构建中包含不是 .py 文件的文件除非你告诉它。
那么,真正发生了什么?
存在于您计算机上“普通”scrapy 模块中的文本文件“VERSION”(您已使用 pip 或 pipenv 安装)未包含在程序构建中的模仿 scrapy 模块中。 Scrapy 需要这个文件; Python 为您提供FileNotFoundError,因为它从未被包含在内。因此,您必须使用 Pyinstaller 将该文件包含在您的程序构建中。
如何告诉 Pyinstaller 在哪里可以找到模块和文件?
This guy 说只需将丢失的文件从计算机上安装它们的位置复制到从 Pyinstaller 吐出的构建文件夹中。这确实有效。但是,有一种更好的方法,Pyinstaller 可以为您做更多的工作(防止您可能得到进一步的ImportErrors 和FileNotFoundErrors)。见下文:
build.spec 文件是你的朋友
spec 文件只是 Python 文件,Pyinstaller 像配置文件一样使用它来告诉它如何构建程序。阅读更多关于他们的信息here。下面是一个真实的 build.spec 文件示例,我最近使用它来构建一个带有 Windows 图形用户界面的 Scrapy 程序(我的项目名称是 B.O.T. Bot):
import gooey
gooey_root = os.path.dirname(gooey.__file__)
gooey_languages = Tree(os.path.join(gooey_root, 'languages'), prefix = 'gooey/languages')
gooey_images = Tree(os.path.join(gooey_root, 'images'), prefix = 'gooey/images')
a = Analysis(['botbotgui.py'],
pathex=['C:\\Users\\Colton\\.virtualenvs\\bot-bot-JBkeVQQB\\Scripts', 'C:\\Program Files (x86)\\Windows Kits\\10\\Redist\\ucrt\\DLLs\\x86'],
hiddenimports=['botbot.spiders.spider'],
hookspath=['.\\hooks\\'],
runtime_hooks=None,
datas=[('.\\spiders\\','.\\spiders\\'), ('.\\settings.py','.'),
('.\\scrapy.cfg','.'), ('.\\items.py','.'), ('.\\itemloaders.py','.'),
('.\\middlewares.py','.'), ('.\\pipelines.py','.')
]
)
pyz = PYZ(a.pure)
options = [('u', None, 'OPTION'), ('u', None, 'OPTION'), ('u', None, 'OPTION')]
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
options,
gooey_languages, # Add them in to collected files
gooey_images, # Same here.
name='BOT_Bot_GUI',
debug=False,
strip=None,
upx=True,
console=False,
windowed=True,
icon=os.path.join(gooey_root, 'images', 'program_icon.ico'))
#coll = COLLECT(exe,
#a.binaries,
#a.zipfiles,
#a.datas,
#options,
#gooey_languages, # Add them in to collected files
#gooey_images, # Same here.
#name='BOT_Bot_GUI',
#debug=False,
#strip=False,
#upx=True,
#console=False,
#windowed=True,
#icon=os.path.join(gooey_root, 'images', 'program_icon.ico'))
如果您想构建一个文件夹而不是独立的.exe,请取消注释最后一个区域。这是一个特定于我的计算机和项目结构的配置文件。因此,在您的文件中,您必须更改一些内容(例如,pathex 告诉 Pyinstaller 在 Windows 10 上哪里可以找到 DLL。但是,前提是相同的。
我的项目目录如下所示:
botbotgui.py botbot.py hooks images __init__.py itemloaders.py items.py middlewares.py pipelines.py __pycache__ scrapy.cfg settings.py spiders
特别注意hooks/ 目录。使用钩子可以让你免去很多麻烦。阅读更多关于 Pyinstaller 的钩子功能here。在hooks/ 目录中有一个用于 Scrapy 的挂钩文件。这将告诉 Pyinstaller 包含许多模块和文件如果你不使用 .spec 文件,它会错过。 这是迄今为止我在这里写的最重要的内容。如果您不执行此步骤,您将在每次尝试运行使用 Pyinstaller 构建的 Scrapy 程序时不断收到ImportErrors。 Scrapy 导入了 Pyinstaller 遗漏的许多模块。
hook-scrapy.py(注意:你的钩子文件必须像这样命名。):
from PyInstaller.utils.hooks import collect_submodules, collect_data_files
# This collects all dynamically imported scrapy modules and data files.
hiddenimports = (collect_submodules('scrapy') +
collect_submodules('scrapy.pipelines') +
collect_submodules('scrapy.extensions') +
collect_submodules('scrapy.utils')
)
datas = collect_data_files('scrapy')
写完正确的build.spec 文件后,您需要做的就是在shell 提示符下像这样运行Pyinstaller:
pyinstaller build.spec
Pyinstaller 应该会生成一个可以正常工作的程序的正确版本。问题解决了。