背景:最近工作有需要自动化FTP上传下载数据并进行对比的功能,本来是打算直接在网上搜现成的来改一下的,但是整合完发现,一次性下载超过200多个文件的时候就会报错,而且看别人写的代码是一件痛苦的事情,所以就干脆搜了点资料自己写了,现在记个笔记,有需要的可以拿现成的用。
目录
ftplib模块的一些基本的东西:
OS模块的一些操作:
自己的实现部分:
ftplib模块的一些基本的东西:
from ftplib import FTP # 导入FTP模块
ftp = FTP() # 设置变量
ftp.set_debuglevle(2) # 设置实例的调试级别,0:不输出(缺省值),1:中等调试输出,通常每个请求一行,2:显示详细信息
ftp.connect(host='', port=0) # 连接到给定的主机和端口(默认21),很少需要指定其他端口号。
ftp.getwelcome() # 打印出欢迎信息
ftp.login('user', 'password') # 以用户身份登录
ftp.abort() # 中止正在进行的文件传输
ftp.retrbinary("RETR filename", fp, blocksize) # 接受服务器上的文件并写入本地文件
ftp.storbinary("STOR filename", fp, blocksize) # 以二进制传输模式存储文件,即上传文件至FTP服务器
ftp.set_pasv() # 设置模式, true为被动模式(默认值),false为主动模式
ftp.quit() # 退出ftp
ftp.cwd(pathname) # 设置FTP当前操作的路径
ftp.dir() # 显示目录下所有目录信息
ftp.nlist() # 获取目录下的文件
ftp.mkd(pathname) # 新建远程目录
ftp.pwd() # 返回当前所在位置
ftp.rmd(dirname) # 删除远程目录
ftp.delete(filename) # 删除远程文件
ftp.rename(fromname, toname) # 将fromname修改为toname
ftp.size(filename) # 请求服务器上名为filename的文件的大小
OS模块的一些操作:
方法说明
os.mkdir创建目录
os.rmdir删除目录
os.rename重命名
os.remove删除文件
os.getcwd获取当前工作路径
os.walk遍历目录
os.path.join连接目录与文件名
os.path.split分割文件名与目录
os.path.abspath获取绝对路径
os.path.dirname获取路径
os.path.basename获取文件名或文件夹名
os.path.splitext分离文件名与扩展名
os.path.isfile判断给出的路径是否是一个文件
os.path.isdir判断给出的路径是否是一个目录
方法说明 os.mkdir创建目录 os.rmdir删除目录 os.rename重命名 os.remove删除文件 os.getcwd获取当前工作路径 os.walk遍历目录 os.path.join连接目录与文件名 os.path.split分割文件名与目录 os.path.abspath获取绝对路径 os.path.dirname获取路径 os.path.basename获取文件名或文件夹名 os.path.splitext分离文件名与扩展名 os.path.isfile判断给出的路径是否是一个文件 os.path.isdir判断给出的路径是否是一个目录
自己的实现部分:
#! /usr/bin/python # -*- coding: utf-8 -*
import re
import sys
import datetime, time
from ftplib import FTP # 定义了FTP类,实现ftp上传和下载
import traceback
import logging
import os
"""
V1.0:
初步实现功能
V1.1:
目录下载方式进行优化,目录下存在目录的话,也会将该目录下的文件进行下载
进行文件对比的优化,进行对比的目录中存在目录的话,也会将该目录下的文件进行对比
备注:
Linux系统,所下载的文件权限(其他用户权限)没有读权限时会报错:ftplib.error_perm: 550 Failed to open file.
-rw-rw---- 1 ftp 1000 291151872 Mar 29 20:55 sj1.dat #无法下载
-rw-rw-rw- 1 ftp 1000 11829248 Mar 29 20:55 sj2.dat #可下载
-rw------- 1 ftp 1000 17743872 Mar 29 20:55 sj3.dat #无法下载
从左至右:-rwxrwxrwx
最左侧1位d表示文件夹,l表示连接文件,-表示文件
2-4位数字代表文件所有者的权限
5-7位数字代表同组用户的权限
8-10数字代表其他用户的权限
权限命令:chmod 777 文件名
无权限(-)=0,读(r)=4,写(w)=2,执行(x)=1,例如:读+写+执行=4+2+1=7
"""
class MyFTP:
"""
ftp自动下载、自动上传脚本,可以递归目录操作
"""
def __init__(self, host, port=21, username=None, password=None):
""" 初始化 FTP 客户端
参数:
host:ip地址
port:端口号
"""
self.log()
self.logger.info("__init__()---> host = %s ,port = %s ,username=%s ,password = %s ," % (host, port, username, password))
self.ftp = FTP()
self.ftp.set_debuglevel(2)
self.ftp.connect(host, port)
self.ftp.login(username, password)
# 设置下编码方式、主被动模式、缓冲区大小,打印欢迎信息
self.ftp.encoding = 'gbk'
self.ftp.set_pasv(True)
self.BLOCKSIZE = 8192
self.logger.info(self.ftp.getwelcome())
def log(self):
"""
创建日志器
设置日志打印级别
创建一个handler,用于写入日志文件
mode = "a":追加,“w”:覆盖
"""
self.logger = logging.getLogger()
self.logger.setLevel(logging.INFO)
fh = logging.FileHandler(filename="log.log", mode='w', encoding='utf-8') # 指定utf-8格式编码,避免输出的日志文本乱码
fh.setLevel(logging.DEBUG)
#创建一个handler,用于将日志输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
# 定义handler的输出格式
formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 给logger添加handler
self.logger.addHandler(fh)
self.logger.addHandler(ch)
def FILE_Compare(self, path_0, path_1):
'''
进行文件内容比较
'''
with open(path_0, 'rb') as file_byte:
a = file_byte.read()
with open(path_1, 'rb') as file_byte1:
b = file_byte1.read()
if a == b:
self.logger.debug('内容对比:YES-->:[%s<--->%s]' % (path_0, path_1))
else:
self.logger.info("内容对比:NO-->:[%s<--->%s]" % (path_0, path_1))
return
def Obtain_Directory_list(self, path, a=1):
"""
“进入指定列表并获取列表下所有文件信息”
:param remote_dir: 远程路径
:return: 输出目录下文件列表
:param a: a==0:获取远程路径列表
a==1:获取本地路径列表
:return:
"""
if a == 0:
self.ftp.cwd(path) #进入远程路径
return self.ftp.nlst()#获取路径下文件名
elif a == 1:
return os.listdir(path)#获取本地路径下文件名
def FTP_Download_File(self, local_dir, remote_dir):
"""
下载单个文件
:param local_dir:本地路径
:param remote_dir:远程路径
:return:
"""
with open(local_dir, "wb") as f:
try:
self.ftp.retrbinary("RETR %s" % remote_dir, f.write, self.BLOCKSIZE)
except Exception as result:
self.logger.warning("下载文件%s时发生错误 % s----->可能是文件无读写权限" % (remote_dir, result))
return
return()
def FTP_Upload_File(self, local_dir, remote_dir):
"""
上传单个文件
:param local_dir:本地路径
:param remote_dir:远程路径
:return:
"""
with open(local_dir, "rb") as f:
self.ftp.storbinary("STOR %s" % remote_dir, f, self.BLOCKSIZE)
return ()
def FTP_Download_Directory(self, local_dir, remote_dir):
# """
# "下载整个目录"
# :param local_dir:
# :param remote_dir:
# :return:
# """
# local_dir = local_dir
# List = self.Obtain_Directory_list(remote_dir, a=0)
#
# for a in List:
# self.logger.info(local_dir + a + "----kaisi--->" + remote_dir + "/" + a)
# self.FTP_Download_File(str(local_dir + a), str(remote_dir + "/" + a))
#
return print("该函数已经弃用")
def FTP_Upload_Directory(self, local_dir, remote_dir):
"""
“上传整个目录”
:param local_dir:
:param remote_dir:
:return:
"""
local_dir = local_dir
List = self.Obtain_Directory_list(local_dir, a=1)
for a in List:
self.logger.debug(local_dir + a + "------->" + remote_dir + "/" + a)
self.FTP_Upload_File(str(local_dir + "/" + a), str(remote_dir + a))
return
def FILE_Compare_EXECUTE(self, path_0, path_1):
"""
“对目录文件进行比较”
:param path_0: 第一个目录路径
:param path_1: 第二个目录路径
:return:
列表比较方式
a = [1,0,2] b = [1,0,0]
x = [k for k in a if k in b] x = [1,0]
x = [k for k in a if k not in b] x = [2]
"""
path_0_list = self.Obtain_Directory_list(path_0, a=1)
path_1_list = self.Obtain_Directory_list(path_1, a=1)
x = [k for k in path_0_list if k not in path_1_list]
y = [k for k in path_1_list if k not in path_0_list]
z = [k for k in path_0_list if k in path_1_list]
if x or y:
self.logger.info("丨———————————————↓———————————————————丨")
self.logger.info("目录" + path_0 + "缺少>>:" + str(y))
self.logger.info("目录" + path_1 + "缺少>>:" + str(x))
self.logger.info("丨———————————————↑———————————————————丨")
else:
self.logger.debug(">----两个目录文件个数一致,无缺失<----")
for i in z:
#判断路径是否为目录,是则进入路径,不是则进行文件比较
if os.path.isdir(path_0 + i) == True:
logging.debug("转到目录:--->" + path_0 + i + "/" + "与" + path_1 + i + "/")
self.FILE_Compare_EXECUTE(path_0 + i + "/", path_1 + i + "/")
else:
self.FILE_Compare(path_0 + i, path_1 + i)
def get_file_name(self, line):
""" 获取远程路径目录下的文件名
pos = line.rfind(' '):匹配字符串最后一次出现的地方
参数:
line:
"""
pos = line.rfind(' ')
while (line[pos] != ' '):
pos += 1
while (line[pos] == ' '):
pos += 1
file_arr = [line[0], line[pos:]]
return file_arr
def FTP_Download_Directory_duo_Directory(self, local_dir, remote_dir):
"""
"下载整个目录"
:param local_dir:本地路径
:param remote_dir:远程路径
:return:
"""
local_dir = local_dir
remote_dir = remote_dir + "/"
self.ftp.cwd(remote_dir) # 进入远程路径
dir_list = []
self.ftp.dir('.', dir_list.append) #获取本地文件
list_list = []
for i in dir_list:
list_list.append(self.get_file_name(i))
self.logger.debug(list_list)
for a in list_list:
# 判断是否为目录
if a[0] == "d":
#判断本地是否存在该目录,没有则创建
if os.path.exists(local_dir + a[1]) == False:
os.mkdir(local_dir + a[1])
self.logger.debug(local_dir + "/" + a[1] + "/" + "------建文件夹并进入------" + remote_dir + a[1])
#递归进入下一路径
self.FTP_Download_Directory_duo_Directory(local_dir + a[1] + "/", remote_dir + a[1])
elif a[0] == "-":
self.logger.info(str(local_dir + a[1]) + "-------下载文件-----" + str(remote_dir + a[1]))
self.FTP_Download_File(str(local_dir + a[1]), str(remote_dir + "/" + a[1]))
if __name__ == '__main__':
My_FTP = MyFTP("10.0.1.4")
My_FTP.FILE_Compare_EXECUTE("H:/1/", "H:/2/")
My_FTP.ftp.close()#退出FTP
'''
使用示例
下载单个文件
My_FTP.FTP_Download_File("H:/200001010034-vib.dat", "/LH_DATA/hdd/clb/raw/20000101/200001010034-vib.dat")
上传单个文件
My_FTP.FTP_Upload_File("H:/200001010034-vib.dat","/LH_DATA/hdd/log/200001010034-vib.dat")
下载目录:目录中不能有目录,不然可能会报错--------->(弃用,已经被注释)
My_FTP.FTP_Download_Directory("H:/2/", "/LH_DATA/hdd/clb/raw/20000101")
下载整个目录:目录中有目录也可下载
My_FTP.FTP_Download_Directory_duo_Directory("H:/vib/", "/motor/motor/20220806")
上传目录:目录中不能有目录,不然可能会报错
My_FTP.FTP_Upload_Directory("H:/2/","/LH_DATA/hdd/log/")
目录文件对比:
My_FTP.FILE_Compare_EXECUTE("H:/1/", "H:/2/")
'''
由于在Linux系统下创建目录还需要给权限,不然没法直接上传文件,所以就懒得写上传目录的部分了,有需要的小伙伴可以参考一下代码中《下载整个目录》的部分然后自己写。