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

35 投票
6 回答
91419 浏览
提问于 2025-04-16 13:14

我正在写一个脚本,用来通过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 个回答

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."

就可以下载所有文件了。

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 列表做聪明事情的程序,比如在图形界面中展示给用户,可能需要写很多特殊情况的代码,以应对奇怪或不常见的服务器。而且当遇到恶意文件名时,它们可能都会做一些愚蠢的事情。)

你能不能用 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 成员中查看详细信息。

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上是有效的。

撰写回答