<p>Excel在打开<code>.csv</code>文件时采用Windows编码。这种编码取决于语言/国家,但在英语和西欧国家,它是<a href="https://en.wikipedia.org/wiki/Windows-1252" rel="nofollow noreferrer">^{<cd2>}</a>,它非常类似于{a2}(也称为“拉丁语1”)。在</p>
<p>这种编码对每个字符使用一个字节。这意味着它最多允许256个不同的字符(事实上,它们少于256个,因为有些代码是为控制字符和不可打印字符保留的)。在</p>
<p>Python3使用<a href="https://en.wikipedia.org/wiki/Unicode" rel="nofollow noreferrer">Unicode</a>来表示字符串。Unicode没有“只有256”符号的限制,因为它内部使用大约20位。实际上,Unicode可以表示世界上任何语言的任何字符(甚至是世界上一些语言的字符)。在</p>
<p>问题是,当Unicode必须写入文件(或通过网络传输)时,它必须作为字节序列进行“编码”。实现这一点的方法之一是“<a href="https://en.wikipedia.org/wiki/UTF-8" rel="nofollow noreferrer">UTF-8</a>”。在</p>
<p>UTF-8编码使用每个字符的可变字节数。它被设计为与ASCII兼容,因此ASCII表中的任何符号都用一个字节表示(这与它的ASCII代码一致)。但任何非ascii字符都需要超过1个字节来表示。特别是,字符<code>±</code>(codepoint<code>U+00B1</code>或177)在用UTF-8编码时,需要两个字节的十六进制值<code>c2</code>和{<cd6>}。在</p>
<p>当Excel读取这些字节时,由于它采用cp-1252编码,每个字符使用一个字节,所以它将序列<code>c2</code>,<code>b1</code>解码为两个单独的字符。第一个被解码为<code>Â</code>,第二个被解码为<code>±</code>。在</p>
<blockquote>
<p><strong>Note</strong> Incidentally, unicode <code>ñ</code> (codepoint <code>U+00F1</code>, or 241) is encoded in UTF-8 also as two bytes, of values <code>c3</code>, <code>b1</code>, which when decoded as cp-1252 are shown as <code>ñ</code>. Note that the first one is now <code>Ã</code> instead of <code>Â</code>, but the second one is again (casually again) <code>±</code>.</p>
</blockquote>
<p>当写入解决方案1252时,应使用cp-2编码:</p>
<pre><code>df.to_csv("file.csv", encoding="cp1252")
</code></pre>
<p>当然,这有一个潜在的问题。由于“cp-1252”最多只能表示256个符号,而Unicode可以表示超过1M个符号,因此可能会发生数据帧中的某些字符串数据使用“cp-1252”中无法表示的任何字符。在这种情况下,您将得到一个编码错误。在</p>
<p>另外,当用Pandas读回这个<code>.csv</code>时,必须指定编码,因为Pandas假定它是UTF-8。在</p>
<h2>关于<code>utf-8-sig</code>的更新</h2>
<p>其他答案和一些注释引用<code>"utf-8-sig"</code>编码,这将是另一个有效的(也许更可取)解决方案。我来详细说明一下这是什么。在</p>
<p>UTF8并不是将Unicode转换为字节序列的唯一方法,尽管它是一些标准中推荐的方法。另一个流行的选择是(曾经?)<strong>UTF-16</strong>。在这种编码中,所有Unicode字符都被编码为16位值(其中一些字符不能用这种方式表示,但是可以通过对某些字符使用<em>两个</em>16位值来扩展集合)。在</p>
<p>每个字符使用16位而不是8位的问题是,那么<em>结束符</em>是相关的。因为在网络内存中,16位或16位都是发送给磁盘的,实际上是在网络内存单元中写16位或不是。这些字节的发送顺序取决于体系结构。例如,假设您需要在磁盘中写入16位数字<code>66ff</code>(以十六进制表示)。你必须把它分成<code>66</code>和{<cd16>},然后决定哪个先写。磁盘中的序列可以是<code>66</code>,<code>ff</code>(这称为<em>big-endian</em>顺序),或者<code>ff</code>,<code>66</code>(这称为<em>小端顺序</em>)。在</p>
<p>如果您在一个<em>little-endian</em>架构中,比如Intel,那么磁盘中字节的默认顺序将与<em>big-endian</em>架构中的不同。当然,问题是当您试图在一台架构与创建文件的机器不同的机器中读取文件时。您可能最终错误地将这些字节组合为<code>ff66</code>,w这将是一个不同的Unicode字符。在</p>
<p>因此,必须以某种方式在文件中包含有关创建时使用的<em>endianity</em>的信息。这就是所谓的BOM(字节顺序标记)的作用。它由Unicode字符<code>FEFF</code>组成。如果这个字符是作为文件中的第一个字符写入的,当文件被读回时,如果您的软件发现<code>FEFF</code>作为第一个字符,它将知道用于读取文件的<em>endianity</em>与写入文件时使用的相同。但是,如果它找到<code>FFFE</code>(顺序被交换),它将知道存在一个<em>endianity</em>不匹配,然后在读取时交换每对字节,以获得正确的Unicode字符。在</p>
<p>顺便说一句,Unicode标准的<em>没有代码为<code>FFFE</code>的字符,以避免读取BOM时出现混淆。如果在开头找到<code>FFFE</code>,则表示结尾错误,必须交换字节。在</p>
<p>这些都与UTF-8无关,因为这种编码使用字节(而不是16位)作为信息的基本单位,因此它不受端接性问题的影响。但是,您可以用UTF-8编码FEFF(它将产生一个3字节的序列,值为<code>EF</code>,<code>BB</code>,和<code>BF</code>),并将其作为文件中的第一个字符写入。当您指定<code>utf-8-sig</code>编码时,Python就是这样做的。在</p>
<p>在这种情况下,它的目的不是帮助确定endianity,而是充当一种“指纹”,帮助读回文件的软件猜测使用的编码是UTF-8。如果软件在文件的前3个字节中发现了“魔力值”<code>EF</code>、<code>BB</code>、和{<cd29>},那么它可以断定该文件存储在UTF-8中。这三个字节被丢弃,其余的从UTF-8解码。在</p>
<p>在微软的软件中,尤其是在微软的大多数软件中。显然,对于Excel来说,这也是有效的,因此,总结一下:</p>
<ul>
<li>您可以使用<code>df.to_csv("file.csv", encoding="utf-8-sig")</code>编写csv</li>
<li>Excel读取文件并在开始处找到<code>EF</code>、<code>BB</code>、<code>BF</code>。因此它丢弃这些字节,并假定文件的其余部分使用utf-8。在</li>
<li>当稍后压缩<code>c2</code>,<code>b1</code>出现在文件中时,它被正确地解码为UTF-8以产生<code>±</code></li>
</ul>
<p>这样做的好处是可以在任何Windows计算机上工作,不管它使用的是哪种代码页(cp1252适用于西欧,其他国家也可以使用其他代码页,但Unicode和UTF-8是通用的)。在</p>
<p>潜在的问题是如果您试图在非windows计算机中读取这个csv。第一个“魔法字节”<code>EF</code>、<code>BB</code>、<code>BF</code>对于读取它的软件来说是没有意义的。然后,可能会在文件的开头以“伪”字符结尾,这可能会导致问题。如果读取文件的软件采用UTF-8编码,这三个前字节将被解码为Unicode字符<code>FFFE</code>,但它们不会被丢弃</em>。此字符是不可见的,宽度为零,因此无法使用任何编辑器“查看”它,但它仍将存在。如果读取文件的软件采用任何其他编码方式,例如“latin1”,那么前三个字节将被错误地解码为<code></code>,并且它们将在文件的开头可见。在</p>
<p>如果使用python读回该文件,则必须再次指定<code>utf-8-sig</code>编码,以使python丢弃这三个初始字节。在</p>