将从网络服务器获取的解压文件写入磁盘

2 投票
3 回答
546 浏览
提问于 2025-04-18 14:56

我可以获取一个文件,它的 content-encodinggzip

这是不是意味着服务器存储的是压缩文件?那对于存储为压缩的 zip 或 7z 文件的情况也是这样吗?

如果是这样的话(假设 durl 是一个 zip 文件)

>>> durl = 'https://db.tt/Kq0byWzW'
>>> dresp = requests.get(durl, allow_redirects=True, stream=True)
>>> dresp.headers['content-encoding']
'gzip'

>>> r = requests.get(durl, stream=True)
>>> data = r.raw.read(decode_content=True)

但是数据出来却是空的,而我想要在提取这个 zip 文件到磁盘时直接获取数据!!

3 个回答

0

你需要区分一下 内容编码(不要和 传输编码搞混了)和 内容类型

简单来说,content-type 是你想获取的资源的媒体类型(也就是实际的文件类型)。而 content-encoding 是在发送给客户端之前对数据进行的任何修改。

假设你想获取一个名为 "foo.txt" 的资源。它的内容类型可能是 text/plain。除此之外,数据在传输过程中可能会被修改,这就是 content-encoding。所以在这个例子中,你可以有一个内容类型为 text/plain,同时内容编码为 gzip。这意味着在服务器将文件发送出去之前,它会使用 gzip 对文件进行压缩。这样在网络上传输的只有压缩后的数据,而不是原始文件的字节(foo.txt)。

客户端的工作就是根据这些头信息来处理数据。

现在,我不太确定 requests 或底层的 Python 库是否会这样做,但很可能它们会。如果没有,Python 自带一个默认的 gzip 库,所以你可以自己处理这个问题。

考虑到这些,来回答你的问题:不,拥有 gzip 的 "content-encoding" 并不意味着远程资源是一个压缩文件。包含这个信息的字段是 content-type(根据你的问题,这个字段的值可能是 application/zipapplication/x-7z-compressed,具体取决于实际使用的压缩算法)。

如果你无法根据 content-type 字段确定真实的文件类型(例如,如果它是 application/octet-stream),你可以将文件保存到磁盘,然后用十六进制编辑器打开它。对于 7z 文件,你应该能在某处看到字节序列 37 7a bc af 27 1c,很可能在文件的开头或者文件末尾的112字节处。对于 gzip 文件,开头应该是 1f 8b

既然你在 content-encoding 字段中看到了 gzip:如果你得到的是 7z 文件,你可以确定 requests 已经解析了 content-encoding 并为你正确解码了。如果你得到的是 gzip 文件,这可能有两种情况。要么是 requests 没有解码任何东西,要么文件确实是一个 gzip 文件,因为它可能是用 gzip 编码发送的。这就意味着它被压缩了两次。这听起来没有什么意义,但根据服务器的不同,这种情况可能还是会发生。

你可以简单地在控制台上运行 gunzip 来看看结果。

0

你需要从请求文件中获取内容才能进行写入。已经确认可以正常工作:

import requests
durl = 'https://db.tt/Kq0byWzW'
dresp = requests.get(durl, allow_redirects=True, stream=True)
dresp.headers['content-encoding']

file = open('test.html', 'w')
file.write(dresp.text)
0

首先,durl 不是一个压缩文件,它是一个 Dropbox 的登录页面。你看到的其实是用 gzip 编码发送的 HTML 文件。如果你用 gzip 解码从原始套接字获取的数据,你会得到 HTML 内容。所以使用原始数据只是为了隐藏你意外获取了一个不同的文件,而不是你想要的那个。

根据你在 这个链接 上问的问题:

有没有人知道如何直接将压缩文件写入磁盘并解压?

我理解你是想直接获取一个 zip 文件,并将其解压到一个目录,而不需要先存储它。要做到这一点,你需要使用 这个链接 中的内容。

不过,这里有个问题,来自请求的响应实际上是不能随机访问的,而 zipfile 需要这种随机访问(它的第一步就是要找到文件的末尾,以确定文件的长度)。

为了解决这个问题,你需要将响应包装成一个类似文件的对象。我个人建议使用 tempfile.SpooledTemporaryFile,并设置一个最大大小。这样,如果文件比你预期的要大,你的代码就会自动切换到将内容写入磁盘。

import requests
import tempfile
import zipfile

KB = 1<<10
MB = 1<<20

url = '...' # Set url to the download link.

resp = requests.get(url, stream=True)
with tmp as tempfile.SpooledTemporaryFile(max_size=500*MB):
    for chunk in resp.iter_content(4*KB):
        tmp.write(chunk)
    archive = zipfile.ZipFile(tmp)
    archive.extractall(path)

使用 io.BytesIO 的相同代码:

resp = requests.get(url, stream=True)
tmp = io.BytesIO()
for chunk in resp.iter_content(4*KB):
    tmp.write(chunk)
archive = zipfile.ZipFile(tmp)
archive.extractall(path)

撰写回答