在不下载整个压缩文件的情况下,获取FTP服务器上压缩文件内的文件名

5

我有很多zip文件存储在远程FTP服务器上,它们的大小高达20TB。我只需要这些zip文件内部的文件名,以便将它们插入到我的Python脚本中。

有没有办法仅获取文件名而不实际下载文件并在本地机器上提取它们?如果有,有人可以指导我使用正确的库/包吗?

1个回答

5
您可以实现一个类似于文件的对象,从FTP读取数据,而不是从本地文件读取。然后将其传递给ZipFile构造函数,而不是传递本地文件名。
一个简单的实现可以是:
from ftplib import FTP
from ssl import SSLSocket

class FtpFile:

    def __init__(self, ftp, name):
        self.ftp = ftp
        self.name = name
        self.size = ftp.size(name)
        self.pos = 0
    
    def seek(self, offset, whence):
        if whence == 0:
            self.pos = offset
        if whence == 1:
            self.pos += offset
        if whence == 2:
            self.pos = self.size + offset

    def tell(self):
        return self.pos

    def read(self, size = None):
        if size == None:
            size = self.size - self.pos
        data = B""

        # Based on FTP.retrbinary 
        # (but allows stopping after certain number of bytes read)
        # An alternative implementation is at
        # https://stackoverflow.com/q/58819210/850848#58819362
        ftp.voidcmd('TYPE I')
        cmd = "RETR {}".format(self.name)
        conn = ftp.transfercmd(cmd, self.pos)
        try:
            while len(data) < size:
                buf = conn.recv(min(size - len(data), 8192))
                if not buf:
                    break
                data += buf
            # shutdown ssl layer (can be removed if not using TLS/SSL)
            if SSLSocket is not None and isinstance(conn, SSLSocket):
                conn.unwrap()
        finally:
            conn.close()
        try:
            ftp.voidresp()
        except:
            pass
        self.pos += len(data)
        return data

然后你可以像这样使用它:

ftp = FTP(host, user, passwd)
ftp.cwd(path)

ftpfile = FtpFile(ftp, "archive.zip")
zip = zipfile.ZipFile(ftpfile)
print(zip.namelist())

上面的实现相当轻松但效率低下。它启动了许多(至少三个)下载小数据块以检索包含的文件列表。可以通过读取和缓存较大数据块来进行优化。但是,这应该能给你一个想法。

特别是因为您只需要阅读列表,所以可以利用这一点。列表位于ZIP存档的末尾。因此,您只需在开始时下载最后(约)10 KB的数据即可。然后,您将能够从缓存中完成所有read调用。


了解这一点后,你实际上可以进行一个小技巧。由于列表在归档末尾,你实际上只需下载归档末尾即可。虽然下载的ZIP将会是损坏的,但仍然可以列出。这样,你就不需要FtpFile类了。你甚至可以将列表下载到内存StringIO)。

zipstring = StringIO()
name = "archive.zip"
size = ftp.size(name)
ftp.retrbinary("RETR " + name, zipstring.write, rest = size - 10*2024)

zip = zipfile.ZipFile(zipstring)

print(zip.namelist())

如果您因为10 KB太小而收到BadZipfile异常,无法包含整个列表,则可以使用更大的块重新尝试该代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接