如何处理Python中FTP服务器上的非UTF-8编码文件
在使用Python的ftplib库操作FTP服务器时,我们经常会遇到文件编码不是UTF-8的情况,比如中文环境常见的GBK、GB2312编码。默认的ftplib在处理文本文件时不会自动做编码转换,直接读取可能会出现乱码或者编码错误,下面我们就来介绍几种常见的处理方式。
一、读取非UTF-8编码的文本文件
读取非UTF-8编码的文本文件时,核心思路是先以二进制模式获取文件内容,再按照对应的编码格式解码。使用ftplib的retrbinary方法可以以二进制形式下载文件内容,我们自定义一个回调函数保存二进制数据,之后再进行解码操作。
from ftplib import FTP
def read_ftp_file_with_encoding(ftp_host, ftp_user, ftp_pass, remote_file_path, encoding="gbk"):
"""
读取FTP服务器上指定编码的文本文件
:param ftp_host: FTP服务器地址
:param ftp_user: FTP用户名
:param ftp_pass: FTP密码
:param remote_file_path: 远程文件路径
:param encoding: 文件编码,默认gbk
:return: 解码后的文件内容字符串
"""
# 连接FTP服务器
ftp = FTP(ftp_host)
ftp.login(user=ftp_user, passwd=ftp_pass)
binary_data = []
# 二进制模式读取文件,回调函数中保存二进制数据
def handle_binary(data):
binary_data.append(data)
ftp.retrbinary(f"RETR {remote_file_path}", handle_binary)
# 拼接所有二进制数据并解码
content = b"".join(binary_data).decode(encoding)
ftp.quit()
return content
# 使用示例,假设FTP服务器地址是192.168.0.1,用户名密码为test/123456
if __name__ == "__main__":
try:
file_content = read_ftp_file_with_encoding(
ftp_host="192.168.0.1",
ftp_user="test",
ftp_pass="123456",
remote_file_path="/docs/test.txt",
encoding="gbk"
)
print("文件内容:", file_content)
except Exception as e:
print(f"读取文件失败:{e}")上面的代码中,我们没有使用默认的文本模式读取,而是通过二进制方式拿到原始字节流,再根据文件实际编码调用decode方法转换,这样就能正确读取GBK等其他编码的文本文件了。
二、上传非UTF-8编码的文本文件到FTP服务器
上传非UTF-8编码的文件时,需要先把要上传的文本按照目标编码转换成二进制,再用storbinary方法以二进制形式上传。如果直接上传字符串,可能会被默认按照UTF-8编码转换,导致服务器端文件编码错误。
from ftplib import FTP
def upload_ftp_file_with_encoding(ftp_host, ftp_user, ftp_pass, local_text, remote_file_path, encoding="gbk"):
"""
将文本按照指定编码上传到FTP服务器
:param ftp_host: FTP服务器地址
:param ftp_user: FTP用户名
:param ftp_pass: FTP密码
:param local_text: 要上传的文本内容
:param remote_file_path: 远程文件路径
:param encoding: 目标编码,默认gbk
"""
ftp = FTP(ftp_host)
ftp.login(user=ftp_user, passwd=ftp_pass)
# 按照指定编码将文本转换为二进制
binary_data = local_text.encode(encoding)
# 使用BytesIO将二进制数据转换为类文件对象,方便上传
from io import BytesIO
file_obj = BytesIO(binary_data)
ftp.storbinary(f"STOR {remote_file_path}", file_obj)
file_obj.close()
ftp.quit()
# 使用示例
if __name__ == "__main__":
try:
text_to_upload = "这是一段中文测试内容,需要以GBK编码上传到FTP服务器"
upload_ftp_file_with_encoding(
ftp_host="192.168.0.1",
ftp_user="test",
ftp_pass="123456",
local_text=text_to_upload,
remote_file_path="/docs/upload_test.txt",
encoding="gbk"
)
print("文件上传成功")
except Exception as e:
print(f"上传文件失败:{e}")这里需要注意的是,上传时如果文本中包含目标编码无法表示的字符,会抛出UnicodeEncodeError,实际使用时可以根据需求添加错误处理,比如忽略无法编码的字符或者替换成其他符号。
三、遍历FTP目录时处理非UTF-8编码的文件名
有时候FTP服务器上的文件名也不是UTF-8编码,使用nlst或者dir方法获取文件名时可能会出现乱码。这种情况我们可以先获取原始字节的文件名列表,再按照对应的编码解码。
from ftplib import FTP
def list_ftp_files_with_encoding(ftp_host, ftp_user, ftp_pass, remote_dir, encoding="gbk"):
"""
列出FTP指定目录下的文件,处理非UTF-8编码的文件名
:param ftp_host: FTP服务器地址
:param ftp_user: FTP用户名
:param ftp_pass: FTP密码
:param remote_dir: 远程目录路径
:param encoding: 文件名编码,默认gbk
:return: 解码后的文件名列表
"""
ftp = FTP(ftp_host)
ftp.login(user=ftp_user, passwd=ftp_pass)
# 切换到目标目录
ftp.cwd(remote_dir)
# 使用mlsd方法获取文件信息,部分服务器支持返回字节形式的文件名
# 如果服务器不支持mlsd,可以尝试使用nlst的原始字节处理
file_names = []
try:
# 尝试用mlsd获取,部分实现会返回原始字节
for name, facts in ftp.mlsd():
# 如果name是字节类型,按照指定编码解码
if isinstance(name, bytes):
decoded_name = name.decode(encoding)
else:
# 如果是字符串,可能已经自动解码,尝试重新编码再解码处理乱码
try:
decoded_name = name.encode("latin-1").decode(encoding)
except:
decoded_name = name
file_names.append(decoded_name)
except:
# 如果mlsd不支持,使用nlst的原始字节处理
import io
raw_data = io.BytesIO()
# 发送原始命令获取nlst的字节结果
ftp.retrlines("NLST", lambda line: raw_data.write(line.encode("latin-1") + b"\n"))
raw_bytes = raw_data.getvalue()
# 按照换行分割后解码
for line in raw_bytes.split(b"\n"):
line = line.strip()
if line:
file_names.append(line.decode(encoding))
ftp.quit()
return file_names
# 使用示例
if __name__ == "__main__":
try:
files = list_ftp_files_with_encoding(
ftp_host="192.168.0.1",
ftp_user="test",
ftp_pass="123456",
remote_dir="/docs",
encoding="gbk"
)
print("目录下的文件:", files)
except Exception as e:
print(f"列出文件失败:{e}")文件名编码的处理相对复杂,因为不同FTP服务器的实现可能有差异,上面的代码提供了两种常见的兼容方式,实际使用时可以根据服务器的情况调整编码和解码逻辑。
四、注意事项
- 操作前最好先确认FTP服务器上文件的实际编码,避免用错编码导致乱码或者解码错误,可以通过下载小文件用文本编辑器查看编码,或者咨询服务器管理员。
- 如果同时需要处理多种编码的文件,可以在代码中添加编码检测逻辑,比如使用
chardet库检测二进制数据的编码,再动态选择对应的解码方式。 - 操作完成后记得调用
ftp.quit()或者ftp.close()关闭FTP连接,避免占用服务器资源。