Python-FTP下载目录中的所有文件

35

我正在编写一个脚本,通过FTP下载目录中的所有文件。到目前为止,我已经成功连接并获取了一个文件,但是我似乎无法批量工作(获取目录中的所有文件)。以下是我目前的代码:

from ftplib import FTP
import os, sys, os.path

def handleDownload(block):
    file.write(block)
    print ".",

ddir='C:\\Data\\test\\'
os.chdir(ddir)
ftp = FTP('test1/server/')

print 'Logging in.'
ftp.login('user1\\anon', 'pswrd20')
directory = '\\data\\test\\'

print 'Changing to ' + directory
ftp.cwd(directory)
ftp.retrlines('LIST')

print 'Accessing files'

for subdir, dirs, files in os.walk(directory):
    for file in files: 
        full_fname = os.path.join(root, fname);  
        print 'Opening local file ' 
        ftp.retrbinary('RETR C:\\Data\\test\\' + fname,
                       handleDownload,
                       open(full_fname, 'wb'));
        print 'Closing file ' + filename
        file.close();
ftp.close()

我敢打赌你可以看出来,当我运行它时它并没有做太多事情,所以如果有任何改进的建议将不胜感激。

6个回答

78

我已经成功破解了这个问题,现在发布相关的代码片段以供日后访问者参考:

filenames = ftp.nlst() # get filenames within the directory
print filenames

for filename in filenames:
    local_filename = os.path.join('C:\\test\\', filename)
    file = open(local_filename, 'wb')
    ftp.retrbinary('RETR '+ filename, file.write)

    file.close()

ftp.quit() # This is the “polite” way to close a connection

这个方法适用于我在Python 2.5、Windows XP上的使用。


3
建议使用 ftp.quit() 代替 ftp.close()。请参考此链接:https://docs.python.org/2/library/ftplib.html#ftplib.FTP.quit - Oran
ftp.nlst() 如何知道我想要哪个链接?这个答案似乎不完整。 - Soren
如果在filenames列表中有一个目录名,那么它将无法工作。 - Jhon Margalit

11

如果您只是想解决这个问题,我建议使用wget命令:

cd c:\destination
wget --mirror --continue --no-host-directories --user=username --password=s3cr3t ftp://hostname/source/path/
--continue选项如果服务器上的文件变化,可能非常危险。如果只有添加文件,那么这个选项就非常友好。

然而,如果这是你的练习并且想让程序正常工作,我认为你应该从这一行开始查看:

for subdir, dirs, files in os.walk(directory):

directory通常是你程序中的远程源目录,但os.walk()函数无法遍历远程目录。你需要使用提供给retrlines函数的回调来手动迭代返回的文件。

尝试使用MLSDNLST选项而不是LIST,它们可能更容易解析。(请注意FTP实际上并没有规定列表应该看起来如何;它一直旨在由控制台上的人类或特定的文件名驱动。因此,对FTP列表进行聪明处理(例如在GUI中向用户呈现)的程序可能必须具有大量的特例代码,用于奇怪或模糊的服务器。当面对恶意文件名时,它们可能都做了一些愚蠢的事情。)

你能否使用sftp代替?sftp确实有一个文件列表解析规范,不会明文传输用户名/密码,并且没有被动连接与主动连接巨大的烦恼——它仅使用单个连接,这意味着它可以跨越比FTP更多的防火墙工作。

编辑:你需要向retrlines函数传递一个“可调用”对象。可调用对象是定义了一个__call__方法的类的实例或者一个函数。虽然函数可能更容易描述,但类的实例可能更有用。(你可以使用这个实例来收集文件名,但函数必须写入一个全局变量。不好。)

这里是其中最简单的可调用对象之一:

>>> class c:
...  def __call__(self, *args):
...   print(args)
...
>>> f = c()
>>> f('hello')
('hello',)
>>> f('hello', 'world')
('hello', 'world')

这段代码创建了一个名为c的新类,它定义了一个实例方法__call__。该方法以相当愚蠢的方式打印其参数,但它说明了我们所讲述的最小限度。

如果您想要更智能的东西,可以像这样做:

class handle_lines:
  def __init__(self):
    self.lines = []
  def __call__(self, *args):
    self.lines << args[0]

使用此类的对象调用iterlines,然后查看对象的lines成员以获取详细信息。


@Sosti,我在帖子中提到的retrlines函数是一个链接,指向文档 :) - sarnold
非常感谢您的建议,它们听起来都很可靠!我忘了提到我在Windows XP上使用Python 2.5(如果有用的话)。如果我使用MLSD选项,'ftp.retrlines('MLSD')',那么代码是否适用于迭代,还是需要进行更多修改?(当然,这听起来有点傻,但我是新手,记得吗?:DD) - Sosti
@Sosti,你仍然需要修改你的代码:你不能使用os.walk()函数。我稍后会编辑我的答案,展示如何为retrlines创建回调对象。 - sarnold
我必须承认,我需要对此进行一些研究,并尝试编写一些代码。我希望问题可以通过调整一些代码行来解决,但显然问题更加根本性。我会尽力而为,然后会反馈任何结果。感谢所有的输入和建议! - Sosti

