Python中默認安裝的ftplib模塊定義了FTP類,可用來實現簡單的ftp客戶端,用於上傳或下載文件。
ftplib模塊常用方法
ftp登陸連接 from ftplib import FTP #加載ftp模塊 ftp=FTP() #設置變量 ftp.set_debuglevel(2) #打開調試級別2,顯示詳細信息 ftp.connect("IP","port") #連接的ftp sever和端口 ftp.login("user","password") #連接的用戶名,密碼 print ftp.getwelcome() #打印出歡迎信息 ftp.cmd("xxx/xxx") #進入遠程目錄 bufsize=1024 #設置的緩沖區大小 filename="filename.txt" #需要下載的文件 file_handle=open(filename,"wb").write #以寫模式在本地打開文件 ftp.retrbinaly("RETR filename.txt",file_handle,bufsize) #接收服務器上文件並寫入本地文件 ftp.set_debuglevel(0) #關閉調試模式 ftp.quit() #退出ftp ftp相關命令操作 ftp.cwd(pathname) #設置FTP當前操作的路徑 ftp.dir() #顯示目錄下所有目錄信息 ftp.nlst() #獲取目錄下的文件 ftp.mkd(pathname) #新建遠程目錄 ftp.pwd() #返回當前所在位置 ftp.rmd(dirname) #刪除遠程目錄 ftp.delete(filename) #刪除遠程文件 ftp.rename(fromname, toname)#將fromname修改名稱為toname。 ftp.storbinaly("STOR filename.txt",file_handel,bufsize) #上傳目標文件 ftp.retrbinary("RETR filename.txt",file_handel,bufsize) #下載FTP文件
FTP.quit()與FTP.close()的區別
- FTP.quit():發送QUIT命令給服務器並關閉掉連接。這是一個比較“緩和”的關閉連接方式,但是如果服務器對QUIT命令返回錯誤時,會拋出異常。
- FTP.close():單方面的關閉掉連接,不應該用在已經關閉的連接之后,例如不應用在FTP.quit()之后。
下載、上傳文件
#!/usr/bin/python # -*- coding: utf-8 -*- # @Time : 2018/8/6 17:17 # @File : ftpclient.py # @Software: PyCharm # FTP操作 from ftplib import FTP # 加載ftp模塊 from ftplib import error_perm from utils import file_util import os import time import socket from concurrent.futures import ThreadPoolExecutor host = '127.0.0.1' username = 'egon' password = '123456' file = '1.txt' port = 2111 def ftpconnect(host, port, username, password): ftp = FTP() # ftp.set_debuglevel(2) #打開調試級別2,顯示詳細信息 ftp.encoding = 'utf-8' # 解決中文編碼問題,默認是latin-1 try: ftp.connect(host, port) # 連接 ftp.login(username, password) # 登錄,如果匿名登錄則用空串代替即可 print(ftp.getwelcome()) # 打印歡迎信息 except(socket.error, socket.gaierror): # ftp 連接錯誤 print("ERROR: cannot connect [{}:{}]" .format(host, port)) return None except error_perm: # 用戶登錄認證錯誤 print("ERROR: user Authentication failed ") return None return ftp def is_ftp_file(ftp_conn, ftp_path): try: if ftp_path in ftp_conn.nlst(os.path.dirname(ftp_path)): return True else: return False except error_perm: return False def downloadfile(ftp, remotepath, localpath): """ 下載文件 :param ftp: :param remotepath: :param localpath: :return: """ bufsize = 1024 # 設置緩沖塊大小 fp = open(localpath, 'wb') # 以寫模式在本地打開文件 res = ftp.retrbinary( 'RETR ' + remotepath, fp.write, bufsize) # 接收服務器上文件並寫入本地文件 if res.find('226') != -1: print('download file complete', localpath) ftp.set_debuglevel(0) # 關閉調試 fp.close() # 關閉文件 def uploadfile(ftp, remotepath, localpath): """ 上傳文件 :param ftp: :param remotepath: :param localpath: :return: """ bufsize = 1024 fp = open(localpath, 'rb') res = ftp.storbinary('STOR ' + remotepath, fp, bufsize) # 上傳文件 if res.find('226') != -1: print('upload file complete', remotepath) ftp.set_debuglevel(0) fp.close() def ftp_theadpool(func, ftp, file_list): """ 通過線程池調用上傳文件列表 :param func: :param file_list: :return: """ pool = ThreadPoolExecutor(6) for remotepath, localpath in file_list: pool.submit(func, ftp, remotepath, localpath) pool.shutdown() if __name__ == "__main__": ftp = ftpconnect(host, port, username, password) file_list = ftp.nlst() print(file_list) # 將傳輸模式改為二進制模式 ,避免提示 ftplib.error_perm: 550 SIZE not allowed in ASCII # mode錯誤 ftp.voidcmd('TYPE I') file_size = ftp.size("sqldeveloper-3.1.07.42.zip") # 文件大小 print('filesize [{}]'.format(file_util.bytes2human(file_size))) start = time.time() downloadfile(ftp, "sqldeveloper-3.1.07.42.zip", "e:/x.zip") end = time.time() print('consume time [{}]'.format(end - start)) if '20180910' not in file_list: # 創建目錄 res = ftp.mkd('20180910') print('mk ', res) ftp.cwd('20180910') # 進入到新目錄 print("FTP當前路徑:", ftp.pwd()) print("文件信息:", ftp.nlst()) uploadfile(ftp, "testup.zip", "e:/x.zip") # 上傳文件 # ftp.cwd('20180910') # pwd_path = ftp.pwd() # print("FTP當前路徑:", pwd_path) # print("文件信息:", ftp.nlst()) ftp.quit()
帶進度條下載文件
from ftplib import FTP from ftplib import error_perm import os import socket import os import time from utils import my_logset from utils.time_utils import run_time import sys import math from utils import file_util """ ftp操作上傳和下載 """ class FTP_OPS(object): """ ftp文件操作 """ def __init__(self, log_file, ftp_ip, ftp_port, ftp_user, ftp_pwd): self.db_log = my_logset.get_mylogger("ftp", log_file) self.ftp_ip = ftp_ip self.ftp_port = ftp_port self.ftp_user = ftp_user self.ftp_pwd = ftp_pwd def ftp_connect(self): """ 連接ftp :return: """ socket.setdefaulttimeout(160) # 超時FTP時間設置為60秒 ftp = FTP() ftp.connect(host=self.ftp_ip, port=self.ftp_port) ftp.set_debuglevel(2) # 開啟調試模式 ftp.encoding = 'utf-8' try: ftp.login(self.ftp_user, self.ftp_pwd) self.db_log.info( '[{}]login ftp {}'.format( self.ftp_user, ftp.getwelcome())) # 打印歡迎信息 except(socket.error, socket.gaierror): # ftp 連接錯誤 self.db_log.warn( "ERROR: cannot connect [{}:{}]".format( self.ftp_ip, self.ftp_port)) return None except error_perm: # 用戶登錄認證錯誤 self.db_log.warn("ERROR: user Authentication failed ") return None except Exception as e: print(e) return None return ftp @run_time def upload_file(self, ftp: FTP, remotepath: str, localpath: str, file: str): """ # 從本地上傳文件到ftp :param ftp: ftp對象 :param remotepath: ftp遠程路徑 :param localpath: 本地 :return: """ flag = False buffer_size = 10240 # 默認是8192 print(ftp.getwelcome()) # 顯示登錄ftp信息 fp = open(os.path.join(localpath, file), 'rb') try: ftp.cwd(remotepath) # 進入遠程目錄 self.db_log.info( "found folder [{}] in ftp server, upload processing.".format(remotepath)) print('進入目錄', ftp.pwd()) # 將傳輸模式改為二進制模式 ,避免提示 ftplib.error_perm: 550 SIZE not allowed in # ASCII ftp.voidcmd('TYPE I') ftp.storbinary('STOR ' + file, fp, buffer_size) ftp.set_debuglevel(0) self.db_log.info("上傳文件 [{}] 成功".format(file)) flag = True except error_perm as e: self.db_log.warn('文件[{}]傳輸有誤,{}'.format(file, str(e))) except TimeoutError: self.db_log.warn('文件[{}]傳輸超時'.format(file)) pass except Exception as e: self.db_log.warn('文件[{}]傳輸異常'.format(file, str(e))) pass finally: fp.close() return {'file_name': file, 'flag': flag} def download_file(self, ftp_file_path, dst_file_path): """ 從ftp下載文件到本地 :param ftp_file_path: ftp下載文件 :param dst_file_path: 本地存放 :return: """ buffer_size = 10240 # 默認是8192 ftp = self.ftp_connect() print(ftp.getwelcome()) # 顯示登錄ftp信息 # 將傳輸模式改為二進制模式 ,避免提示 ftplib.error_perm: 550 SIZE not allowed in ASCII ftp.voidcmd('TYPE I') remote_file_size = ftp.size(ftp_file_path) # 文件總大小 print('remote filesize [{}]'.format(remote_file_size)) cmpsize = 0 # 下載文件初始大小 lsize = 0 # check local file isn't exists and get the local file size if os.path.exists(dst_file_path): lsize = os.stat(dst_file_path).st_size if lsize >= remote_file_size: print('local file is bigger or equal remote file') return start = time.time() conn = ftp.transfercmd('RETR {0}'.format(ftp_file_path), lsize) f = open(dst_file_path, "ab") while True: data = conn.recv(buffer_size) if not data: break f.write(data) cmpsize += len(data) self.progressbar(cmpsize, remote_file_size) # print( # '\b'*30, 'download process:%.2f%%' % # (float(cmpsize) / remote_file_size * 100)) # ftp.retrbinary( # 'RETR {0}'.format(ftp_file_path), # f.write, # buffer_size) f.close() try: ftp.voidcmd('NOOP') print('keep alive cmd success') ftp.voidresp() print('No loop cmd') conn.close() ftp.quit() except Exception as e: pass finally: end = time.time() print('consume time [{}]'.format(end - start)) file_size = os.stat(dst_file_path).st_size print('local filesize [{}] md5:[{}]'.format( file_size, file_util.get_md5(dst_file_path))) def progressbar(cur, total): """ 進度條顯示 cur表示當前的數值,total表示總的數值。 :param cur: :param total: :return: """ percent = '{:.2%}'.format(cur / total) sys.stdout.write('\r') sys.stdout.write('[%-50s] %s' % ('=' * int(math.floor(cur * 50 / total)), percent)) sys.stdout.flush() if cur == total: sys.stdout.write('\n') if __name__ == '__main__': host = "10.0.0.1" username = "test" password = "test" port = "21" ftp_file_path = "/data/an/1.zip" dst_file_path = "/data/tmp/1.zip" ftp = FTP_OPS(host=host, username=username, password=password, port=port) ftp.download_file(ftp_file_path=ftp_file_path, dst_file_path=dst_file_path)