Python zipfile.Path 怎样读取压缩包内的嵌套资源
来源:17golang原创
时间:2026-11-14 06:10:54 157浏览 收藏
处理 ZIP 包时,最容易卡住的场景不是“怎么解压”,而是只想读取其中一个多级目录里的配置或模板,却不想把整个压缩包落到本地。zipfile.Path 可以把 ZIP 内部表示成一棵可遍历的虚拟路径树:从根节点开始用 / 或 joinpath() 进入目录,再用 read_text()、read_bytes() 或 open() 读取目标文件。
官方地址:https://docs.python.org/3/library/zipfile.html
要点是把“路径拼接”和“路径安全”分开处理:zipfile.Path 负责访问 ZIP 内的虚拟节点,但不会替你校验不可信成员名。可信来源可以直接按层读取;外部上传的 ZIP 则要先检查成员名,再进行读取或解压。
问题现场:嵌套文件不想先解压
假设压缩包里有这样的结构:
project.zip
├── config/
│ └── app/
│ └── settings.json
└── docs/
└── readme.txt
传统做法是先调用 extractall(),然后用本地路径读取 config/app/settings.json。如果只需要查看一个小文件,这会多出临时目录、清理目录和文件名冲突等问题。zipfile.Path 提供的是另一条路径:ZIP 仍保持打开状态,代码只在压缩包内部定位和读取目标成员。

初步判断:先从根节点观察结构
不要一开始就把目标路径写死。先把 ZipFile 交给 zipfile.Path,再通过 iterdir() 查看根节点的直接子项。这样可以区分“目录名写错”和“目标文件确实不存在”这两类问题。
from pathlib import Path
from zipfile import Path as ZipPath
from zipfile import ZipFile
archive = Path("project.zip")
with ZipFile(archive) as zf:
root = ZipPath(zf)
for child in root.iterdir():
# 这里只观察 ZIP 根目录的直接子项,不会递归展开全部成员。
kind = "目录" if child.is_dir() else "文件"
print(f"{kind}: {child.name}")
这里的 root 不是磁盘上的目录,它代表 ZIP 内部的空路径。iterdir() 只列当前节点的子项;遇到目录时可以继续调用 iterdir(),遇到文件时则应进入读取分支。
动手验证:拼接并读取嵌套资源
确认根节点结构后,就可以用 / 操作符写出和目录层级一致的路径。下面的例子只读取 settings.json,不把它解压到工作目录。
import json
from pathlib import Path
from zipfile import Path as ZipPath
from zipfile import ZipFile
with ZipFile(Path("project.zip")) as zf:
root = ZipPath(zf)
settings = root / "config" / "app" / "settings.json"
if not settings.exists():
# 先判断成员是否存在,把路径拼写错误和读取异常区分开。
raise FileNotFoundError(settings.at)
if not settings.is_file():
# 目标若是目录,不能直接按 JSON 文件读取。
raise IsADirectoryError(settings.at)
text = settings.read_text(encoding="utf-8")
config = json.loads(text)
print(config.get("environment", "unknown"))
joinpath("config", "app", "settings.json") 也可以表达同样的路径。较新的 Python 版本还支持 Path.open() 的文本和二进制模式;要读取二进制资源时可以使用 read_bytes(),避免先把内容错误地解码成文本。
from zipfile import Path as ZipPath
from zipfile import ZipFile
with ZipFile("project.zip") as zf:
root = ZipPath(zf)
icon = root.joinpath("assets", "icons", "app.bin")
if icon.is_file():
# 二进制资源直接保留 bytes,避免编码转换造成数据损坏。
payload = icon.read_bytes()
print(f"读取字节数: {len(payload)}")
定位原因:Path 不负责路径安全
如果 ZIP 来自用户上传、第三方下载或消息附件,不能因为使用了 zipfile.Path 就认为路径安全。Python 官方文档明确提示:与 ZipFile.extract() 和 extractall() 会尝试处理成员名不同,Path 不会清理压缩包内的绝对路径或 .. 路径,调用方需要自行校验。
这也是两个容易混淆的边界:
zipfile.Path的职责是访问 ZIP 内的虚拟路径;它不会把文件写到磁盘。extract()或extractall()的职责是写出文件,写出前应防止成员名逃逸目标目录。

