Использование R для доступа к FTP-серверу и загрузки файлов приводит к статусу "530 Not logged in"

Что я пытаюсь сделать

Я пытаюсь загрузить несколько файлов данных о погоде с FTP-сервера Национального центра климатических данных США, но у меня возникают проблемы с сообщением об ошибке после успешного завершения загрузки нескольких файлов.

После успешной загрузки двух комбинаций станция / год я получаю сообщение об ошибке "530 Not logged in". Я попытался начать с оскорбительного года и бежать оттуда и получить примерно те же результаты. Он загружает данные за год или два, а затем останавливается с тем же сообщением об ошибке, что не вошел в систему.

Рабочий пример

Ниже приведен рабочий пример (или нет) с усеченным выводом и вставленным ниже.

options(timeout = 300)
ftp <- "ftp://ftp.ncdc.noaa.gov/pub/data/gsod/"
td <- tempdir()
station <– c("983240-99999", "983250-99999", "983270-99999", "983280-99999", "984260-41231", "984290-99999", "984300-99999", "984320-99999", "984330-99999")
years <- 1960:2016

for (i in years) {
  remote_file_list <- RCurl::getURL(
    paste0(ftp, "/", i, "/"), ftp.use.epsv = FALSE, ftplistonly = TRUE,
    crlf = TRUE, ssl.verifypeer = FALSE)
  remote_file_list <- strsplit(remote_file_list, "\r*\n")[[1]]

  file_list <- paste0(station, "-", i, ".op.gz")

  file_list <- file_list[file_list %in% remote_file_list]

  file_list <- paste0(ftp, i, "/", file_list)

  Map(function(ftp, dest) utils::download.file(url = ftp,
                                               destfile = dest, mode = "wb"),
      file_list, file.path(td, basename(file_list)))
}


trying URL 'ftp://ftp.ncdc.noaa.gov/pub/data/gsod/1960/983250-99999-1960.op.gz'
Content type 'unknown' length 7135 bytes
==================================================
downloaded 7135 bytes

...

trying URL 'ftp://ftp.ncdc.noaa.gov/pub/data/gsod/1961/984290-99999-1961.op.gz'
Content type 'unknown' length 7649 bytes
==================================================
downloaded 7649 bytes

trying URL 'ftp://ftp.ncdc.noaa.gov/pub/data/gsod/1962/983250-99999-1962.op.gz'
downloaded 0 bytes

 Error in utils::download.file(url = ftp, destfile = dest, mode = "wb") : 
 cannot download all files In addition: Warning message: 
 In utils::download.file(url = ftp, destfile = dest, mode = "wb") : 
 URL ftp://ftp.ncdc.noaa.gov/pub/data/gsod/1962/983250-99999-1962.op.gz':      
 status was '530 Not logged in'

Различные методы и идеи, которые я пробовал, но пока не достиг успеха

До сих пор я пытался замедлить запросы с помощью Sys.sleep в цикле for и любом другом способе медленного извлечения файлов путем открытия, закрытия соединений и т. д. Это озадачивает, потому что: i) он работает немного, затем останавливается и не связан с конкретной комбинацией год / станция как таковой; II) я могу использовать почти тот же код и загружать гораздо большие годовые файлы глобальных данных о погоде без каких-либо ошибок в течение длительного периода времени, как это; и iii) он не всегда останавливается после 1961 года и до 1962 года, иногда он останавливается в 1960 году, когда он начинается в 1961 году, и т. д., но, похоже, он последовательно между годами, а не в пределах того, что я нашел.

Логин является анонимным, но вы можете использовать userpwd "ftp:your@email.address". До сих пор мне не удавалось использовать этот метод, чтобы убедиться, что я вошел в систему для загрузки файлов станции.

1 ответ

Решение

Я думаю, что вам понадобится более оборонительная стратегия при работе с этим FTP-сервером:

library(curl)  # ++gd > RCurl
library(purrr) # consistent "data first" functional & piping idioms FTW
library(dplyr) # progress bar

# We'll use this to fill in the years
ftp_base <- "ftp://ftp.ncdc.noaa.gov/pub/data/gsod/%s/"

dir_list_handle <- new_handle(ftp_use_epsv=FALSE, dirlistonly=TRUE, crlf=TRUE,
                              ssl_verifypeer=FALSE, ftp_response_timeout=30)

# Since you, yourself, noted the server was perhaps behaving strangely or under load
# it's prbly a much better idea (and a practice of good netizenship) to cache the
# results somewhere predictable rather than a temporary, ephemeral directory
cache_dir <- "./gsod_cache"
dir.create(cache_dir, showWarnings=FALSE)

# Given the sporadic efficacy of server connection, we'll wrap our calls
# in safe & retry functions. Change this variable if you want to have it retry
# more times.
MAX_RETRIES <- 6

# Wrapping the memory fetcher (for dir listings)
s_curl_fetch_memory <- safely(curl_fetch_memory)
retry_cfm <- function(url, handle) {

  i <- 0
  repeat {
    i <- i + 1
    res <- s_curl_fetch_memory(url, handle=handle)
    if (!is.null(res$result)) return(res$result)
    if (i==MAX_RETRIES) { stop("Too many retries...server may be under load") }
  }

}

# Wrapping the disk writer (for the actual files)
# Note the use of the cache dir. It won't waste your bandwidth or the
# server's bandwidth or CPU if the file has already been retrieved.
s_curl_fetch_disk <- safely(curl_fetch_disk)
retry_cfd <- function(url, path) {

  # you should prbly be a bit more thorough than `basename` since
  # i think there are issues with the 1971 and 1972 filenames. 
  # Gotta leave some work up to the OP
  cache_file <- sprintf("%s/%s", cache_dir, basename(url))
  if (file.exists(cache_file)) return()

  i <- 0
  repeat {
    i <- i + 1
    if (i==6) { stop("Too many retries...server may be under load") }
    res <- s_curl_fetch_disk(url, cache_file)
    if (!is.null(res$result)) return()
  }

}

# the stations and years
station <- c("983240-99999", "983250-99999", "983270-99999", "983280-99999",
             "984260-41231", "984290-99999", "984300-99999", "984320-99999",
             "984330-99999")
years <- 1960:2016

# progress indicators are like bowties: cool
pb <- progress_estimated(length(years))
walk(years, function(yr) {

  # the year we're working on
  year_url <- sprintf(ftp_base, yr)

  # fetch the directory listing
  tmp <- retry_cfm(year_url, handle=dir_list_handle)
  con <- rawConnection(tmp$content)
  fils <- readLines(con)
  close(con)

  # sift out only the target stations
  map(station, ~grep(., fils, value=TRUE)) %>%
    keep(~length(.)>0) %>%
    flatten_chr() -> fils

  # grab the stations files
  walk(paste(year_url, fils, sep=""), retry_cfd)

  # tick off progress
  pb$tick()$print()

})

Вы также можете установить curl_interrupt в TRUE в curl обрабатывать, если вы хотите иметь возможность остановить / Esc/ прервать загрузки.

Другие вопросы по тегам