在使用Python的ftplib库进行FTP服务器操作时,文件名包含中文、日文等非ASCII字符时,很容易出现乱码、文件无法找到或者操作失败的问题,这类问题的核心原因是FTP协议本身没有统一规定文件名的编码格式,不同服务器可能采用GBK、UTF-8等不同的编码方式,而Python的ftplib默认使用latin-1编码处理返回的文件名,就会出现编码不匹配的情况。

问题产生的原因
FTP协议在传输文件名时,并不会在协议头中声明使用的编码格式,大部分Windows环境的FTP服务器默认使用GBK编码存储文件名,而Linux环境的服务器多使用UTF-8编码,Python的ftplib库中,ftplib.FTP类在处理服务器返回的文件名列表时,默认使用latin-1编码解码字节流,当服务器实际编码和latin-1不一致时,就会产生乱码或者UnicodeDecodeError错误。
常见解决方案
1. 手动指定编码转换
如果明确知道FTP服务器使用的编码格式,可以在获取到文件名之后,先按latin-1编码转回字节流,再用服务器对应的编码解码。以下是列出目录文件的示例:
from ftplib import FTP
# 连接FTP服务器,假设服务器使用GBK编码
ftp = FTP()
ftp.connect("192.168.0.1", 21)
ftp.login("username", "password")
# 获取原始文件名列表,ftplib返回的是latin-1解码后的字符串
raw_file_list = []
ftp.retrlines("LIST", raw_file_list.append)
# 转换文件名编码,假设服务器用GBK编码
for item in raw_file_list:
# 先按latin-1转回字节,再用GBK解码
decoded_item = item.encode("latin-1").decode("gbk")
print(decoded_item)
ftp.quit()2. 重写ftplib的编码处理逻辑
如果需要频繁操作FTP,可以自定义一个继承ftplib.FTP的类,重写相关方法,统一处理编码问题,避免每次都手动转换。以下是适配GBK编码服务器的示例:
from ftplib import FTP
class CustomFTP(FTP):
def __init__(self, *args, **kwargs):
# 设置默认编码为GBK,可根据服务器实际情况修改
self.encoding = "gbk"
super().__init__(*args, **kwargs)
def retrlines(self, cmd, callback=None):
# 重写方法,使用自定义编码处理返回内容
if callback is None:
callback = print
resp = self.sendcmd(cmd)
if resp[:3] not in ("125", "150"):
raise Exception(resp)
self.file = self.makefile("r", encoding=self.encoding)
while True:
line = self.file.readline()
if not line:
break
if line[-2:] == "\r\n":
line = line[:-2]
elif line[-1:] == "\n":
line = line[:-1]
if callback:
callback(line)
self.file.close()
return self.getresp()
# 使用自定义类连接服务器
ftp = CustomFTP()
ftp.connect("192.168.0.1", 21)
ftp.login("username", "password")
ftp.retrlines("LIST")
ftp.quit()3. 适配多编码环境
如果不确定服务器使用的编码,可以尝试多种常见编码,捕获解码异常后切换编码,提高兼容性:
from ftplib import FTP
def decode_filename(raw_str, encodings=["utf-8", "gbk", "latin-1"]):
# 尝试多种编码解码文件名
for enc in encodings:
try:
return raw_str.encode("latin-1").decode(enc)
except UnicodeDecodeError:
continue
# 所有编码都失败时返回原始字符串
return raw_str
ftp = FTP()
ftp.connect("192.168.0.1", 21)
ftp.login("username", "password")
file_list = []
ftp.retrlines("LIST", file_list.append)
for item in file_list:
print(decode_filename(item))
ftp.quit()注意事项
上传文件时如果遇到文件名编码问题,需要将本地文件名按服务器编码转换后,再传递给FTP操作命令,例如上传名为中文的文件时,可先将文件名编码为服务器对应的字节流再传输。另外部分FTP服务器支持UTF-8编码,可以在登录后发送OPTS UTF8 ON命令尝试开启UTF-8模式,减少编码转换的工作量。
| 服务器环境 | 常见编码格式 | 适配建议 |
|---|---|---|
| Windows 自带FTP服务 | GBK | 使用GBK编码转换文件名 |
| Linux vsftpd 默认配置 | UTF-8 | 可尝试开启UTF-8模式或直接使用UTF-8解码 |
| 老旧FTP服务器 | GB2312 | 使用GB2312编码转换,兼容GBK编码 |