导出csv文件转换时出现奇怪字符

2 投票
3 回答
9046 浏览
提问于 2025-04-15 20:24

我遇到了一个自己解决不了的问题,涉及到从Google搜索趋势下载的csv格式的数据文件。

我懒得手动重新格式化I4S给我的文件,这意味着我需要提取出实际的趋势数据部分,并重新排列列,以便我能在学校的建模程序中使用。

所以我写了一个小脚本,应该可以帮我完成这项工作:读取一个文件,做一些处理,然后给我一个格式正确的新文件。

这个脚本的功能是读取文件内容,提取趋势部分,按行分割,再把每一行分开,然后重新排列列,可能还会重新格式化它们。

当我查看一个未处理的I4S csv文件时,它看起来很正常,行与行之间有CR LF字符(可能这是因为我在用Windows)。

但是,当我用脚本读取内容并写入新文件时,CR和LF之间出现了奇怪的亚洲字符。我尝试用一个手动写的类似文件运行脚本,甚至还试过一个来自Google趋势的csv文件,结果都很好。

我使用的是Python,下面是我用来做这个示例的脚本(代码片段):

            # Read from an input file 
            file = open(file,"r") 
            contents = file.read() 
            file.close() 
            cfile = open("m.log","w+") 
            cfile.write(contents) 
            cfile.close()

有没有人知道为什么会出现这些字符???谢谢你的帮助!

我给你一个例子:

I4S csv文件的前几行:

Web Search Interest: foobar
Worldwide; 2004 - present

Interest over time
Week foobar
2004-01-04 - 2004-01-10 44
2004-01-11 - 2004-01-17 44
2004-01-18 - 2004-01-24 37
2004-01-25 - 2004-01-31 40
2004-02-01 - 2004-02-07 49
2004-02-08 - 2004-02-14 51
2004-02-15 - 2004-02-21 45
2004-02-22 - 2004-02-28 61
2004-02-29 - 2004-03-06 51
2004-03-07 - 2004-03-13 48
2004-03-14 - 2004-03-20 50
2004-03-21 - 2004-03-27 56
2004-03-28 - 2004-04-03 59

读取和写入内容后的输出文件:

Web Search Interest: foobar
਍圀漀爀氀搀眀椀搀攀㬀 ㈀  㐀 ⴀ 瀀爀攀猀攀渀琀ഀഀ

਍䤀渀琀攀爀攀猀琀 漀瘀攀爀 琀椀洀攀ഀഀ
Week foobar
਍㈀  㐀ⴀ ㄀ⴀ 㐀 ⴀ ㈀  㐀ⴀ ㄀ⴀ㄀ ऀ㐀㐀ഀഀ
2004-01-11 - 2004-01-17 44
਍㈀  㐀ⴀ ㄀ⴀ㄀㠀 ⴀ ㈀  㐀ⴀ ㄀ⴀ㈀㐀ऀ㌀㜀ഀഀ
2004-01-25 - 2004-01-31 40
਍㈀  㐀ⴀ ㈀ⴀ ㄀ ⴀ ㈀  㐀ⴀ ㈀ⴀ 㜀ऀ㐀㤀ഀഀ
2004-02-08 - 2004-02-14 51
਍㈀  㐀ⴀ ㈀ⴀ㄀㔀 ⴀ ㈀  㐀ⴀ ㈀ⴀ㈀㄀ऀ㐀㔀ഀഀ
2004-02-22 - 2004-02-28 61
਍㈀  㐀ⴀ ㈀ⴀ㈀㤀 ⴀ ㈀  㐀ⴀ ㌀ⴀ 㘀ऀ㔀㄀ഀഀ
2004-03-07 - 2004-03-13 48
਍㈀  㐀ⴀ ㌀ⴀ㄀㐀 ⴀ ㈀  㐀ⴀ ㌀ⴀ㈀ ऀ㔀 ഀഀ
2004-03-21 - 2004-03-27 56
਍㈀  㐀ⴀ ㌀ⴀ㈀㠀 ⴀ ㈀  㐀ⴀ 㐀ⴀ ㌀ऀ㔀㤀ഀഀ
2004-04-04 - 2004-04-10 69
਍㈀  㐀ⴀ 㐀ⴀ㄀㄀ ⴀ ㈀  㐀ⴀ 㐀ⴀ㄀㜀ऀ㘀㔀ഀഀ
2004-04-18 - 2004-04-24 51
਍㈀  㐀ⴀ 㐀ⴀ㈀㔀 ⴀ ㈀  㐀ⴀ 㔀ⴀ ㄀ऀ㔀㄀ഀഀ
2004-05-02 - 2004-05-08 56
਍㈀  㐀ⴀ 㔀ⴀ 㤀 ⴀ ㈀  㐀ⴀ 㔀ⴀ㄀㔀ऀ㔀㈀ഀഀ
2004-05-16 - 2004-05-22 54
਍㈀  㐀ⴀ 㔀ⴀ㈀㌀ ⴀ ㈀  㐀ⴀ 㔀ⴀ㈀㤀ऀ㔀㔀ഀഀ
2004-05-30 - 2004-06-05 74
਍㈀  㐀ⴀ 㘀ⴀ 㘀 ⴀ ㈀  㐀ⴀ 㘀ⴀ㄀㈀ऀ㔀㜀ഀഀ
2004-06-13 - 2004-06-19 50
਍㈀  㐀ⴀ 㘀ⴀ㈀  ⴀ ㈀  㐀ⴀ 㘀ⴀ㈀㘀ऀ㔀㐀ഀഀ
2004-06-27 - 2004-07-03 58
਍㈀  㐀ⴀ 㜀ⴀ 㐀 ⴀ ㈀  㐀ⴀ 㜀ⴀ㄀ ऀ㔀㤀ഀഀ
2004-07-11 - 2004-07-17 59
਍㈀  㐀ⴀ 㜀ⴀ㄀㠀 ⴀ ㈀  㐀ⴀ 㜀ⴀ㈀㐀ऀ㘀㈀ഀഀ

