最近在做一个稽核任务,需要FTP登录服务器下载文件到本地,参考了不少例子,功能都太单一,很少能直接拿来使用,于是自己写了一个。

一 . 几个模块

from ftplib import FTP
ftp = FTP() #设置变量
ftp.set_debuglevel(2) #打开调试级别2 显示详细信息
ftp.connect("IP", "port") #连接ftp, IP和端口
ftp.log("user", "password") #连接的用户名、密码
ftp.cwd(pathname) #设置FTP当前操作的路径
ftp.dir() #显示目录下的文件信息
ftp.nlst() #获取目录下的文件
ftp.mkd(pathname) #新建远程目录
ftp.pwd() #返回当前所在位置
ftp.rmd(dirname) #删除远程目录
ftp.delete(filename) #删除远程文件
ftp.rename(from_name, to_name) #修改文件名
ftp.storbinaly("STOR filename.txt", file_handel, bufsize) # 上传目标文件
ftp.retrbinaly("RETR filename.txt", file_handel, bufsize) # 上传FTP文件

二. 功能实现:

1. 下载整个目录包括子目录

def DownLoadFileTree(LocalDir, RemoteDir, ftp, IsRecursively):

入参:LocalDir 本地目录,可以是当前路径

RemoteDir: 远端目录 ,必须是服务器上存在的且有权限访问的目录

ftp : FTP() 创建的类对象

IsRecursively:是否递归即是否下载所有文件包含子目录 True或者False

2. 指定文件名下载

def DownLoadRUledFile(LocalDir, RemoteDir, filename, ftp):

入参:LocalDir 本地目录,可以是当前路径

RemoteDir: 远端目录 ,必须是服务器上存在的且有权限访问的目录

ftp : FTP() 创建的类对象

filename:远端文件名

3.实际负责下载功能的函数

def ftp_download(LocalFile, RemoteFile, bufsize, ftp):

入参:LocalFile:本地写入的文件名,目录在上一个函数已经创建

RemoteFile: 远端文件名, 传入需要下载的文件名即可 此时已经切换到远端目标目录

ftp : FTP() 创建的类对象

bufsize:远端文件的大小

4. 简单的检查函数

def IsDownloadCompletely(RemoteFile, LocalFile, remote_size):

入参:LocalFile:下载完成后本地存在此文件

LocalFile: 远端文件名, 传入远端文件名即可

remote_size:远端文件的大小 用于对比是否一致

三. 代码

可以直接使用,只用修改ftp相关信息和目录,文件名即可,测试通过

说明:在检查是否是目录的时候使用了分解字符函数 split:

dir_list = []
''' 对当前目录进行dir 将结果放入列表'''
ftp.dir('.', dir_list.append)
p_subdir = dif.split(" ")[-1]

因为在linux 系统下,所有文件或目录通过list (ls -lrt) 都显示如下 没有更好的方式获取远端服务器判断是文件或目录。默认linux系统中目录是以d开头+权限+文件大小+日期等:如

drwxr-x---    2 5005     5010         4096 Oct 31 21:10 recycle

而文件则是以-开头,即没有目录的d标志

-rwxr-xr-x    1 5005     5010          792 Nov 12 09:10 rate_proccess_proc.exp

这样就很好理解以上代码了 只用取split分拣字符列表的最后一个元素就是文件或目录的名称。

请看代码吧:

