# 先说结论

以记事本格式打开 csv,文件 - 另存为,然后编码选择 "带有 BOM 的 UTF-8"。然后 csv 就可以正常显示中文了。

# 背景

# csv

逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,不含必须像二进制数字那样被解读的数据。CSV 文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。通常,所有记录都有完全相同的字段序列。通常都是纯文本文件。建议使用 WORDPAD 或是记事本来开启,再则先另存新档后用 EXCEL 开启,也是方法之一。

“CSV” 并不是一种单一的、定义明确的格式(尽管 RFC 4180 有一个被通常使用的定义)。因此在实践中,术语 “CSV” 泛指具有以下特征的任何文件:

  1. 纯文本,使用某个字符集,比如 ASCII、Unicode、EBCDIC 或 GB2312;
  2. 由记录组成(典型的是每行一条记录);
  3. 每条记录被分隔符分隔为字段(典型分隔符有逗号、分号或制表符;有时分隔符可以包括可选的空格);
  4. 每条记录都有同样的字段序列。

在这些常规的约束条件下,存在着许多 CSV 变体,故 CSV 文件并不完全互通。然而,这些变异非常小,并且有许多应用程序允许用户预览文件(这是可行的,因为它是纯文本),然后指定分隔符、转义规则等。如果一个特定 CSV 文件的变异过大,超出了特定接收程序的支持范围,那么可行的做法往往是人工检查并编辑文件,或通过简单的程序来修复问题。因此在实践中,CSV 文件还是非常方便的。

# bom

BOM(Byte Order Mark),字节顺序标记,出现在文本文件头部,Unicode 编码标准中用于标识文件是采用哪种格式的编码。

BOM —— Byte Order Mark,中文名译作 “字节顺序标记”。在这里找到一段关于 BOM 的说明:

在 UCS 编码中有一个叫做 “Zero Width No-Break Space” ,中文译名作 “零宽无间断间隔” 的字符,它的编码是 FEFF。而 FEFF 在 UCS 中是不存在的字符,所以不应该出现在实际传输中。UCS 规范建议我们在传输字节流前,先传输字符 “Zero Width No-Break Space”。这样如果接收者收到 FEFF,就表明这个字节流是 Big-Endian 的;如果收到 FFFE,就表明这个字节流是 Little- Endian 的。因此字符 “Zero Width No-Break Space” (“零宽无间断间隔”)又被称作 BOM。

UTF-8 不需要 BOM 来表明字节顺序,但可以用 BOM 来表明编码方式。字符 “Zero Width No-Break Space” 的 UTF-8 编码是 EF BB BF。所以如果接收者收到以 EF BB BF 开头的字节流,就知道这是 UTF-8 编码了。Windows 就是使用 BOM 来标记文本文件的编码方式的。

字符 U+FEFF 如果出现在字节流的开头,则用来标识该字节流的字节序,是高位在前还是低位在前。如果它出现在字节流的中间,则表达零宽度非换行空格的意义,用户看起来就是一个空格。从 Unicode3.2 开始,U+FEFF 只能出现在字节流的开头,只能用于标识字节序,就如它的名称 —— 字节序标记 —— 所表示的一样;除此以外的用法已被舍弃。取而代之的是,使用 U+2060 来表达零宽度无断空白。
类似 WINDOWS 自带的记事本等软件,在保存一个以 UTF-8 编码的文件时,会在文件开始的地方插入三个不可见的字符(0xEF 0xBB 0xBF,即 BOM)。它是一串隐藏的字符,用于让记事本等编辑器识别这个文件是否以 UTF-8 编码。对于一般的文件,这样并不会产生什么麻烦。但对于 PHP 来说,BOM 是个大麻烦。

PHP 并不会忽略 BOM,所以在读取、包含或者引用这些文件时,会把 BOM 作为该文件开头正文的一部分。根据嵌入式语言的特点,这串字符将被直接执行(显示)出来。由此造成即使页面的 top padding 设置为 0,也无法让整个网页紧贴浏览器顶部,因为在 html 一开头有这 3 个字符呢!

# 解决方法

使用程序将 txt 转化为 csv 时,在头部写入 \xEF\xBB\xBF ,即可解决。

# python 例程

def conv_datafile_to_csv(src:str):
    """将txt文件转化为csv文件。其中txt每行格式为str(dict())形式      
    
    :param src: path of .txt file     
    :return: none     
    """
    import ast
    dst = src.replace('.txt','.csv')
    with open(dst,'wb') as d:
        d.write(b'\xef\xbb\xbf')
    with open(dst,'a',encoding='utf8') as d:
        with open(src,'r',encoding='utf8') as s:
            for line in s:
                dic = ast.literal_eval(line)
                print(*dic.values(),sep=',',file=d)

# 参考资料

  • https://baike.baidu.com/item/CSV/10739
  • https://baike.baidu.com/item/BOM/2790364
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

c01dkit 微信支付

微信支付

c01dkit 支付宝

支付宝

c01dkit qqpay

qqpay