修复方案:建立安全成员名边界
一个实用的做法是先遍历 ZipInfo 的成员名,只允许相对路径,并拒绝包含空成员、绝对路径或父级跳转的条目。校验通过后,再把确定的相对路径交给 ZipPath 访问。
import posixpath
from pathlib import PurePosixPath
from zipfile import Path as ZipPath
from zipfile import ZipFile
def is_safe_member(name: str) -> bool:
# ZIP 成员统一按正斜杠处理,拒绝绝对路径和父级跳转。
normalized = posixpath.normpath(name.replace("\\", "/"))
parts = PurePosixPath(normalized).parts
return bool(name) and normalized != "." and not normalized.startswith("/") and ".." not in parts
with ZipFile("project.zip") as zf:
unsafe = [info.filename for info in zf.infolist() if not is_safe_member(info.filename)]
if unsafe:
# 不可信归档中只要出现越界成员,就停止后续读取流程。
raise ValueError(f"发现不安全的 ZIP 成员: {unsafe[:3]}")
root = ZipPath(zf)
target = root.joinpath("config", "app", "settings.json")
if target.is_file():
# 通过成员名检查后,再读取目标文件的文本内容。
settings_text = target.read_text(encoding="utf-8")
这段检查解决的是成员名的路径边界,不等于完整的资源安全策略。生产代码还应按业务限制压缩包总大小、成员数量和单个文件大小,并对编码、重复成员名和损坏归档单独处理。
验证结果:把读取流程固定成几个判断
回到最开始的嵌套资源问题,可以把判断顺序固定下来:先打开 ZipFile,再从 ZipPath(zf) 根节点拼接路径;路径不确定时用 iterdir() 观察;目标节点先判断 exists(),再用 is_file() 或 is_dir() 区分类型;文本、二进制和流式读取分别选择对应方法;外部归档则在读取前检查成员名。
| 场景 | 推荐写法 | 需要留意 |
|---|---|---|
| 查看根目录 | ZipPath(zf).iterdir() | 只返回当前层级子项 |
| 访问嵌套文件 | root / "a" / "b.txt" | 这是 ZIP 内的虚拟路径 |
| 读取文本 | path.read_text(encoding="utf-8") | 确认文件编码 |
| 读取二进制 | path.read_bytes() | 不要误用文本解码 |
| 处理外部归档 | 先检查 ZipInfo.filename | Path 不替你清理越界成员 |
常见问题
zipfile.Path 能直接替代 pathlib.Path 吗?
不能完全替代。两者都有路径拼接和遍历的表达方式,但 zipfile.Path 指向 ZIP 内部成员,不代表本地文件系统;它的读取、写入和安全语义也由 ZIP 容器决定。
为什么 iterdir() 看不到我以为存在的目录?
ZIP 格式允许目录条目缺失,文件名本身也可能带有多级前缀。可以先查看 zf.namelist() 或逐层遍历,再根据实际成员名拼接路径,不要假设每一级都有独立的目录条目。
Path 读取之前一定要解压吗?
不一定。只读文本或二进制成员时,可以保持 ZipFile 打开并直接读取;需要让其他程序使用真实磁盘路径,或需要批量落盘时,才考虑解压流程。
总结
zipfile.Path 适合把“读取 ZIP 内某个嵌套资源”写成清晰的路径操作:从根节点开始遍历,按层拼接,按节点类型判断,再选择文本、字节或打开流。真正容易出问题的地方在安全边界:对于不可信压缩包,路径访问不会自动替你清洗成员名,必须把成员名校验、资源大小限制和异常处理放在读取流程之前。
-
文章 · python教程 | 9分钟前 | Python教程 · 进程管理 · Python subprocess.Popen TimeoutExpired 进程组 start_new_session killpg340 收藏
-
241 收藏
-
447 收藏
-
275 收藏
-
466 收藏
-
312 收藏
-
260 收藏
-
文章 · python教程 | 21分钟前 | 协程 · 任务调度 · Python教程 · asyncio · 兼容性 · Python asyncio 异步调度 create_task eager_start155 收藏
-
215 收藏
-
文章 · python教程 | 9小时前 | SQLite · Python教程 · Python SQLite sqlite3 SetLimit SQLITE_LIMIT_VARIABLE_NUMBER getlimit 参数编号315 收藏
-
文章 · python教程 | 11小时前 | 并发编程 · Python教程 · Python multiprocessing Pool initializer spawn initargs 多进程配置295 收藏
-
文章 · python教程 | 11小时前 | 文件处理 · 标准库 · csv · python · 数据导入 · Python csv DictReader DictWriter 空行 newline lineterminator365 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习