Python:成功下载文件后,使用ftplib永久挂起文件

时间:2018-04-23 08:01:02

标签: python download ftp hang ftplib

我一直在尝试解决从ftp / ftps下载文件的问题。文件下载成功,但文件下载完成后不执行任何操作。没有发生错误,可以提供有关该问题的更多信息。 我尝试在stackoverflow上搜索这个,并发现这个link谈论类似的问题陈述,看起来我面临类似的问题,虽然我不确定。在解决问题方面需要更多帮助。

我尝试将FTP连接超时设置为60分钟,但帮助较少。 在此之前,我使用了ftplib的retrbinary(),但同样的问题出现在那里。我试过传递不同的块大小和窗口大小但是这个问题也是可以重现的。

我正在尝试从AWS EMR群集下载大小约为3GB的文件。示例代码如下所示。

    def download_ftp(self, ip, port, user_name, password, file_name, target_path):
    try:
        os.chdir(target_path)
        ftp = FTP(host=ip)
        ftp.connect(port=int(port), timeout=3000)
        ftp.login(user=user_name, passwd=password)

        if ftp.nlst(file_name) != []:
            dir = os.path.split(file_name)
            ftp.cwd(dir[0])
            for filename in ftp.nlst(file_name):
                sock = ftp.transfercmd('RETR ' + filename)

                def background():
                    fhandle = open(filename, 'wb')
                    while True:
                        block = sock.recv(1024 * 1024)
                        if not block:
                            break
                        fhandle.write(block)
                    sock.close()

                t = threading.Thread(target=background)
                t.start()
                while t.is_alive():
                    t.join(60)
                    ftp.voidcmd('NOOP')
                logger.info("File " + filename + " fetched successfully")
            return True
        else:
            logger.error("File " + file_name + " is not present in FTP")

    except Exception, e:
        logger.error(e)
        raise

上述链接中建议的另一个选项是在下载文件的小块后关闭连接,然后重新启动连接。有人可以建议如何实现这一点,不确定如何从关闭连接之前上次停止文件下载的同一点恢复下载。这种方法是否可以完全证明下载整个文件。

我对FTP服务器级超时设置了解不多,所以不知道需要改变什么以及如何更改。我基本上想要编写一个通用的FTP下载器,它可以帮助从FTP / FTPS下载文件。

当我使用ftplib的retrbinary()方法并将调试级别设置为2时。

ftp.set_debuglevel(2)
ftp.retrbinary('RETR ' + filename, fhandle.write)

以下日志正在打印。

  

cmd 'TYPE I'    put 'TYPE I \ r \ n'    get '200 Type设置为I. \ r \ n'    resp '200类型设置为I.'    cmd 'PASV'    put 'PASV \ r \ n'    get '227进入被动模式(64,27,160,28,133,251)。\ r \ n'    resp '227进入被动模式(64,27,160,28,133,251)。    cmd 'RETR FFFT_BRA_PM_R_201711.txt'    put 'RETR FFFT_BRA_PM_R_201711.txt \ r \ n'    get '150为FFFT_BRA_PM_R_201711.txt打开BINARY模式数据连接。\ r \ n'    resp '150打开FFFT_BRA_PM_R_201711.txt的BINARY模式数据连接。'

1 个答案:

答案 0 :(得分:1)

在做任何事情之前,请注意您的连接存在一些问题,并且诊断并解决此问题远比解决它更好。但有时候,你只需处理一个损坏的服务器,甚至发送Keepalive也无济于事。那么,你能做什么?

诀窍是一次下载一个块,然后中止下载 - 或者,如果服务器无法处理中止,请关闭并重新打开连接。

请注意,我正在使用ftp://speedtest.tele2.net/5MB.zip测试下面的所有内容,希望这不会导致一百万人开始锤击他们的服务器。当然,您需要使用实际的服务器进行测试。

测试REST

整个解决方案当然依赖于服务器能够恢复传输,这并不是所有服务器都可以做到的 - 尤其是当您处理严重损坏的事情时。所以我们需要测试一下。请注意,此测试将非常慢,并且在服务器上非常繁重,因此请勿使用3GB文件进行测试;找到更小的东西。此外,如果你可以在那里放一些可读的东西,它将有助于调试,因为你可能会在十六进制编辑器中比较文件。

def downit():
    with open('5MB.zip', 'wb') as f:
        while True:
            ftp = FTP(host='speedtest.tele2.net', user='anonymous', passwd='test@example.com')
            pos = f.tell()
            print(pos)
            ftp.sendcmd('TYPE I')
            sock = ftp.transfercmd('RETR 5MB.zip', rest=pos)
            buf = sock.recv(1024 * 1024)
            if not buf:
                return
            f.write(buf)

你可能一次不会得到1MB,而是8KB以下的东西。我们假设你看到了1448,然后是2896,4344等等。

  • 如果您从REST获得例外,服务器无法处理恢复 - 放弃,您就会被冻结。
  • 如果文件超过实际文件大小,请点击^ C,然后在十六进制编辑器中进行检查。
    • 如果你看到相同的1448字节或其他(你看到它打印出来的数量)一遍又一遍,那么你就会被冲洗。
    • 如果你有正确的数据,但每个1448字节的块之间有额外的字节,那实际上是可以修复的。如果你碰到这个并且无法弄清楚如何使用f.seek修复它,我可以解释一下 - 但你可能不会遇到它。

测试ABRT

我们可以做的一件事是尝试中止下载,重新连接。

def downit():
    with open('5MB.zip', 'wb') as f:
        ftp = FTP(host='speedtest.tele2.net', user='anonymous', passwd='test@example.com')
        while True:
            pos = f.tell()
            print(pos)
            ftp.sendcmd('TYPE I')
            sock = ftp.transfercmd('RETR 5MB.zip', rest=pos)
            buf = sock.recv(1024 * 1024)
            if not buf:
                return
            f.write(buf)
            sock.close()
            ftp.abort()

您将要尝试多种变体:

  • sock.close
  • ftp.abort
  • sock.close之后的ftp.abort
  • ftp.abort之后的sock.close
  • 上述所有四个重复TYPE I移至循环之前而不是每次。

有些人会提出异常。其他人似乎永远都会挂起。如果对所有8个人都这样,我们需要放弃堕胎。但如果它们中的任何一个有效,那就太好了!

下载完整的块

另一种加快速度的方法是在中止或重新连接之前一次下载1MB(或更多)。只需替换此代码:

buf = sock.recv(1024 * 1024)
if buf:
    f.write(buf)

用这个:

chunklen = 1024 * 1024
while chunklen:
    print('   ', f.tell())
    buf = sock.recv(chunklen)
    if not buf:
        break
    f.write(buf)
    chunklen -= len(buf)

现在,每次传输不是读取1442或8192字节,而是每次传输最多读取1MB。试着把它推得更远。

与Keepalive结合

如果你的下载量达到了10MB,并且你的问题中的keepalive代码达到了512MB,但只有3GB还不够 - 你可以将两者结合起来。使用keepalive一次读取512MB,然后中止或重新连接并读取下一个512MB,直到完成。