Python-FTP download all files in directory
Solution 1:
I've managed to crack this, so now posting the relevant bit of code for future visitors:
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
This worked for me on Python 2.5, Windows XP.
Solution 2:
If this is just a problem you'd like to solve, I might suggest the wget
command:
cd c:\destination
wget --mirror --continue --no-host-directories --user=username --password=s3cr3t ftp://hostname/source/path/
The --continue
option could be very dangerous if files change on the server. If files are only ever added, then it is very friendly.
However, if this is a learning exercise for you and you'd like to make your program work, I think you should start by looking at this line:
for subdir, dirs, files in os.walk(directory):
directory
has been the remote source directory in most of your program, but the os.walk()
function cannot walk a remote directory. You need to iterate over the returned files yourself, using a callback supplied to the retrlines
function.
Take a look at the MLSD
or NLST
options instead of LIST
, they will probably be easier to parse. (Note that FTP doesn't actually specify how lists should look; it was always intended to be driven by a human at a console, or a specific filename transferred. So programs that do clever things with FTP listings like present them to the user in a GUI probably have to have huge piles of special case code, for odd or obscure servers. And they probably all do something stupid when faced with malicious file names.)
Can you use sftp
instead? sftp
does have a specification for how file listings are supposed to be parsed, doesn't transmit username/password in the clear, and doesn't have the giant annoyance of passive vs active connections -- it simply uses the single connection, which means it works across more firewalls than FTP does.
Edit: You need to pass a 'callable' object to the retrlines
function. A callable object is either an instance of a class that defined a __call__
method, or a function. While the function might be easier to describe, an instance of a class may be more useful. (You could use the instance to collect the filenames, but the function would have to write to a global variable. Bad.)
Here's one of the simplest callable object:
>>> class c:
... def __call__(self, *args):
... print(args)
...
>>> f = c()
>>> f('hello')
('hello',)
>>> f('hello', 'world')
('hello', 'world')
This creates a new class, c
, that defines an instance method __call__
. This just prints its arguments in a fairly stupid manner, but it shows how minimal we're talking. :)
If you wanted something smarter, it could do something like this:
class handle_lines:
def __init__(self):
self.lines = []
def __call__(self, *args):
self.lines << args[0]
Call iterlines
with an object of this class, then look in the object's lines
member for details.
Solution 3:
this code is a little bit of overkill I think.
(from the python example https://docs.python.org/2/library/ftplib.html) After ftp.login() and setting ftp.cwd() you can just use:
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."
to download all the files.
Solution 4:
A recursive solution (py 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)