Excel 在打开 .csv 文件时采用 Windows 编码。这种编码取决于语言/国家,但在英语和西欧国家,它是cp-1252,与ISO-8859-1(也称为“latin1”)非常相似。
这种编码对每个字符使用一个字节。这意味着它最多允许 256 个不同的字符(实际上,它们少于 256 个,因为某些代码是为控制字符和不可打印字符保留的)。
Python3 使用Unicode 来表示字符串。 Unicode 没有“只有 256 个”符号的限制,因为它在内部使用约 20 位。在实践中,Unicode 可以表示世界上任何语言的任何字符(甚至是世界上的某些语言)。
问题在于,当必须将 Unicode 写入文件(或通过网络传输)时,必须将其“编码”为字节序列。执行此操作的方法之一以及许多领域的当前标准是“UTF-8”。
UTF-8 编码使用每个字符的可变字节数。它被设计为与 ASCII 兼容,因此 ASCII 表中的任何符号都用一个字节表示(与它的 ascii 代码一致)。但是任何不在 ascii 中的字符都需要超过 1 个字节来表示。特别是,字符 ±(代码点 U+00B1 或 177)在以 UTF-8 编码时,需要两个字节的十六进制值 c2 和 b1。
当 Excel 读取这些字节时,由于它假定 cp-1252 编码每个字符使用一个字节,它会将序列 c2、b1 解码为两个单独的字符。第一个解码为Â,第二个随便解码为±。
注意顺便说一句,unicode ñ(代码点 U+00F1,或 241)在 UTF-8 中也被编码为两个字节,值 c3,b1,解码时因为 cp-1252 显示为 ñ。请注意,第一个现在是Ã,而不是Â,但第二个又是(偶然又是)±。
解决办法是给pandas指明写文件的时候应该使用cp-1252编码:
df.to_csv("file.csv", encoding="cp1252")
当然,这有一个潜在的问题。由于“cp-1252”最多只能表示 256 个符号,而 Unicode 可以表示超过 1M 的符号,因此数据框中的某些字符串数据可能会使用“cp-1252”中无法表示的任何字符。在这种情况下,您将收到编码错误。
另外,当用 Pandas 读回这个 .csv 时,您必须指定编码,因为 Pandas 假定它是 UTF-8。
关于utf-8-sig的更新
其他答案和一些 cmets 指的是"utf-8-sig" 编码,这将是另一种有效(也许更可取)的解决方案。我会详细说明这是什么。
UTF8 并不是将 Unicode 转换为字节序列的唯一方法,尽管它是多个标准中推荐的方法。另一个流行的选择是(曾经?)UTF-16。在这种编码中,所有 Unicode 字符都被编码为 16 位值(其中一些无法以这种方式表示,但可以通过对某些字符使用 两个 16 位值来扩展该集合)。
每个字符使用 16 位而不是 8 位的问题在于 endianess 是相关的。由于 16 位不是内存、网络和磁盘运行的基本单位,因此当您向内存、网络或磁盘写入或发送 16 位值时,实际上发送了两个字节。发送这些字节的顺序取决于架构。例如,假设您需要在磁盘中写入 16 位数字66ff(以十六进制表示)。您必须将其“分解”为66 和ff,并决定哪个先写入。磁盘中的序列可以是66、ff(这称为大端顺序)或ff、66(这称为小端顺序)。
如果您使用 little-endian 架构,例如 Intel,则磁盘中字节的默认顺序将不同于 big-endian 架构.当然,问题是当您尝试在架构与创建文件的机器不同的机器中读取文件时。您最终可能会将这些字节错误地组合为 ff66,这将是一个不同的 Unicode 字符。
因此必须通过某种方式在文件中包含有关创建时使用的字节序的信息。这就是所谓的 BOM(字节顺序标记)的作用。它包含 Unicode 字符FEFF。如果这个字符被写为文件中的第一个字符,当文件被回读时,如果你的软件发现FEFF作为第一个字符,它就会知道用来读取文件的字节序是与编写时使用的相同。但是,如果它找到FFFE(交换顺序),它将知道存在 endianity 不匹配,然后它将在读取时交换每对字节,以获取正确的 Unicode 字符。
顺便说一下,Unicode 标准没有代码为FFFE 的字符,以避免在读取 BOM 时混淆。如果在开头找到FFFE,说明字节序不对,必须交换字节。
这些都与 UTF-8 无关,因为这种编码使用字节(而不是 16 位)作为信息的基本单位,因此它不受字节顺序问题的影响。不过,您可以将 FEFF 编码为 UTF-8(它将生成 3 个字节的序列,其值为 EF、BB 和 BF)并将其写入文件中的第一个字符。这就是 Python 在您指定 utf-8-sig 编码时所做的事情。
在这种情况下,它的目的不是帮助确定字节顺序,而是充当一种“指纹”,帮助读回文件的软件猜测使用的编码是 UTF-8。如果软件发现文件中的前 3 个字节为“魔术值”EF、BB 和 BF,则可以断定文件以 UTF-8 存储。这三个字节被丢弃,其余的从 UTF-8 解码。
特别是,Microsoft Windows 在其大部分软件中都使用了这种技术。显然,在 Excel 的情况下,这也有效,所以总结一下:
- 您使用
df.to_csv("file.csv", encoding="utf-8-sig") 编写您的csv
- Excel 读取文件并在开头找到
EF、BB、BF。因此它会丢弃这些字节,并为文件的其余部分假定 utf-8。
- 当后来序列
c2,b1出现在文件中时,正确解码为UTF-8生成±
这具有在任何 Windows 计算机上工作的优势,无论它使用什么代码页(cp1252 用于西欧,其他国家可以使用其他代码页,但 Unicode 和 UTF-8 是通用的)。
潜在的问题是如果您尝试在非 Windows 机器上读取此 csv。第一个“魔术字节”EF、BB、BF 可能对读取它的软件毫无意义。然后,您可能会在文件开头以“虚假”字符结尾,这可能会导致问题。如果读取文件的软件采用 UTF-8 编码,则这三个前字节将被解码为 Unicode 字符FFFE,但它们不会被丢弃。这个字符是不可见的并且宽度为零,因此使用任何编辑器都无法“看到”它,但它仍然存在。如果读取文件的软件采用任何其他编码,例如“latin1”,这三个前字节将被错误地解码为,并且它们将在文件开头可见。
如果你使用python读回这个文件,你必须再次指定utf-8-sig编码,让python丢弃这三个初始字节。