3
这段代码我认为有点过度设计了。
(来自Python示例https://docs.python.org/2/library/ftplib.html)在ftp.login()和设置ftp.cwd()之后,您只需使用以下代码即可:
os.chdir(ddir)
ls = ftp.nlst()
count = len(ls)
curr = 0
print "found {} files".format(count)
for fn in ls:
    curr += 1
    print 'Processing file {} ... {} of {} ...'.format(fn, curr, count)
    ftp.retrbinary('RETR ' + fn, open(fn, 'wb').write)

ftp.quit()
print "download complete."

下载所有文件。


往前看,是否可以在写出之前检查哈希值?我尝试下载的FTP服务器上有file1.gzfile1.gz.md5等文件,远程端有超过1200个文件,因此不可能下载所有文件并进行检查(内存问题)。 - jimmymcheung

1
一种递归解决方案(使用Python 2.7):

import os, ftplib, shutil, operator

def cloneFTP((addr, user, passw), remote, local):
    try:
        ftp = ftplib.FTP(addr)
        ftp.login(user, passw)
        ftp.cwd(remote)
    except: 
        try: ftp.quit()
        except: pass
        print 'Invalid input ftp data!'
        return False
    try: shutil.rmtree(local)
    except: pass
    try: os.makedirs(local)
    except: pass
    dirs = []
    for filename in ftp.nlst():
        try:
            ftp.size(filename)
            ftp.retrbinary('RETR '+ filename, open(os.path.join(local, filename), 'wb').write)
        except:
            dirs.append(filename)
    ftp.quit()
    res = map(lambda d: cloneFTP((addr, user, passw), os.path.join(remote, d), os.path.join(local, d)), dirs)
    return reduce(operator.iand, res, True)

0

我是初学者,所以我的代码并不高效,但我已经编写并测试了它,它能够正常工作。这是我从FTP站点下载文件和文件夹的方法,但只限于有限的文件结构深度。

try:
   a = input("Enter hostname : ")
   b = input("Enter username : ")
   c = input("Enter password : ")
   from ftplib import FTP
   import os
   os.makedirs("C:\\Users\\PREM\\Desktop\\pyftp download\\ftp")
   os.chdir("C:\\Users\\PREM\\Desktop\\pyftp download\\ftp")
   ftp = FTP(host = a, user= b, passwd = c)
   D = ftp.nlst()
   for d in D:
      l = len(d)
      char = False
      for i in range(0,l):
          char = char or d[i]=="."
      if not char:
         ftp.cwd("..")
         ftp.cwd("..")
         E = ftp.nlst("%s"%(d))
         ftp.cwd("%s"%(d))
         try:
             os.makedirs("C:\\Users\\PREM\\Desktop\\pyftp download\\ftp\\%s"%(d))
         except:
             print("you can debug if you try some more")
         finally:
             os.chdir("C:\\Users\\PREM\\Desktop\\pyftp download\\ftp\\%s"%(d))
             for e in E:
                l1 = len(e)
                char1 = False
                for i in range(0,l1):
                   char1 = char1 or e[i]=="."
                if not char1:
                   ftp.cwd("..")
                   ftp.cwd("..")
                   F = ftp.nlst("%s/%s"%(d,e))
                   ftp.cwd("%s/%s"%(d,e))
                   try:
                       os.makedirs("C:\\Users\\PREM\\Desktop\\pyftp download\\ftp\\%s\\%s"%(d,e))
                   except:
                       print("you can debug if you try some more")
                   finally:
                       os.chdir("C:\\Users\\PREM\\Desktop\\pyftp download\\ftp\\%s\\%s"%(d,e))
                       for f in F:
                           if "." in f[2:]:
                               with open(f,'wb') as filef:
                                   ftp.retrbinary('RETR %s' %(f), filef.write)
                           elif not "." in f:
                               try:
                                  os.makedirs("C:\\Users\\PREM\\Desktop\\pyftp download\\ftp\\%s\\%s\\%s"%(d,e,f))
                               except:
                                  print("you can debug if you try some more")
                elif "." in e[2:]:
                   os.chdir("C:\\Users\\PREM\\Desktop\\pyftp download\\ftp\\%s"%(d))
                   ftp.cwd("..")
                   ftp.cwd("..")
                   ftp.cwd("..")
                   ftp.cwd("%s"%(d))
                   with open(e,'wb') as filee:
                      ftp.retrbinary('RETR %s' %(e), filee.write)
      elif "." in d[2:]:
          ftp.cwd("..")
          ftp.cwd("..")
          os.chdir("C:\\Users\\PREM\\Desktop\\pyftp download\\ftp")
          with open(d,'wb') as filed:
             ftp.retrbinary('RETR %s'%(d), filed.write)
   ftp.close()
   print("Your files has been successfully downloaded and saved. Bye")
except:
    print("try again you can do it")
finally:
    print("code ran")

你能解释一下你的代码是如何/为什么工作的吗?这将使原帖和其他人能够理解并在其他地方应用你的方法(如果适用)。仅有代码的答案是不被鼓励的,并且可能会被删除。- 审核期间 - Wai Ha Lee

-4

不必使用Python库来FTP下载目录,我们可以从Python程序中调用一个DOS脚本。在DOS脚本中,我们将使用原生的FTP协议,通过mget *.*命令下载文件夹中的所有文件。

fetch.bat
ftp -s:fetch.txt

fetch.txt
open <ipaddress>
<userid>
<password>
bin (set the mnode to binary)
cd </desired directory>
mget *.*
bye

fetch.py
import os
os.system("fetch.bat")

1
它也特定于Windows(DOS)。 - Carl
有时候,它是有帮助的。 - JOHN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接