完美的世界是没有乱码的,但是我们的世界是不完美的,乱码问题,你总有一天会遇到。

之前解决了一个 Python 的 UnicodeEncodeError 问题,比较具有代表性,特此分享一下,希望可以帮到遇到此类问题的朋友。

通常情况下,Linux 默认使用的字符编码是 utf-8,windows 是 gbk,不跨操作系统的情况下,我们按此编码存取数据,文件和文件名均不会出现乱码问题。

但现实就是要跨系统传输文件。如果文件名都是英文,也不会出现乱码问题,因为英文都是 ascii 编码,而所有的编码都是包含 ascii 码的,谁让人家先发明了计算机呢。

然而,现实就是需要跨系统传输中文名称的文件。

修改文件名的编码

乱码的原因就是编码不一样,比如说 Linux 一个中文名称的文件,“中文.txt",实际上保存在磁盘上时,对其做了 utf-8 的编码,实际保存的就是字节:"中文.txt".encode('utf-8'),这段字节传输到 Windows 机器上时不会改变,当你打开目录查看时,Windows 会按照 gbk 进行解码,就是 "中文.txt".encode('utf-8').decode('gbk'),编码和解码用到的字符集不一样,自然会乱码。

解决办法就是保存文件时,修改文件名的编码,怎么修改?

先来看下 Python 内建的 open 函数签名:

遇到乱码问题,如何解决?_linux

这里的 encoding 参数是指定文件内容的字符编码,而不是文件名的编码,因此我们需要关注 file 这个参数,file 是一个像路径一样的对象,点击 ​​path-like object​​ 可以看到说明:

遇到乱码问题,如何解决?_乱码_02

也就是说,file 可以是一个字符串,也可以是字节串,那就好办了,假如要在 Linux 环境保存一个文件名是 gbk 编码的文件,可以这样做:

file_name = "中文.txt"

with open(file_name.encode('gbk'), "w",encoding = 'your file content encoding') as write:
# do something

这个文件在 Linux 下的文件名看起来是乱码,但传输到 Windows 就是正常显示的。

ftplib 传输的编码问题

我在数仓做数据交换的时候,通常要 Linux 和 Windows 互相传文件,为此专门写过一个通用的传输文件库 transferfile[1],就遇到了两个编码问题:

1、Linux 向 Windows 传输的文件中文乱码。

解决方法:ftplib 考虑到了这一点,在 ftplib.FTP 初始化后可以传入 encoding 参数,来指定目标系统以何种编码保存文件名称。如下图所示:

遇到乱码问题,如何解决?_java_03

2、Windows 下有乱码路径时无法在 Windows 里面递归的创建目标路径。

传输文件时可以指定目标路径,路径不存在时需要递归创建,以便存放上传的文件,ftplib 本身只能创建一个目录,需要自行写递归创建,那么创建之前就要先判断目录是否存在,这就需要用到 ftp.retrlines('LIST'),能列出来的就是存在的。

如果目录内没有乱码文件,也不会有问题,有就会报 UnicodeEncodeError,如下图:

遇到乱码问题,如何解决?_linux_04

怎么解决呢?

那就是不能让乱码目录影响了我们的主程序,在读取列表时遇到乱码忽略即可,按照 traceback 修改标准库 ftplib 文件 471 行,传入参数 errors='ignore' 如下图所示

遇到乱码问题,如何解决?_linux_05

标准库为什么不直接加上呢?我猜测就是为了让你知道,这里存在乱码,要忽略的话,自己搞定,我不为你背锅,????。

最后

本文介绍了乱码问题的原因,如何修改文件名称的编码,用 ftplib 遇到的编码问题如何解决,虽然场景具体,但解决的乱码问题的思路都是一样的,那就是让编码解码使用的字符编码保持一致,如果乱码不影响可以忽略掉不能解码的数据。

「二、字符串编码」

感谢阅读。