您可以实现一个类似文件的对象,该对象从FTP读取数据,而不是从本地文件读取数据。并将其传递给
ZipFile构造函数,而不是(本地)文件名。
一个简单的实现可以像:
from ftplib import FTPfrom ssl import SSLSocketclass 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) 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())如果
BadZipfile由于10 KB太小而无法包含整个清单而导致异常,则可以使用更大的块重试代码。



