使用 Python ftplib 下载时忽略丢失的文件

Ignore missing file while downloading with Python ftplib

我正在尝试从 FTP 服务器下载某个文件(名为 010010-99999-year.gz)。同样的文件,但不同年份位于不同的 FTP 目录中。例如:

ftp://ftp.ncdc.noaa.gov/pub/data/noaa/isd-lite/2000/010010-99999-1973.gz ftp://ftp.ncdc.noaa.gov/pub/data/noaa/isd-lite/2001/010010-99999-1974.gz 等等。图片说明了其中一个目录:

该文件未位于所有目录中(即所有年份)。在这种情况下,我希望脚本忽略丢失的文件,打印 "not available",并继续下一个目录(即明年)。 我可以通过首先在当前 FTP 目录中生成文件列表然后检查我的文件是否在该列表中来使用 NLST 列表来执行此操作,但这很慢,而 NOAA(拥有服务器的组织)确实如此不像文件列表 (source)。因此我想出了这个代码:

def FtpDownloader2(url="ftp.ncdc.noaa.gov"):
    ftp=FTP(url)        
    ftp.login()
    for year in range(1901,2015):
        ftp.cwd("/pub/data/noaa/isd-lite")
        ftp.cwd(str(year))
        fullStationId="010010-99999-%s.gz" % year
        try:              
            file=open(fullStationId,"wb")
            ftp.retrbinary('RETR %s' % fullStationId, file.write)
            print("File is available")
            file.close()
        except: 
            print("File not available")
    ftp.close()

这会正确下载现有文件(1973-2014 年),但也会生成 1901-1972 年的空文件。该文件不在 1901-1972 的 FTP 中。 我在使用 try 和 except 时做错了什么,还是其他问题?

我认为问题出在您的 try:except 块中,您在检查文件是否存在之前为新文件保持文件处理程序打开:

try:              
    file=open(fullStationId,"wb")
    ftp.retrbinary('RETR %s' % fullStationId, file.write)
    print("File is available")
    file.close()
except: 
    print("File not available")

相反,在 except 块中添加一个额外的语句来关闭文件处理程序,并在文件为空时添加另一个语句来删除文件。

另一种可能性是仅当文件存在且在服务器上具有非零大小时才打开文件以在本地写入,使用 ftp.size

我把你的代码做了一点修改:

from ftplib import FTP, error_perm
import os

def FtpDownloader2(url="ftp.ncdc.noaa.gov"):
    ftp = FTP(url)
    ftp.login()
    for year in range(1901, 2015):
        remote_file = '/pub/data/noaa/isd-lite/{0}/010010-99999-{0}.gz'.format(year)
        local_file = os.path.basename(remote_file)
        try:
            with open(local_file, "wb") as file_handle:
                ftp.retrbinary('RETR %s' % remote_file, file_handle.write)
            print('OK', local_file)
        except error_perm:
            print('ERR', local_file)
            os.unlink(local_file)
    ftp.close()

备注

  • 一个人能做的最危险的操作就是拥有一个没有特定例外的except子句class。这种类型的构造将忽略所有错误,使其难以排除故障。为了解决这个问题,我添加了特定的例外 error_perm
  • 一旦发生异常,我绝对确定本地文件已关闭,因为with语句保证
  • 如果发生 error_perm 异常,我删除了本地文件,这表明该文件在服务器上不可用
  • 我删除了更改目录的代码:每年,您 cwd 两次,这会减慢进程
  • range(1901, 2015)不会包含2015年,如果要的话要指定range(1901, 2016)
  • 我改进了打印语句以包含文件名,从而更容易跟踪哪些文件可用,哪些文件不可用

更新

此更新回答了您关于不创建空本地文件(然后必须删除它们)的问题。有几种不同的方法:

  1. 下载前查询远程文件是否存在。只有在远程存在时才创建本地文件。这种方法的问题是查询远程文件比 creating/deleting 本地文件需要更长的时间。
  2. 创建一个字符串缓冲区 (StringIO),下载到该缓冲区。仅当该字符串缓冲区不为空时才创建本地文件。这种方法的问题是您将相同的数据写入两次:一次写入字符串缓冲区,一次从字符串缓冲区写入文件。