9

我已经搜索了几天,但还没有找到答案。

我试图从 FTP 下载视频文件,我的脚本检查服务器,将 nlist() 与从文本文件解析的已下载文件列表进行比较,然后创建一个新的文件列表以获取并迭代它下载每个文件,断开与服务器的连接并重新连接下一个文件(我认为服务器超时可能是一个问题,所以我在每次文件下载后退出()连接)。

这适用于前几个文件,但是一旦我遇到一个耗时超过 5 分钟的文件,fitlib 就会在传输结束时挂起(我可以在资源管理器中看到文件大小正确,因此下载已经完成,但是它似乎没有收到消息并继续下一个文件)

任何帮助将不胜感激,我的代码如下:

newPath = "Z:\\pathto\\downloads\\"

for f in getFiles:
    print("Getting " + f)

for f in getFiles:

    fil = f.rstrip()
    ext = os.path.splitext(fil)[1]
    if ext in validExtensions:
        print("Downloading new file: " + fil)
        downloadFile(fil, newPath)

这是下载.py

from ftplib import FTP
def downloadFile(filename, folder):
    myhost = 'host'
    myuser = 'user'
    passw = 'pass'
    #login
    ftp = FTP(myhost,myuser,passw)
    localfile = open(folder + filename, 'wb')
    ftp.retrbinary("RETR " + filename, localfile.write, 1024)
    print("Downloaded " + filename)
    localfile.close()
    ftp.quit()
4

2 回答 2

30

如果没有更多信息,我实际上无法调试您的问题,所以我只能建议最一般的答案。这对你来说可能不是必需的,但对任何人来说可能就足够了。

retrbinary将阻塞直到整个文件完成。如果超过 5 分钟,则在整个 5 分钟内不会通过控制通道发送任何内容。您的客户端正在超时控制通道,或者服务器正在超时。因此,当您尝试挂断 时ftp.quit(),它将永远挂起或引发异常。

您可以使用构造timeout函数上的参数来控制您这边的超时。FTP一些服务器支持IDLE允许您设置服务器端超时的命令。但是,即使适当的超时是可行的,您如何首先选择合适的超时?

您真正想要做的是防止控制套接字在数据套接字上发生传输时超时。但是怎么做?例如,如果你ftp.voidcmd('NOOP')经常在你的回调函数中,这将足以保持连接活跃......但它也会迫使你阻塞直到服务器响应NOOP,许多服务器在数据传输之前不会这样做已完成,这意味着您最终将永远阻塞(或直到另一个超时)并且无法获取您的数据。

处理两个套接字而不阻塞另一个套接字的标准技术是多路复用器select.select或线程。您可以在此处执行此操作,但您将不得不放弃使用简单retrbinary接口,而是使用transfercmd显式获取数据套接字。

例如:

def downloadFile(…):
    ftp = FTP(…)
    sock = ftp.transfercmd('RETR ' + filename)
    def background():
        f = open(…)
        while True:
            block = sock.recv(1024*1024)
            if not block:
                break
            f.write(block)
        sock.close()
    t = threading.Thread(target=background)
    t.start()
    while t.is_alive():
        t.join(60)
        ftp.voidcmd('NOOP')

另一种解决方案是一次读取 20MB,然后调用ftp.abort(),并使用rest参数恢复每个新的传输,retrbinary直到到达文件末尾。但是,ABOR可能会永远挂起,就像那样NOOP,所以这并不能保证任何事情——更不用说服务器不必响应它。

可以做的只是关闭整个连接(不是quit,而是close)。这对服务器来说不是很好,并且可能会导致重新发送一些浪费的数据,并且如果您过快地终止套接字,还可能会阻止 TCP 正常加速到全速。但它应该工作。

请参阅此答案- 并注意它需要对您的特定损坏服务器进行一些测试,以确定哪些变体(如果有)正确有效地工作。

于 2013-10-30T21:00:06.073 回答
1

基于 abarnet 的解决方案(最后仍然挂起)我写了这个终于可以工作了:-)

import ftplib
from tempfile import SpooledTemporaryFile

MEGABYTE = 1024 * 1024

def download(ftp_host, ftp_user, ftp_pass, ftp_path, filename):
    ftp = ftplib.FTP(ftp_host, ftp_user, ftp_pass, timeout=3600) # timeout: 1-hour
    ftp.cwd(ftp_path)

    filesize = ftp.size(filename) / MEGABYTE
    print(f"Downloading: {filename}   SIZE: {filesize:.1f} MB")

    with SpooledTemporaryFile(max_size=MEGABYTE, mode="w+b") as ff:
        sock = ftp.transfercmd('RETR ' + filename)
        while True:
            buff = sock.recv(MEGABYTE)
            if not buff: break
            ff.write(buff)
        sock.close()
        ff.rollover()  # force saving to HDD of the final chunk!!
        ff.seek(0)     # prepare for data reading
        print("Reading the buffer...")
        # alldata = ff.read()
        # upload_file_to_adls(filename, alldata, account_name, account_key, container, adls_path)
    ftp.quit()
于 2021-11-19T15:06:33.270 回答