最近在做一个稽核任务,需要FTP登录服务器下载文件到本地和上传文件到服务器,参考了不少例子,功能都太单一,很少能直接拿来使用,于是自己写了一个。FTP下载文件到本地请参考前一篇Python实现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文件
ftp.size(filename) # 返回服务器中的文件大小
1. 上传整个目录包括子目录
def upload_file_tree(local_path, remote_path, ftp, IsRecursively):
核心函数
入参:local_path:本地目录,可以是当前路径
remote_path: 远端目录 ,必须是服务器上存在的且有权限访问的目录
ftp: FTP() 创建的类对象
IsRecursively:是否递归即是否下载所有文件包含子目录 True或者False
2. 指定文件名上传
def upload_file(local_file, remote_file, ftp):
核心函数
入参:local_file本地目录,可以是当前路径
remote_file: 远端目录 ,必须是服务器上存在的且有权限访问的目录
ftp: FTP() 创建的类对象
3. 简单的检查函数
def is_same_size(ftp, local_file, remote_file):
入参:local_file:下载完成后本地存在此文件
remote_file: 远端文件名, 传入远端文件名即可
ftp:FTP() 创建的类对象
注意ftp切换目录时,递归上传目录后需要返回上一层
代码如下,只需修改if __name__ == '__main__': 内的目录和文件名即可 是否递归上传源目录下的子目录通过IsRecursively 控制
#! /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
FTP_PERFECT_BUFF_SIZE = 8192
'''
@检查是否下载/上传完整
'''
def is_same_size(ftp, local_file, remote_file):
"""判断远程文件和本地文件大小是否一致
参数:
local_file: 本地文件
remote_file: 远程文件
"""
try:
remote_file_size = ftp.size(remote_file)
except Exception as err:
logging.debug("get remote file_size failed, Err:%s" % err)
remote_file_size = -1
try:
local_file_size = os.path.getsize(local_file)
except Exception as err:
logging.debug("get local file_size failed, Err:%s" % err)
local_file_size = -1
#三目运算符
result = True if(remote_file_size == local_file_size) else False
return (result, remote_file_size, local_file_size)
'''
@实际负责上传功能的函数
'''
def upload_file(local_file, remote_file, ftp):
# 本地是否有此文件
if not os.path.exists(local_file):
logging.debug('no such file or directory %s.' (local_file))
return False
result = False
remote_file_size = local_file_size = -1
result, remote_file_size, local_file_size = is_same_size(ftp, local_file, remote_file)
if True != result:
print('remote_file %s is not exist, now trying to upload...' %(remote_file))
logging.debug('remote_file %s is not exist, now trying to upload...' %(remote_file))
global FTP_PERFECT_BUFF_SIZE
bufsize = FTP_PERFECT_BUFF_SIZE
try:
with open(local_file, 'rb') as file_handler :
#ftp.retrbinary('RETR %s' % remote_file, f.write, bufsize)
if ftp.storbinary('STOR ' + remote_file, file_handler, bufsize):
result, remote_file_size, local_file_size = is_same_size(ftp, local_file, remote_file)
except Exception as err:
logging.debug('some error happend in storbinary file :%s. Err:%s' %(local_file, traceback.format_exc()))
result = False
logging.debug('Upload 【%s】 %s , remote_file_size = %d, local_file_size = %d.' \
%(remote_file, 'success' if (result == True) else 'failed', remote_file_size, local_file_size))
print('Upload 【%s】 %s , remote_file_size = %d, local_file_size = %d.' \
%(remote_file, 'success' if (result == True) else 'failed', remote_file_size, local_file_size))
"""
上传整个目录下的文件
"""
def upload_file_tree(local_path, remote_path, ftp, IsRecursively):
print("remote_path:", remote_path)
#创建服务器目录 如果服务器目录不存在 就从当前目录创建目标外层目录
# 打开该远程目录
try:
ftp.cwd(remote_path) # 切换工作路径
except Exception as e:
print('Except INFO:', e)
base_dir, part_path = ftp.pwd(), remote_path.split('/')
for subpath in part_path:
# 针对类似 '/home/billing/scripts/zhf/send' 和 'home/billing/scripts/zhf/send' 两种格式的目录
#如果第一个分解后的元素是''这种空字符,说明根目录是从/开始,如果最后一个是''这种空字符,说明目录是以/结束
#例如 /home/billing/scripts/zhf/send/ 分解后得到 ['', 'home', 'billing', 'scripts', 'zhf', 'send', ''] 首位和尾都不是有效名称
if '' == subpath:
continue
base_dir = os.path.join(base_dir, subpath) #base_dir + subpath + '/' # 拼接子目录
try:
ftp.cwd(base_dir) # 切换到子目录, 不存在则异常
except Exception as e:
print('Except INFO:', e)
print('remote not exist directory %s , create it.' %(base_dir))
ftp.mkd(base_dir) # 不存在创建当前子目录 直到创建所有
continue
#本地目录切换
try:
#os.chdir(local_path)
#远端目录通过ftp对象已经切换到指定目录或创建的指定目录
file_list = os.listdir(local_path)
#print(file_list)
for file_name in file_list:
if os.path.isdir(os.path.join(local_path, file_name) ):
print('%s is a directory...' %(file_name))
if IsRecursively: #递归目录上传
#创建相关的子目录 创建不成功则目录已存在
try:
cwd = ftp.pwd()
ftp.cwd(file_name) # 如果cwd成功 则表示该目录存在 退出到上一级
ftp.cwd(cwd)
except Exception as e:
print('check remote directory %s not eixst, now trying to create it! Except INFO:%s.' %(file_name, e))
ftp.mkd(file_name)
print('trying to upload directory %s --> %s ...' %(file_name, remote_path))
logging.debug('trying to upload directory %s --> %s ...' %(file_name, remote_path))
p_local_path = os.path.join(local_path, file_name)
p_remote_path = os.path.join(ftp.pwd(), file_name)
upload_file_tree(p_local_path, p_remote_path, ftp, IsRecursively)
#对于递归 ftp 每次传输完成后需要切换目录到上一级
ftp.cwd("..")
else:
logging.debug('translate mode is UnRecursively, %s is a directory, continue ...' %(file_name))
continue
else:
#是文件 直接上传
local_file = os.path.join(local_path, file_name)
remote_file = os.path.join(remote_path, file_name)
if upload_file(local_file, remote_file, ftp):
pass
#logging.debug('upload %s success, delete it ...' %(local_file))
#os.unlink(local_file)
except:
logging.debug('some error happend in upload file :%s. Err:%s' %(file_name, traceback.format_exc()))
return
if __name__ == '__main__':
""" 定义LOG名 """
current_time = time.time()
str_time = time.strftime('%Y%m%d%H%M%S',time.localtime(current_time ))
log_file_name = 'ftpput' + str_time + '.log'
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 = os.path.join(os.getcwd(), log_file_name)
logging.basicConfig(level=logging.DEBUG,
format=LOG_FORMAT,
datefmt = DATE_FORMAT ,
filemode='w', #覆盖之前的记录 'a'是追加
filename=LOG_PATH #有了filename参数就不会直接输出显示到控制台,而是直接写入文件
)
host = '1.2.3.4'
port = 21
username = 'li'
password = 'ly'
ftp = FTP()
ftp.connect(host,port)
ftp.login(username, password)
''' 上传整个路径下的文件 '''
local_path = 'li'
remote_path = 'ly'
#local_file =None
#remote_file = None
IsRecursively = True
#递归传输本地所有文件包括目录 递归开关 IsRecursively
upload_file_tree(local_path, remote_path, ftp, IsRecursively)
#传递本地指定文件
#upload_file(local_file, remote_file, ftp)
上传的过程: 初次获取远端文件大小会失败,抛出错:get remote file_size failed, Err:550 Could not get file size.,但是不影响程序,继续走后面的程序分支上传。