
每个人应该都经历过这样的场景拿到一个别人的Python项目满怀信心地敲下python main.py结果迎面就是一行ModuleNotFoundError: No module named xxx。然后开始凭直觉pip install装一个报一个装完这个坏那个最后整个环境面目全非项目还是跑不起来。requirements.txt就是为了终结这种“依赖地狱”而存在的。它本质上就是一份项目依赖的“购物清单”告诉你这个项目需要哪些库、各自的版本要求是什么。而我今天要聊的是怎么用这份清单在一台新电脑、一个新服务器、或者一个刚创建的虚拟环境里把项目所依赖的库完整、正确、不踩坑地装好。这篇文章不是简单念一遍pip install -r的官方文档而是把我从无数次环境重建、客户现场部署、换电脑续命中积累下来的实操细节和排查经验都翻出来希望能帮你少走点弯路。1. 依赖清单的本质为什么一份文本文件如此重要1.1 一份清晰的“项目依赖说明书”requirements.txt说到底是纯文本文件每行一条依赖规则。格式很简单但里面的门道不少最常见的几种写法是requests2.31.0 numpy1.24.0 pandas~1.5.3 flask3.0是精确锁定版本比如requests2.31.0这种写法最严格适合追求可复现性的场景表示最低版本限制只要不低于这个版本就行~是兼容版本意思是pandas~1.5.3允许自动升级到1.5.x系列的最新版但不会跑到1.6.0因为~只锁定形如“发布版本号”的部分或用来限制最高版本通常在处理已知不兼容问题时使用。理解了这几种语法你再看别人的requirements.txt时脑子里就有画面了项目作者是用精确锁定的思路保证环境一致性还是用范围约束的思路留出灵活性。这两种思路没有绝对好坏但会直接影响你导入依赖时的体验。范围约束的清单安装时可能拿到比你预期更新的版本反而触发潜在的不兼容。1.2 为什么别人环境能跑你这边就报错我自己在给客户做项目交付时最常见的问题就是“我们本地跑得好好的到你服务器就崩”。排查到最后十有八九是依赖环境不一致。举个具体例子项目里用了opencv-python开发机上Python是3.9到了服务器发现系统自带Python是3.6。此时你拿同一份requirements.txt去装大概率会看到ERROR: Could not find a version that satisfies the requirement opencv-python4.7.0.72。这不是因为清单写错了而是opencv-python从某个版本开始就停止了针对Python 3.6的轮子构建pip在索引里找不到对应的版本文件自然就报错。所以用requirements.txt导入依赖本质上你做的是三件事对齐Python解释器版本、对齐操作系统和平台架构、对齐依赖库本身的版本。缺一个都可能让导入过程从“顺利”变成“折腾”。2. 生成“干净”的 requirements.txt这一步决定了导入的成败很多人以为生成清单就是pip freeze requirements.txt这么简单。如果你只是自己写完代码自己用那确实够了但如果你想通过这份清单让项目在别的环境顺利跑起来pip freeze产出的内容往往会让对方欲哭无泪。2.1 pip freeze最快但最容易“带偏”pip freeze会把当前环境里所有已安装的第三方包全部列出来还包括那些间接依赖。换句话说你为了写爬虫装了requests它会顺带装一堆urllib3、certifi、idna、charset-normalizer之类的子依赖。这些间接依赖在freeze里也会被列出来。问题出在这些间接依赖很可能只是你环境里“恰好存在”的版本不一定是你代码实际需要的版本。当你的清单在别人电脑上导入时由于操作系统不同、Python版本不同间接依赖的版本解析逻辑可能发生变化导致最终安装出来的组合和你的环境并不一致反而更容易出兼容性冲突。所以我的习惯是pip freeze只用来快速备份当前环境或者在同一台机器上快速重建完全相同环境时用。如果是给别人分发项目我不会直接用它的输出当requirements.txt。2.2 pipreqs按代码实际引用扫描给项目生成“干净”清单我推荐用pipreqs。它会扫描项目目录里的所有import语句只抽取出代码里真正引用的第三方库再通过PyPI元数据反查对应的包名生成精简的清单。pip install pipreqs pipreqs ./project_dir --encodingutf8 --force--encodingutf8是防止源码里有中文注释导致解码报错--force是允许覆盖已经存在的requirements.txt。用pipreqs生成的清单非常“瘦身”基本只保留你直接 import 的包。但它也不是万能的如果代码里有动态导入比如__import__(os).path.join()这种写法或者importlib.import_module(fplugin_{name})这种模式pipreqs会识别不出来。另外如果你的项目通过setup.py或setup.cfg声明依赖pipreqs就覆盖不了。它适合纯脚本项目不太适合需要分发的包项目。2.3 pip-tools适合团队协作的进阶方案当项目规模变大、参与的人变多我建议改用pip-tools这套工作流。它的思想是你在requirements.in里只写顶层依赖然后用pip-compile自动解析出完整的锁定版本清单输出到requirements.txt。# requirements.in requests flask pandas执行pip install pip-tools pip-compile requirements.in生成的requirements.txt会列出所有包和间接依赖并标注# via requests之类的来源注释。好处是你在源文件里只关心顶层依赖交给工具去解析所有依赖关系。之后要升级某个库改requirements.in再重新pip-compile即可。这个方案在团队协作里特别实用。因为代码评审时我可以只看requirements.in感知依赖的大方向而不需要逐行审查几十条带版本号的间接依赖。而且pip-tools生成的内容非常规范直接拿来做环境导入也很少踩坑。3. 从零实操完整导入流程与参数选择这部分我按照自己在新服务器或新电脑上的完整操作顺序走一遍。假设你已经有一份requirements.txt现在要把依赖装到一个全新的环境里。3.1 先建一个隔离的虚拟环境拿到新机器第一件事绝对不是直接pip install -r requirements.txt。如果直接装在全局环境里过一段时间你会发现不同项目之间的依赖版本互相冲突整个环境的可维护性会迅速恶化。我通常用conda或venv先创建一个独立环境。以conda为例conda create -n myproject python3.10 conda activate myproject用venv的方式python -m venv .venv # Windows .venv\Scripts\activate # Linux / macOS source .venv/bin/activate选哪个取决于你的项目生态。如果你的项目涉及很多非Python的二进制依赖像numpy、scipy、pandas、opencvconda在处理二进制包依赖方面更省心如果项目比较纯粹venv就够用了而且它不额外依赖anaconda这种大体积工具。有一点必须强调激活虚拟环境后务必确认pip指向的是虚拟环境的pip。我见过太多人建了环境忘了激活或者激活失败仍在用全局pip安装最后包装到了别的环境里项目还是报ModuleNotFoundError。激活后可以用which pipLinux/macOS或where pipWindows看一眼路径确认无误再继续。提示conda create时指定python3.10是非常关键的一步。你要先看清楚项目原本是在什么Python版本下开发的千万别在Python 3.7的环境里硬装需要3.9的依赖这会让后面所有步骤都变得艰难。3.2 配置镜像源把下载速度拉满这一步在国内环境几乎是刚需。默认的pypi.org源在直连时速度不稳定几百兆的依赖包能磨到怀疑人生。我一般直接用清华镜像或阿里云镜像配置方式也很简单pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple这条命令会写入pip的配置文件之后所有pip install都会走镜像源。临时只想用一次也行pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple镜像源不只是“快”这么简单。某些基础库在官方源上的轮子可能缺失比如pandas对某些Python小版本的轮子未覆盖而镜像源会同步完整一些的轮子文件。实际操作中很多“找不到对应版本”的报错换个镜像源就莫名其妙解决了原因就在这。如果你在公司内网环境还可能要用公司自建的私有源。私有源上通常会有内部封装库但同时也可能缺少部分公共包这时候可以同时配多个源pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple --extra-index-url https://pypi.org/simple注意--extra-index-url会先查主源找不到再去额外源。这样既能保证内网库可用又不会遗漏公共包。3.3 执行 pip install -r requirements.txt 并看懂安装日志准备工作做完正式的安装命令很简短pip install -r requirements.txt但执行安装时我强烈建议不要干等学会看日志。正常安装过程分为三个阶段Collectingpip 逐个解析依赖包查询版本信息构建依赖解析图。这个阶段如果项目依赖很多可能要花几十秒甚至几分钟。如果你看到某个包一直在这阶段转圈大概率是网络不通或源里没有对应版本。Downloading下载轮子或源码包。轮子文件是.whl结尾下载完直接安装源码包是.tar.gz结尾下载完还会被解压并执行构建脚本。看到源码包时就要留个心因为源码安装意味着可能涉及编译需要gcc、python3-dev之类的系统级依赖。Installingpip 逐个安装包并执行依赖冲突检查。安装过程中如果出现Building wheel for xxx的提示意味着该包没有提供编译好的轮子需要现场构建。这类安装最容易报错常见的原因是系统里缺编译工具链。在Ubuntu上可以先补基础构建工具sudo apt update sudo apt install build-essential python3-dev在CentOS/RHEL系上对应的是yum groupinstall Development Tools加python3-devel。安装完成后我还习惯做一步清理——因为安装过程中可能会顺手解析并安装很多间接依赖前文说过这些间接依赖没必要出现在你最终交付的清单里。也就是说装完环境我会基于实际运行情况重新整理一份requirements.txt备着防止以后再次重建环境时被间接依赖版本差异坑到。3.4 导入后的三件套验证很多人装完依赖就直接python main.py等到跑到第1000行代码才知道某个库缺编译组件。我的习惯是装完先做三件事把风险前置。第一件用pip check验证依赖关系是否完整pip check这个命令会检查当前环境里是否存在依赖冲突、缺失依赖等情况。正常情况下输出为空。如果它有输出说明环境里存在不满足的依赖约束需要处理。第二件用python -c批量导入核心包。模拟项目实际使用场景把项目里重要的第三方库一次性 import 一遍python -c import requests, numpy, pandas, flask; print(all imports ok)如果这些核心库能顺利导入说明环境的大方向是对的。第三件尝试跑一次项目的测试用例或者最小可执行入口。这一步最有说服力。很多包比如torch、tensorflow、opencv虽然import没问题但调用特定功能时会依赖底层的动态链接库libGL.so.1、libgomp.so.1等这些是系统层面的依赖不是pip能解决的。跑一次真实执行流程能把这些隐藏问题尽早暴露出来。4. 高频报错与排查技巧实录4.1 常见报错速查表我把这些年环境导入过程中遇到的高频问题整理成了表格方便你遇到了直接对号入座。报错信息特征常见原因解决思路Could not find a version that satisfies the requirementPython版本不满足该包要求或源中确实没有该版本检查当前Python版本换成更高版本Python换完整镜像源No matching distribution found for xxx包名拼写错误、私有源缺失、或平台不兼容核对包名用pip index versions xxx查看可用版本确认系统架构Failed to build wheel for xxx需要编译源码包但缺编译工具链或系统库Ubuntu装build-essential python3-devCentOS装对应开发工具组查看报错中缺失的.h文件猜对应库名ERROR: pips dependency resolver does not currently take into account all the packages that are installed环境中已安装的包与目标依赖存在冲突在干净的虚拟环境安装不要混装error: subprocess-exited-with-error共性外壳报错真正原因在下方拼接的Caused by段别只看第一行往上翻找真正的报错位置SSL certificate verify failed私有源证书不受信任私有源场景配置企业证书公共源换成可信镜像源Cant find a version of xxx that satisfies the requirement ... from versions: none通常意味着该包在目标Python版本下没有可用版本升级Python版本或换更低版本的包这里我想特别展开一下subprocess-exited-with-error。很多经验不多的朋友看到这串英文就慌了其实它只是一个“笼子”具体原因往往在它后面跟着的Caused by段落里。比如安装pymssql这种需要编译的库真正的报错可能是fatal error: sqlfront.h file not found这说明缺freetds-dev这个系统库。解决办法是sudo apt install freetds-dev后重装。所以排查时一定要把滚动的终端日志往上翻找到第一次出现error:的位置那才是根源。4.2 依赖冲突用工具拆解依赖树团队项目里依赖冲突比缺失更让人头疼。举个我真实遇过的场景项目A依赖flask2.0而它内部的werkzeug又要求2.2但项目里另一个库flask-admin却锁死了werkzeug2.0.3。这种情况下pip install -r requirements.txt会陷入解析困境最终报依赖解析失败。排查这类问题我用pipdeptree它能以树状结构展示当前环境的依赖关系pip install pipdeptree pipdeptree -p werkzeug-p参数指定包名就能看到werkzeug是谁引入的、谁在制约它的版本。然后你可以决定升级flask-admin到支持新werkzeug的版本或者反过来在requirements.txt里给werkzeug加一个明确的2.1约束让各方都能接受。另外我想提醒一点pip install -r的依赖解析是全局性的它不会只检查requirements.txt里的顶层包而是会把所有间接依赖一起解析。如果环境里已经装了某冲突版本的包没有卸载新的解析也会被干扰。所以最安全的做法永远是在干净环境里安装遇到顽固冲突时不要犹豫直接重建虚拟环境再试。4.3 三条来自实战的避坑建议第一不要迷信pip freeze生成的清单。它适合在同平台、同Python版本环境下快速备份恢复但不适合跨平台交付。我在Windows上freeze出来的清单拿到Linux上装每次都有一堆包找不到因为很多包在不同平台上的构建产物差异很大版本索引也不完全一致。第二先升级pip再执行清单安装。pip install -r之前建议先做一步pip install --upgrade pip。老版本的pip在依赖解析能力上偏弱处理复杂依赖时容易报解析失败或者做出次优选择。用新版pip能减少很多莫名其妙的报错。第三如果你的项目里需要安装大量依赖建议分步骤安装不要一把梭。先把核心的科学计算库和大体积库装好再装业务依赖。原因很简单一旦某个包构建失败你执行pip install -r时默认会中止它的前面已经装好的包和后面没装的包之间状态就很混乱。分批安装可以更快定位是哪一类包出了问题。比如pip install numpy pandas scikit-learn matplotlib pip install -r requirements.txt不过要注意分批安装时先装大包有个好处这些包通常有复杂的系统级依赖先解决它们能避免后面的包在编译期反复失败。注意在安装过程中如果中断过一次再次pip install -r requirements.txt时之前已经成功装的包会被跳过不会再重复下载安装。这其实是pip设计上的一个优点不用怕中断继续跑就行。5. 分环境管理一份清单走天下的进阶思路当项目要部署到多个环境开发、测试、生产时一份requirements.txt往往不够用。因为开发环境需要pytest、sphinx这类调试和文档工具而生产环境装了纯粹是浪费空间、还会增加攻击面。我的做法是把清单拆成多层requirements-base.txt # 所有环境共用的核心依赖 requirements-dev.txt # 基础依赖 测试/开发工具 requirements-prod.txt # 基础依赖 生产专用依赖比如 gunicornrequirements-dev.txt第一行可以写-r requirements-base.txt这样 pip 会先加载基础清单再在此基础上安装额外的开发依赖。这个文件内容大致长这样# requirements-dev.txt -r requirements-base.txt pytest7.4.0 pytest-cov4.1.0 sphinx6.0生产环境执行安装时用不同的文件pip install -r requirements-prod.txt这种分层思路在实际运维中很节省时间。我见过不少团队把pytest直接塞进生产环境的依赖里不仅提高了容器镜像体积还有潜在的安全隐患。分层之后每类环境都能获得恰好够用的依赖集合。另外动态版本的requirements我偶尔也会配合--extra-index-url使用。比如公司内网有priv_repo而生产环境网络隔离、只能访问内网源那你就可以准备一份requirements-internal.txt把公共源地址也写在文件里# requirements-internal.txt --extra-index-url https://pypi.org/simple ...requirements.txt里也可以直接写--find-links、--index-url这类参数从文件层面完成源的选择。这样一个文件复制到生产环境直接执行安装命令就能按照文件内指定的源去下载不用额外敲参数。如果你的项目对版本一致性要求极高比如金融、医疗或者需要审计的领域可以在清单里带上哈希校验。用 pip 官方推荐的方式pip install --require-hashes -r requirements.txt此时requirements.txt里每行包的版本后面必须带--hashsha256:...形式的哈希值。这能防止包被篡改或者索引被污染。虽然维护哈希很繁琐但配合pip-compile --generate-hashes可以自动生成。6. 我自己维护依赖清单的几个小习惯最后聊几个我长期养成的个人习惯不算什么标准答案但确实让环境管理轻松了很多。第一个习惯是每个项目配一个独立的虚拟环境并且把激活命令写进项目README的第一段。换机器时最怕的不是装不上而是忘了这个项目用的Python版本和依赖入口。打开README一行命令就能激活环境对后续接手的人来说是巨大的善意。第二个习惯是每次安装新库之后立刻更新对应的requirements文件。我不会等到项目收尾才一起去整理依赖那样极容易遗漏。比如刚给爬虫加了playwright马上在requirements.txt里加一行playwright1.43.0现在做只花十秒以后重建环境省一小时。第三个习惯是定期用pip list --outdated审视依赖更新。虽然requirements.txt里的版本不应该随便升但关注哪些包有安全更新仍然是必要的。遇到大版本变更我会在虚拟环境里先试升级跑一轮测试用例过了再更新清单。这个流程能避免生产环境里迭代到老版本导致日后的安全问题没人管。第四个习惯是关于conda和pip的混用一个人为多conda install和pip install同时用来维护requirements.txt会乱。我的原则是只用 pip 管理 Python 层的依赖conda 只负责创建环境和安装 Python 解释器。这样requirements.txt始终是唯一的依赖入口不会出现“这个环境里有一半依赖只有 conda 知道”的尴尬局面。依赖环境这件事看上去只是敲几行命令但真正决定体验的是对版本解析、平台差异、构建工具链这些底层逻辑的理解。掌握了这些requirements.txt就从一个“看天吃饭”的文本文件变成你手里可控、可复现、可排查的工程工具。希望这篇内容能帮你在下一次环境重建时少一点焦头烂额多一分从容。