应用背景:别的部门或公司会通过FTP更新一些数据给我,而我则会定期将这些数据下载到本地进行数据分析并备份。然而,我觉得每次通过FTP Client进行这些重复性的工作好无趣,所以就上网查了些资料,写了以下脚本,仅供学习参考,侵删。
# -*- coding: utf-8 -*-
"""
Created on
@author: dendejie
Function:下载ftp服务器中指定目录中的所有文件(已下载过的文件不再重复下载)
"""
import ftplib
import os
import socket
HOST = 'XXX.XX.XX.X'#ftp地址
USER = 'XXX' #用户名
PASSWD = 'XXXXXX' #用户密码
LocalDir = r'D:\FTPDowdoads' #本地存贮路径
FTPDir=r'/338x series/006.ExampleCode'#需要下载的ftp目录路径
local_fname='checkfile.txt'#用本地存放已下载过的文件名
local_files=[]#存放从checkfile.txt中读回的文件名
appendFiles=[]#存放需要写进checkfile.txt的文件各
def FtpConnect(host, username, passwd):
'''
连接并登录ftp服务器
host:ftp地址
username:用户名
passwd:用户密码
'''
try:
ftp = ftplib.FTP(HOST)
#ftp.encoding = 'utf-8' #解决中文乱码问题
#ftp.set_debuglevel(0) #不开启调试模式
except (socket.error, socket.gaierror):
print('Error, cannot reach ' + HOST)
return None
else:
print('Connect To Host Success...')
try:
ftp.login(USER, PASSWD)
except ftplib.error_perm:
print('Username or Passwd Error')
#ftp.quit()
return None
else:
print(ftp.getwelcome()) #显示登录ftp信息
print('Login Success...')
ftp.dir()#显示目录下所有目录信息
print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
return ftp
def filelist(ftp):
'''
递归ftp当前目录下的所有文件及目录信息
'''
flist=[]
ftp.dir('.',flist.append)#将目录中的内容存进flist列表
files = [f.split()[-1] for f in flist if f.startswith('-')]#读取flist列表中的信息,以-开头的是常规文件,将该信息以空字符分割成列表,取最后的元素即为文件名
fids=[f.split(None, 4)[-1] for f in flist if f.startswith('-')] #读取flist列表中的信息,以-开头的是常规文件,将该信息以前4个空字符分割成列表,
#最后的元素包括了文件的大小,修改日期时间,可作为文件的标识
dictf=dict(zip(files,fids))#将文件名与对应的标识合成字典
dirs = [f.split()[-1] for f in flist if f.startswith('d')]#读取flist列表中的信息,以d开头的是目录,将该信息以空字符分割成列表,取最后的元素即为目录名
#print(dirs)
return(dictf,dirs)
def FtpDownloadDir(ftp,ftp_dir,local_dir):
'''
递归下载ftp指定目录下的所有文件及目录
'''
print('+++++++++++++++++++++++++++++++++++++++++++++++++++++')
print(f'Walking to {ftp_dir}')
print(f'Walking to {local_dir}')
if ftp_dir=="/":
dirname = "AllDir"#用于本地创建新目录,如果下载的是FTP根目录,则在目录名为AllDir
else:
dirname = os.path.basename(ftp_dir)#否则本地目录名与FTP目录名一样
ftp.cwd(ftp_dir)#进入ftp对应目录
os.chdir(local_dir)#进入本地下载目录
if os.path.exists(dirname):#如果本地dirname目录已存在
os.chdir(dirname)#则直接进入该目录
else:
try:
os.mkdir(dirname)#否则在本地创建该目录
except OSError:
print('OSError!')
else:
os.chdir(dirname)#创建完后进入该目录
ftp_curr_dir = ftp.pwd()#获取FTP当前目录路径
local_curr_dir = os.getcwd()#获取本地当前目录路径
#print(f'Changing to {ftp_curr_dir}')
#print(f'Changing to {local_curr_dir}')
dictf,dirs = filelist(ftp)#调用filelist函数,递归ftp当前目录下的所有文件及目录
for f,k in dictf.items():#获取到的文件信息的键值对
if k not in local_files:#文件标识与本地存储的已下载过的文件标识做对比
FtpDownloadFile(ftp,f,f)#k不在local_files中说明该文件未下载过,则下载该文件
appendFiles.append(k)#同时将该文件的标识存储到appenFiles列表中,用于下载完成后更新本地的checkfile.txt文件
for d in dirs:#对子目录进行处理
FtpDownloadDir(ftp,d,local_curr_dir)#调用自身,递归下载子目录中的文件
ftp.cwd('..')
os.chdir('..')#每次递归完成后,ftp及本地都返回上一层目录,继续其他子目录的处理
os.chdir(local_dir)
def FtpDownloadFile(ftp, remotefile, localfile):
'''
下载ftp当前目录的文件到本地的当前目录中
'''
buffer_size = 10240 #默认是8192
try:
f = open(localfile, 'wb')
ftp.retrbinary(f'RETR {remotefile}', f.write,buffer_size)
except ftplib.error_perm:
print(f'File:{f} Download Error')
#os.unlink(localpath)
else:
print(f'File:{f} Download Success...')
finally:
f.close()
def operfile(fileTxt,op):
'''
操作下载目录中的文件
op为'r'时读取该文件,如文件不存在则忽略
op为'w'时追加写入文件
'''
fp=os.path.join(LocalDir,fileTxt)
if op=='r':
print(f'从 {fp} 中读取本地文件列表')
try:
with open(fp,'r')as ft:
for line in ft:
line = line.strip()
local_files.append(line)
except Exception as e:
print(e)
elif op=='w':
print(f'更新 {fileTxt} 中文件列表')
try:
with open(fp,'a') as ft:
ft.writelines([f'{x}\n' for x in appendFiles])
except Exception as e:
print(e)
else:
print("参数2请输入'r'或者'w'!")
if __name__ == '__main__':
ftp = FtpConnect(HOST, USER, PASSWD)#连接并登录ftp服务器
if ftp:#如果登录成功
operfile(local_fname,'r')#从checkfile.txt中获取已下载过的文件
FtpDownloadDir(ftp,FTPDir,LocalDir)#将ftp指定目录下的文件更新到本地目录中
if appendFiles:#如果有新文件更新到本地
operfile(local_fname,'w')#则将其追加到checkfile.txt中
appendFiles=[]#清空列表
else:
print(f'无需更新{local_fname}')
ftp.quit()
print("FTP QUIT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
1. 代码和相关文本已上传GitHub
2. 代码没有上传功能,因为我不需要用哦,自己问度娘吧。。。
3. 关于自动执行可以通过设置Windows的定时任务实现。
4. 保存在本地checkfile.txt文件很容易会被误删,也许可以通过将这部分信息写到数据库中存储,mysql?还没学会哦。。。