FTP 协议实战:Python ftplib 模块实现文件上传/下载(附 3 个完整代码示例)
·
Python ftplib 实战指南:从基础连接到自动化文件传输
1. FTP协议与Python的完美结合
FTP(文件传输协议)作为互联网上最古老的文件传输标准之一,至今仍在服务器管理、数据备份和网站维护等领域发挥着重要作用。Python内置的ftplib模块为我们提供了一套简洁而强大的工具,让开发者能够轻松实现跨系统的文件传输自动化。
与直接使用FTP客户端软件不同,通过编程方式操作FTP可以实现:
- 批量文件处理 :自动上传/下载整个目录树
- 定时任务集成 :与crontab或Windows任务计划结合
- 异常处理 :自定义传输失败的重试机制
- 日志记录 :详细记录每次传输的元数据
from ftplib import FTP
# 最简单的连接示例
with FTP('ftp.example.com') as ftp:
ftp.login(user='username', passwd='password')
print("服务器欢迎信息:", ftp.getwelcome())
2. 建立可靠连接的关键步骤
2.1 基础连接配置
完整的FTP连接应该包含以下要素:
from ftplib import FTP, error_perm
import socket
def create_ftp_connection(host, port=21, timeout=30):
try:
ftp = FTP()
ftp.connect(host, port, timeout)
ftp.login() # 匿名登录
return ftp
except socket.timeout:
print(f"连接超时,请检查网络或服务器状态")
except error_perm as e:
print(f"认证失败: {str(e)}")
except Exception as e:
print(f"未知错误: {str(e)}")
return None
2.2 被动模式 vs 主动模式
| 特性 | 主动模式(PORT) | 被动模式(PASV) |
|---|---|---|
| 数据通道建立方 | 服务器主动连接客户端 | 客户端连接服务器 |
| 防火墙友好性 | 差 | 优 |
| 典型问题 | 客户端防火墙拦截 | 服务器需开放随机端口 |
| 推荐场景 | 内网环境 | 公网环境 |
# 强制使用被动模式
ftp = FTP()
ftp.set_pasv(True) # 默认为True
提示:现代网络环境中,被动模式(pasv=True)是更安全可靠的选择,能有效穿越NAT和防火墙
3. 文件传输实战:3个完整代码示例
3.1 基础目录列表与文件检索
def list_ftp_contents(ftp, path='.', detail=False):
"""
列出FTP服务器目录内容
:param ftp: 已建立的FTP连接
:param path: 要列出的路径
:param detail: 是否显示详细信息
:return: 文件列表
"""
try:
ftp.cwd(path)
files = []
if detail:
ftp.retrlines('LIST', files.append)
else:
files = ftp.nlst()
return files
except error_perm as e:
print(f"无法访问 {path}: {str(e)}")
return []
# 使用示例
with FTP('ftp.gnu.org') as ftp:
ftp.login()
print("GNU根目录内容:", list_ftp_contents(ftp))
print("详细列表:", list_ftp_contents(ftp, detail=True))
3.2 安全文件上传与下载
def upload_file(ftp, local_path, remote_path, chunk_size=8192):
"""
上传文件到FTP服务器
:param ftp: 已建立的FTP连接
:param local_path: 本地文件路径
:param remote_path: 远程保存路径
:param chunk_size: 传输块大小
"""
try:
with open(local_path, 'rb') as f:
ftp.storbinary(f'STOR {remote_path}', f, blocksize=chunk_size)
print(f"文件 {local_path} 上传成功")
except IOError:
print("本地文件读取失败")
except error_perm:
print("远程路径无写入权限")
def download_file(ftp, remote_path, local_path, chunk_size=8192):
"""
从FTP服务器下载文件
:param ftp: 已建立的FTP连接
:param remote_path: 远程文件路径
:param local_path: 本地保存路径
:param chunk_size: 传输块大小
"""
try:
with open(local_path, 'wb') as f:
ftp.retrbinary(f'RETR {remote_path}', f.write, blocksize=chunk_size)
print(f"文件 {remote_path} 下载成功")
except IOError:
print("本地文件写入失败")
except error_perm:
print("远程文件不存在或无读取权限")
3.3 带进度显示的大文件传输
class ProgressTracker:
def __init__(self, total_size):
self.total = total_size
self.transferred = 0
def __call__(self, chunk):
self.transferred += len(chunk)
percent = (self.transferred / self.total) * 100
print(f"\r传输进度: {percent:.1f}%", end='')
def download_with_progress(ftp, remote_path, local_path):
"""
带进度条显示的文件下载
"""
try:
file_size = ftp.size(remote_path)
if file_size == 0: raise error_perm
tracker = ProgressTracker(file_size)
with open(local_path, 'wb') as f:
ftp.retrbinary(f'RETR {remote_path}',
lambda data: (f.write(data), tracker(data)),
blocksize=32768)
print("\n下载完成")
except error_perm:
print("无法获取文件大小或文件不存在")
4. 高级应用与错误处理
4.1 递归目录操作
def upload_directory(ftp, local_dir, remote_dir):
"""
递归上传整个目录
"""
for item in os.listdir(local_dir):
local_path = os.path.join(local_dir, item)
remote_path = f"{remote_dir}/{item}"
if os.path.isdir(local_path):
try:
ftp.mkd(remote_path)
except error_perm:
pass # 目录已存在
upload_directory(ftp, local_path, remote_path)
else:
upload_file(ftp, local_path, remote_path)
4.2 常见错误代码处理
| 错误代码 | 含义 | 建议处理方式 |
|---|---|---|
| 421 | 服务不可用 | 等待后重试或联系管理员 |
| 425 | 无法打开数据连接 | 检查防火墙设置或切换传输模式 |
| 450 | 文件忙 | 等待文件释放后重试 |
| 550 | 文件不可用/权限不足 | 检查路径拼写和权限设置 |
def robust_ftp_operation(func, max_retries=3):
"""
带重试机制的FTP操作装饰器
"""
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except error_perm as e:
if str(e).startswith('550'):
print("文件不存在或权限不足")
break
print(f"尝试 {attempt+1} 失败: {str(e)}")
time.sleep(2**attempt) # 指数退避
return None
return wrapper
5. 性能优化与安全实践
5.1 传输优化技巧
- 缓冲区大小 :根据网络条件调整blocksize(通常8KB-64KB最佳)
- 并行传输 :使用多线程同时传输多个小文件
- 压缩传输 :先压缩再传输大目录
- 二进制模式 :始终使用
storbinary/retrbinary避免编码问题
# 多文件并行传输示例
from concurrent.futures import ThreadPoolExecutor
def batch_download(ftp, file_pairs, workers=4):
"""
多线程批量下载文件
:param file_pairs: [(remote_path, local_path), ...]
"""
def download_one(pair):
remote, local = pair
download_file(ftp, remote, local)
with ThreadPoolExecutor(max_workers=workers) as executor:
executor.map(download_one, file_pairs)
5.2 安全增强建议
- 使用FTPS(FTP over SSL)替代普通FTP
- 避免在代码中硬编码凭据,使用环境变量或配置管理工具
- 限制FTP用户的访问目录(chroot)
- 定期轮换访问凭证
# 使用FTPS的示例
from ftplib import FTP_TLS
ftps = FTP_TLS('ftp.example.com')
ftps.login(user='secure', passwd='password')
ftps.prot_p() # 加密数据通道
更多推荐



所有评论(0)