Игнорировать отсутствующий файл при загрузке с помощью Python ftplib

Я пытаюсь загрузить определенный файл (с именем 010010-99999-year.gz) с FTP-сервера. Этот же файл, но за разные годы находится в разных каталогах 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 и так далее. На картинке показан один из каталогов: введите здесь описание изображения

Файл находится не во всех директориях (т.е. всех годах). В таком случае я хочу, чтобы скрипт игнорировал отсутствующие файлы, печатал «недоступно» и переходил к следующему каталогу (т.е. в следующем году). Я мог бы сделать это, используя список NLST, сначала создав список файлов в текущем каталоге FTP, а затем проверив, есть ли мой файл в этом списке, но это медленно, и NOAA (организация, владеющая сервером) не нравится список файлов (источник). Поэтому я придумал этот код:

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 гг. Я делаю что-то неправильно при использовании try и кроме, или это какая-то другая проблема?


person multigoodverse    schedule 07.02.2015    source источник
comment
возможный дубликат Проверьте, существует ли файл с использованием Python   -  person Nir Alfasi    schedule 07.02.2015
comment
@alfasin возможный повторяющийся вопрос касается проверки того, существует ли файл локально. Мой вопрос о продолжении цикла, если файл не существует на FTP-сервере.   -  person multigoodverse    schedule 07.02.2015
comment
Извините, это моя ошибка. Вы должны проверить размер файла на FTP-сервере, если размер › 0 существует. Пример: example-code.com/python/ftp_fileExists.asp   -  person Nir Alfasi    schedule 07.02.2015


Ответы (2)


Я взял ваш код и немного изменил его:

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 без определенного класса исключений. Этот тип конструкции игнорирует все ошибки, что затрудняет устранение неполадок. Чтобы исправить это, я добавил конкретное исключение error_perm
  • Как только произошло исключение, я абсолютно точно знаю, что локальный файл закрыт, потому что оператор with гарантирует, что
  • Я удалил локальный файл, если произошло исключение error_perm, признак того, что файл недоступен с сервера
  • Я удалил код для смены каталогов: за каждый год вы cwd дважды, что замедляет процесс
  • range(1901, 2015) не будет включать 2015 год. Если вы хотите, вы должны указать range(1901, 2016)
  • Я улучшил операторы печати, включив в них имена файлов, что упростило отслеживание того, какие из них доступны, а какие нет.

Обновлять

Это обновление отвечает на ваш вопрос о том, чтобы не создавать пустой локальный файл (а затем удалять его). Есть несколько разных способов:

  1. Запросите существование удаленного файла перед загрузкой. Создавайте локальный файл только тогда, когда удаленный существует. Проблема с этим подходом заключается в том, что запрос к удаленному файлу занимает больше времени, чем создание/удаление локального файла.
  2. Создайте строковый буфер (StringIO), загрузите в этот буфер. Создавайте локальный файл только тогда, когда этот строковый буфер не пуст. Проблема с этим подходом заключается в том, что вы записываете одни и те же данные дважды: один раз в строковый буфер и один раз из строкового буфера в файл.
person Hai Vu    schedule 07.02.2015
comment
Большой! Тоже отличные заметки! Как вы думаете, можно ли вообще не генерировать пустые файлы локально? - person multigoodverse; 07.02.2015

Я думаю, что проблема в вашем блоке try: кроме блока, где вы держите обработчик файла открытым для нового файла, прежде чем проверять, существует ли файл или нет:

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

Вместо этого добавьте в блок исключений дополнительный оператор, чтобы закрыть обработчик файла, и еще один оператор, чтобы удалить файл, если он пуст.

Другая возможность — открыть файл для записи локально, только если файл существует и имеет ненулевой размер на сервере, используя ftp.size.

person Anshul Goyal    schedule 07.02.2015
comment
+1 за это, потому что это работает. Лучше вообще не генерировать пустые файлы. Например: попробуйте: ftp.retrbinary('RETR %s' % fullStationId,open(fullStationId,wb).write.close()) Но это тоже не работает. - person multigoodverse; 07.02.2015