pipreqs 实战指南:基于项目 import 自动生成 requirements.txt 的完整方案
pipreqs 实战指南:基于项目 import 自动生成 requirements.txt 的完整方案
本文围绕开源项目 pipreqs 的官方文档(见 README.rst 与 docs/readme.rst)展开,结合仓库源码 pipreqs/pipreqs.py 与测试用例 tests/test_pipreqs.py,系统讲解如何不依赖
pip freeze、只根据项目代码中真实出现的 import 语句来生成requirements.txt。读完本文,你将掌握 pipreqs 的全部命令行参数、底层实现原理,以及--mode、--diff、--clean、Jupyter Notebook 扫描等高级用法,能够快速为任意 Python 项目产出干净、可复现的依赖清单。
一、为什么不用 pip freeze,而选 pipreqs
官方 README 用三条理由直接点明了 pipreqs 与传统 pip freeze 路线的本质区别:
pip freeze只会保存当前环境中通过pip install安装的包,与项目代码实际用到的模块无关;pip freeze会把环境里所有包全部写入(如果没有virtualenv隔离),其中包含大量你当前项目根本用不到的包,导致requirements.txt臃肿且难以迁移;- 某些场景你只是想为新项目快速生成
requirements.txt,根本不需要先安装这些模块。
pipreqs 的思路恰好相反:它直接扫描项目源码中的 import / from ... import ... 语句,按“代码真正引用了什么”来生成依赖清单。项目主页描述也印证了这一设计初衷——“Generate pip requirements.txt file based on imports of any project”(见 pyproject.toml)。
二、安装
1. 标准安装
pip install pipreqs
该方式会一并安装支持 Jupyter Notebook 扫描所需的依赖(nbconvert、ipython),对应 pyproject.toml 中声明的依赖项:yarg>=0.1.9、docopt>=0.6.2、nbconvert>=7.11.0、ipython>=8.12.3。安装完成后会注册 pipreqs 命令行入口(pipreqs = "pipreqs.pipreqs:main")。
2. 精简安装(不需要 Notebook 支持时)
如果你不需要扫描 Jupyter Notebook,可以跳过相关依赖,只装核心组件:
pip install --no-deps pipreqs
pip install yarg==0.1.9 docopt==0.6.2
注意:这样安装后如果仍使用
--scan-notebooks,程序会抛出NbconvertNotInstalled异常,提示需要安装nbconvert与ipython(见 pipreqs/pipreqs.py 中handle_scan_noteboooks的实现)。
3. Python 版本要求
根据 pyproject.toml,pipreqs 要求 Python >=3.9, <3.14,支持 3.9~3.13。
三、快速上手:一条命令生成 requirements.txt
最简单的用法是传入项目目录路径:
$ pipreqs /home/project/location
Successfully saved requirements file in /home/project/location/requirements.txt
不传路径时默认扫描当前工作目录(源码中 input_path is None 时回退为 os.path.abspath(os.curdir),见 pipreqs/pipreqs.py)。默认输出文件即为项目目录下的 requirements.txt,内容形如:
wheel==0.23.0
Yarg==0.1.9
docopt==0.6.2
生成的文件按包名(忽略大小写)排序,与 pip freeze 的排序风格一致(排序逻辑见 init 函数中的 sorted(imports, key=lambda x: x["name"].lower()))。
四、命令行参数全解析
官方文档给出的完整用法如下:
Usage:
pipreqs [options] [<path>]
Arguments:
<path> The path to the directory containing the application files for which a requirements file
should be generated (defaults to the current working directory)
各选项的作用与实战说明:
| 选项 | 说明 | 实战要点 |
|---|---|---|
--use-local |
仅使用本地已安装包的信息,不查询 PyPI | 离线环境首选;只列出本机 site-packages 中能找到的包 |
--pypi-server <url> |
使用自定义 PyPI 服务器 | 默认服务器为 https://pypi.python.org/pypi/,可用于私有镜像 |
--proxy <url> |
使用代理,参数会传给 requests 库 | 也可通过环境变量设置 HTTP_PROXY / HTTPS_PROXY |
--debug |
打印调试信息 | 等价于把日志级别调为 DEBUG |
--ignore <dirs>... |
忽略额外目录,多个目录用逗号分隔 | 例如 --ignore .ignored_dir,.ignore_second |
--no-follow-links |
不跟随项目中的符号链接 | 防止扫描误入链接指向的外部目录 |
--ignore-errors |
扫描文件时忽略错误 | 遇到语法错误文件时打印堆栈并继续,而不是直接抛异常退出 |
--encoding <charset> |
指定文件打开编码 | 默认 utf-8 |
--savepath <file> |
将依赖清单保存到指定文件 | 与默认的 项目目录/requirements.txt 不同 |
--print |
将依赖清单输出到标准输出 | 不写文件,方便管道操作 |
--force |
覆盖已存在的 requirements.txt | 默认情况下文件已存在会拒绝写入 |
--diff <file> |
对比指定文件中的模块与项目实际 import | 只报告差异,不修改文件 |
--clean <file> |
清理指定文件,删除项目未 import 的模块 | 直接改写该文件 |
--mode <scheme> |
动态版本方案:compat、gt 或 no-pin |
见下文“动态版本控制”小节 |
--scan-notebooks |
同时扫描 Jupyter Notebook(.ipynb)文件中的 import |
需 nbconvert / ipython 支持 |
代理的两种配置方式
pipreqs /path/to/project --proxy http://10.10.1.10:3128
或者在终端设置环境变量:
$ export HTTP_PROXY="http://10.10.1.10:3128"
$ export HTTPS_PROXY="https://10.10.1.10:1080"
源码中 --proxy 会构造成 {"http": url, "https": url} 字典直接传给 requests,而环境变量方案则由 requests 库自身识别。
五、深入源码:import 是如何变成 requirements.txt 的
要真正用好 pipreqs,理解它的四步流水线会很有帮助。整体流程都在 pipreqs/pipreqs.py 中,入口为 init()。
1. 扫描目录并做 AST 解析(get_all_imports)
get_all_imports() 使用 os.walk 递归遍历目标目录,并做三件关键事:
- 默认忽略目录:
.hg、.svn、.git、.tox、__pycache__、env、venv、.venv、.ipynb_checkpoints,这些目录不会被扫描; - 支持
--ignore追加忽略目录:额外目录会先取os.path.basename(os.path.realpath(e))归一化后再加入忽略列表; - 只处理指定扩展名:默认
[".py", ".pyw"],开启--scan-notebooks后追加.ipynb。
对每个文件,pipreqs 用 Python 标准库 ast.parse 把源码解析成抽象语法树,再遍历所有 ast.Import 和 ast.ImportFrom 节点收集模块名,而不是用正则匹配文本,因此能准确处理 import os.path as test、from sys import argv 这类写法。测试用例 tests/_data/test.py 正是用大量“花式 import”验证了这一能力。
收集到的原始模块名会做两步清洗:
- 只保留
.之前的第一段(from django.conf import ...只记django); - 与“项目自身候选模块”求差集(目录名、
.py文件名本身不算外部依赖)。
2. 剔除标准库模块
清洗后的模块集合与 pipreqs/stdlib(1785 行的标准库模块清单)做差集,time、logging、os、sys 等标准库模块会被自动排除。测试 test_get_all_imports 明确断言了 time、logging、curses、__future__、django、models 不会出现在结果中(见 tests/test_pipreqs.py)。
3. 导入名到 PyPI 包名的映射(get_pkg_names)
import 名并不总等于 pip 包名。pipreqs 内置了一张映射表 pipreqs/mapping,例如:
bs4:beautifulsoup4cv2:opencv-pythondjango:DjangoPIL:Pillowflask:FlaskMySQLdb:MySQL-pythonbson:pymongo
映射不存在时才直接使用 import 名本身。这张表还天然完成了依赖去重:比如 tests/_data_duplicated_deps/db.py 同时 import pymongo 和 from bson.objectid import ObjectId,由于 bson 被映射到 pymongo,最终只生成一行 pymongo==x.x.x(测试 test_deduplicate_dependencies 验证了这一点)。
4. 解析版本号(本地包 + PyPI 查询)
版本号解析分两种模式:
--use-local(仅本地):get_locally_installed_packages()遍历sys.path,读取各包目录下的top_level.txt,把“包名 + 版本 + 导出的顶层模块”配对;get_import_local()再按 import 名匹配包名或导出模块,返回本地包信息。离线、或不想访问公网时使用。- 默认模式(本地 + PyPI):先在本地查找,查不到的 import 名进入
get_imports_info(),向{pypi_server}{包名}/json发起请求,用yarg库解析 JSON 拿到latest_release_id作为版本号。包不存在或网络异常时只打警告并跳过该包。
源码在解析完成后会输出提示,要求人工核对最终清单,避免 import 名与包名映射带来的依赖混淆。此外,结果列表统一按包名(小写)排序后再写入文件。
六、高级用法详解
1. 动态版本控制:--mode
默认生成固定版本(==)。需要更灵活的版本约束时使用 --mode,支持三种方案(见 pipreqs/pipreqs.py 的 dynamic_versioning()):
| scheme | 生成格式 | 示例 |
|---|---|---|
默认(不传 --mode) |
== 精确锁定 |
Flask==1.1.2 |
compat |
~= 兼容版本 |
Flask~=1.1.2 |
gt |
>= 下限版本 |
Flask>=1.1.2 |
no-pin |
不写版本号 | Flask |
传入非法 scheme 会抛出 ValueError,提示只能用 compat、gt 或 no-pin。测试 test_dynamic_version_no_pin_scheme、test_dynamic_version_gt_scheme、test_dynamic_version_compat_scheme 分别验证了三种输出格式(见 tests/test_pipreqs.py)。
2. 依赖体检:--diff 与 --clean
这两个选项都基于 parse_requirements()(解析已有 requirements 文件,支持 <、>、=、!、~ 等分隔符,自动跳过注释与空行)和 compare_modules()(求“文件中模块 − 项目 import 模块”的差集):
--diff <file>:只打印“在文件中但项目里没用到”的模块清单,不改动文件;--clean <file>:直接把这类多余模块从文件中删除。若没有多余模块,则输出Nothing to clean in <file>。
测试 test_clean_with_imports_to_clean 演示了完整流程:先用 --force 重新生成 requirements.txt,再在另一个不含 sqlalchemy 的项目上执行 --clean,最终文件中不再出现 sqlalchemy。
3. 离线场景:--use-local
pipreqs /path/to/project --use-local
只输出本机 sys.path 中能解析到的包,适合无外网环境或需要快速出结果的场景。测试 test_init_local_only 验证了输出项全部落在本地已安装包集合内。
4. 自定义 PyPI 与代理
pipreqs /path/to/project --pypi-server https://mirror.example.com/pypi/
pipreqs /path/to/project --proxy http://proxy.example.com:8080
--pypi-server 会替换默认的 https://pypi.python.org/pypi/;测试 test_custom_pypi_server 也验证了传入非法 server(如 nonexistent)时会按 requests 的规则抛错。企业内网或镜像站场景非常实用。
5. 忽略目录、符号链接与容错
pipreqs /path/to/project --ignore tests,docs,migrations
pipreqs /path/to/project --no-follow-links
pipreqs /path/to/project --ignore-errors
--ignore用逗号分隔多个目录名,测试test_ignored_directory验证了.ignored_dir,.ignore_second中 import 的click、getpass不会进入结果;--no-follow-links对应os.walk(..., followlinks=False),避免跟随符号链接导致扫描范围失控;--ignore-errors在文件解析失败时打印堆栈并继续(测试test_ignore_errors:对含语法错误的项目也能返回空结果),而不带该选项时遇到非法文件会直接抛异常(测试test_invalid_python断言抛出SyntaxError)。
6. 扫描 Jupyter Notebook:--scan-notebooks
pipreqs /path/to/project --scan-notebooks
开启后,.ipynb 文件会先通过 nbconvert 的 PythonExporter 转成 Python 源码再走 AST 解析(见 ipynb_2_py())。仓库测试用 tests/_data_notebook 目录验证了三点:
test_import_notebooks:能正确提取 notebook 代码单元格中的 import,同时排除 magic 命令(如%matplotlib inline)等非代码内容;test_ipynb_2_py:同一批 import 在.py与.ipynb中扫描结果完全一致(对比 tests/_data/test.py 与 tests/_data_notebook/test.ipynb);test_ignore_notebooks:未开启该选项时 notebook 不被扫描,生成的 requirements 为空。
注意:按 pyproject.toml,nbconvert 与 ipython 已是标准依赖,若用 --no-deps 精简安装则需手动补装,否则会报 NbconvertNotInstalled。
7. 输出控制:--savepath、--print、--force
--savepath <file>:把结果写到指定文件(测试test_init_savepath使用requirements2.txt验证);--print:把结果打印到标准输出而不落盘(测试test_output_requirements断言 stdout 内容与写入文件的内容完全一致),适合配合>重定向到任意位置;--force:覆盖已存在的 requirements 文件。默认行为是拒绝覆盖——当目标文件已存在且未传--print/--savepath/--force时,会输出requirements.txt already exists, use --force to overwrite it并直接返回(测试test_init_overwrite验证文件内容保持不变)。
七、工作原理小结:一次完整的调用链
以 pipreqs /home/project/location 为例,完整链路为:
main()用docopt解析命令行参数,设置日志级别;init()确定输入路径、编码、忽略目录、保存路径,并检查文件是否已存在;get_all_imports()遍历目录、AST 解析、剔除标准库与项目自身模块;get_pkg_names()通过 pipreqs/mapping 完成 import 名 → PyPI 包名映射与去重;- 本地安装包匹配 + PyPI JSON 查询获取版本号,合并后按小写包名排序;
generate_requirements_file()按所选 symbol(==/~=/>=/ 空)写出文件(无版本信息的包只写包名)。
测试套件 tests/test_pipreqs.py 覆盖了上述全部分支,配合 tests 目录下的各种数据样本(正常、含重复依赖、含非法语法、含 notebook、含 pyw 文件等),可作为你上手验证和二次开发时的参考。
八、使用建议
- 生成后务必人工核对最终清单:README 和源码都提示,import 名与 PyPI 包名并非一一对应,可能产生“依赖混淆”,尤其是通过
--mode no-pin或本地查询生成的版本号; - 团队协作或 CI 场景,建议结合
--mode gt或compat保留版本弹性,避免==锁定过死导致升级困难; - 迁移老项目时,先用
--diff体检、再用--clean清理,可以无痛精简历史遗留的 requirements 文件; - 在无外网环境,优先
--use-local;在内网有镜像站时,用--pypi-server指向镜像,配合--proxy解决网络代理问题。
以上能力全部基于 pipreqs 0.5.0 的官方文档(README.rst)、核心实现(pipreqs/pipreqs.py)与测试用例(tests/test_pipreqs.py),可放心按本文步骤在本地复现。