#! /usr/bin/python # -*- coding: utf-8 -*
#import unittest # 单元测试用例
import os
import re
import sys
import datetime,time
from ftplib import FTP # 定义了FTP类,实现ftp上传和下载
import traceback
import logging
LOG_FORMAT = "%(message)s" #"%(asctime)s %(name)s %(levelname)s %(pathname)s %(message)s "#配置输出日志格式
DATE_FORMAT = '%Y-%m-%d %H:%M:%S %a ' #配置输出时间的格式,注意月份和天数不要搞乱了
LOG_PATH = None#os.path.join(os.getcwd(),'./logs/ftpget.log')
logging.basicConfig(level=logging.DEBUG,
format=LOG_FORMAT,
datefmt = DATE_FORMAT ,
filemode='a', #覆盖之前的记录 'a'是追加
filename=LOG_PATH #有了filename参数就不会直接输出显示到控制台,而是直接写入文件
)
'''
@检查是否下载完整
'''
def IsDownloadCompletely(RemoteFile, LocalFile, remote_size):
p = re.compile(r'\\',re.S)
LocalFile = p.sub('/', LocalFile)
localsize = os.path.getsize(LocalFile)
if localsize == remote_size:
print('downloading %s ...Successs! remote_size:%d , local_size:%d.' %(RemoteFile, remote_size, localsize))
logging.debug('downloading %s ... Successs! remote_size:%d , local_size:%d.' %(RemoteFile, remote_size, localsize))
return True
else:
print('downloading %s ...Successs! remote_size:%d , local_size:%d.' %(RemoteFile, remote_size, localsize))
logging.debug('downloading %s ... Successs! remote_size:%d , local_size:%d.' %(RemoteFile, remote_size, localsize))
return False
'''

@实际负责下载功能的函数

'''
def ftp_download(LocalFile, RemoteFile, bufsize, ftp):
# 本地是否有此文件
if not os.path.exists(LocalFile):
with open(LocalFile, 'wb') as f:
ftp.retrbinary('RETR %s' % RemoteFile, f.write, bufsize)
# ftp.set_debuglevel(0) #关闭调试模式
#到这里文件会写入关闭,放在with语句外面
return IsDownloadCompletely(RemoteFile, LocalFile, bufsize)
else:
if not IsDownloadCompletely(RemoteFile, LocalFile, bufsize):
#如果已存在,但是不完整,重新写入覆盖
with open(LocalFile, 'wb+') as f:
ftp.retrbinary('RETR %s' % RemoteFile, f.write, bufsize)
#到这里文件会写入关闭,放在with语句外面
return IsDownloadCompletely(RemoteFile, LocalFile, bufsize)
"""
# 根据文件名前缀下载文件
"""
def DownLoadRUledFile(LocalDir, RemoteDir, filename, ftp):
print("RemoteDir:", RemoteDir)
if not os.path.exists(LocalDir):
os.makedirs(LocalDir)
Local = os.path.join(LocalDir, filename) # 下载到当地的全路径
try:
# 打开该远程目录
ftp.cwd(RemoteDir)
#直接下载
ftp.voidcmd('TYPE I') # 将传输模式改为二进制模式 ,避免提示 ftplib.error_perm: 550 SIZE not allowed in ASCII
bufsize = ftp.size(filename) #服务器里的文件总大小
#print(bufsize)
ftp_download(Local, filename, bufsize, ftp)
except:
print('some error happend in get file:%s. Err:%s' %(filename, traceback.format_exc()))
logging.debug('some error happend in get file:%s. Err:%s' %(filename, traceback.format_exc()))
return
"""

下载整个目录下的文件

"""
def DownLoadFileTree(LocalDir, RemoteDir, ftp, IsRecursively=False):
print("RemoteDir:", RemoteDir)
if not os.path.exists(LocalDir):
print('local directory %s not exists , make it ...')
os.makedirs(LocalDir)
# 打开该远程目录
ftp.cwd(RemoteDir)
dir_list = []
''' 对当前目录进行dir 将结果放入列表'''
ftp.dir('.', dir_list.append)
for dif in dir_list:
if dif.startswith("d"):
if IsRecursively:
''' 该对象为目录 递归下载'''
print('%s is a directory, download it Recursively...' %(dif))
p_subdir = dif.split(" ")[-1]
p_local_subdir = os.path.join(LocalDir, p_subdir)
''' 本地子目录创建原理相同 但不在此处创建 在递归出创建'''
p_remote_subdir = os.path.join(ftp.pwd(), p_subdir)
DownLoadFileTree(p_local_subdir, p_remote_subdir, ftp)
ftp.cwd("..") # 返回路径最外侧
else:
print('%s is a directory, download mode is UnRecursive, continue...' %(dif))
continue
else:
''' 是文件 直接下载 '''
print('%s is a file, download it directly...' %(dif))
p_filename = dif.split(" ")[-1]
bufsize = ftp.size(p_filename) #服务器里的文件总大小
Localfile = os.path.join(LocalDir, p_filename) # 下载到当地的全路径
ftp_download(Localfile, p_filename, bufsize, ftp)
return
if __name__ == '__main__':
host = '1.2.3.4'
port = 21
username = 'hello'
password = 'world'
ftp = FTP()
ftp.connect(host,port)
ftp.login(username, password)
''' 按文件名下载 '''
LocalDir = os.getcwd()
RemoteDir = '/home/li'
filename = 'readme.txt'
#DownLoadRUledFile(LocalDir, RemoteDir, filename, ftp)
''' 递归下载整个目录 '''
IsRecursively = True #是否递归目录下的子目录 开关 是 True 否 False
DownLoadFileTree(LocalDir, RemoteDir, ftp, IsRecursively)

下载的过程显示:

FTP PYTHON 二级目录 python ftp 下载目录_文件名