3 个回答

2

找到了解决办法:

这是一个字符编码的问题。根据你使用的编辑器,显示的字符集编码会有所不同:

Notepad++: ucs-2 小端

PSPad: utf-16le

用 ucs-2 解码内容没有成功,所以我试了 utf-16le,结果很好。虽然 extraneons 的回答是错的,但它让我找到了一个网站,在那里我学到了用 'U' 打开文件的方法会把 "\r\n" 也当作换行符来识别。所以我现在脚本中相关的代码片段看起来是这样的:

file = open(file,'rU')
contents = file.read()
file.close()

contents = contents.decode("utf-16le").encode("utf-8")

然后我用 utf-8 编码内容,并用下面的代码去掉所有空行:

lines = contents.split("\n")
contents = ""
for line in lines:
  if not line.strip():
    continue
  else:
    contents += line+"\n"

现在我可以继续拆分和重新格式化文件了。感谢 Nick Bastin,你给了我需要的提示!

3

这个问题是关于字符编码的,可能还和Python对换行符的支持有关。正如你提到的,源文件是用UCS-2 LE编码的,并且有一个字节顺序标记(BOM)。你需要做的事情类似于:

import codecs

input_file = codecs.open("Downloads/report.csv", "r", encoding="utf_16")
contents = input_file.read() 
input_file.close() 

cfile = codecs.open("m.log", "w+", encoding="utf_8")
cfile.write(contents) 
cfile.close()

这段代码会读取输入文件,正确解码,然后把内容以UTF-8格式写入新的文件。你需要先删除你现有的m.log文件。

5

repr() 是你的好帮手(在 Python 3.X 中,使用 ascii() 替代)。

prompt>\python26\python -c "print repr(open('report.csv','rb').read()[:300])"
'\xff\xfeW\x00e\x00b\x00 \x00S\x00e\x00a\x00r\x00c\x00h\x00 \x00I\x00n\x00t\x00e
\x00r\x00e\x00s\x00t\x00:\x00 \x00f\x00o\x00o\x00b\x00a\x00r\x00\r\x00\n\x00W\x0
[snip]
x001\x007\x00\t\x004\x004\x00\r\x00\n\x002\x000\x00'

我觉得前两个字节看起来像是 UTF-16LE 的 BOM(U+FEFF)。

记住,Notepad.* 不是你的好朋友。UTF-16 不应该被称为 "UCS-2" 或 "Unicode"。

接下来该怎么做,下面的内容会对你有帮助:

>>> import codecs
>>> lines = list(codecs.open('report.csv', 'r', encoding='UTF-16'))
>>> import pprint
>>> pprint.pprint(lines[:8])
[u'Web Search Interest: foobar\r\n',
 u'Worldwide; 2004 - present\r\n',
 u'\r\n',
 u'Interest over time\r\n',
 u'Week\tfoobar\r\n',
 u'2004-01-04 - 2004-01-10\t44\r\n',
 u'2004-01-11 - 2004-01-17\t44\r\n',
 u'2004-01-18 - 2004-01-24\t37\r\n']
>>>

更新: 为什么你的输出文件看起来像是一堆乱码。

首先,你用的可能是某个程序(比如 Notepad.*),它知道这些文件是用 UTF-16LE 编码的,所以显示得还不错。因此,你的输入文件看起来是正常的。

但是,你的脚本是把输入文件当作原始字节来读取的。然后,它以文本模式('w')写入输出文件,而不是以二进制模式('wb')。因为你在 Windows 系统上,每个 \n 都会被替换成 \r\n。这就给每一行增加了一个字节(相当于一个 UTF-16 字符的一半)。所以每第二行就会变得反向,也就是 UTF-16BE……字母 A 在 UTF-16LE 中是 \x41\x00,但它的后面的 \x00 会丢失,前面会从左边的字符那里得到一个字节(可能是 \x00)。\x00\x41 是一个 CJK(“亚洲”)字符的 UTF-16LE 表示。

推荐阅读:Python Unicode HOWTOJoel 的这篇文章

撰写回答