Python中文字符编码错误

4
我是一名初学者,正在尝试在Python 2.7中解码几十个带有数字和(简体)中文字符的CSV文件为UTF-8,但遇到了困难。
我不知道输入文件的编码,因此我尝试了我所知道的所有可能的编码-GB18030、UTF-7、UTF-8、UTF-16和UTF-32(LE和BE)。此外,为了保险起见,还尝试了GBK和GB3212,尽管这些应该是GB18030的子集。UTF编码都会在第一个中文字符处停止运行。其他编码在第一行的某个位置停止,除了GB18030。我认为它会是解决方案,因为它可以读取前几个文件并正确解码它们。我的部分代码,逐行读取,如下所示:
line = line.decode("GB18030")

我尝试解码的前两个文件都很顺利。到了第三个文件的中途,Python就会报错。

UnicodeDecodeError: 'gb18030' codec can't decode bytes in position 168-169: illegal multibyte sequence

在这个文件中,大约有100万行中有5个这样的错误。
我打开了输入文件,并检查哪些字符导致解码错误。前几个字符在CSV文件的特定列中都有欧元符号。我相当自信这些是打字错误,所以我想删除欧元符号。我想逐个检查编码错误的类型;我想先去掉所有欧元错误,但不想忽略其他错误,直到我先查看它们。
编辑:我使用了chardet,它给出GB2312作为所有文件的编码,并给出.99的置信度。我尝试使用GB2312解码,结果如下:
UnicodeDecodeError: 'gb2312' codec can't decode bytes in position 108-109: illegal multibyte sequence

你能问一下这个 csv 文件的来源它是用什么编码方式吗? - Daenyth
当您在文本编辑器中打开文件时,所有字符是否正确显示?您的文本编辑器认为它是什么编码? - Mark Tolonen
3个回答

9
"""...GB18030。我认为这将是解决方案,因为它能够读取前几个文件并正确解码。"""--请解释一下你的意思。对我来说,成功解码有两个标准:首先是raw_bytes.decode('some_encoding')不会失败,其次是当显示结果时,产生的Unicode在特定语言中是有意义的。使用latin1也就是iso_8859_1解码,宇宙中的每个文件都将通过第一个测试。大多数使用中文、日文和韩文的文件可以通过使用gb18030进行第一个测试,因为中文、日文和韩文中经常使用的字符大多使用相同的双字节序列块进行编码。你已经完成了第二个测试的多少部分呢?
不要在IDE或文本编辑器中查看数据。在Web浏览器中查看数据;它们通常更好地检测编码。
你怎么知道它是欧元符号?通过查看解码原始字节的文本编辑器屏幕上的内容,使用的是什么编码?cp1252?
你怎么知道它包含中文字符?你确定它不是日文或韩文吗?你从哪里得到的?
在香港、台湾、澳门和其他离岸地区创建的中文文件使用big5big5_hkscs编码--尝试一下。
无论如何,采纳Mark的建议,将chardet指向它;如果文件足够大且正确编码为中文、日文或韩文,则chardet通常能够很好地检测所使用的编码--但是,如果有人在文本编辑器中手动编辑文件并使用单字节字符集,那么几个非法字符可能导致未检测到用于其他99.9%字符的编码。
你可以尝试对来自文件的5行数据执行print repr(line),并将输出编辑到你的问题中。
如果文件不保密,你可以提供下载。
文件是在Windows上创建的吗?你如何在Python中读取它?(显示代码)
在OP评论之后进行更新:
记事本等不会尝试猜测编码;"ANSI"是默认值。你必须告诉它该怎么做。你所谓的欧元符号是原始字节"\x80",由你的编辑器使用你的环境的默认编码解码--通常是"cp1252"。不要使用这样的编辑器编辑你的文件。
早些时候,你谈到了“前几个错误”。现在你说你总共有5个错误。请解释一下。
如果文件确实几乎正确为gb18030,则应该能够逐行解码文件,当出现此类错误时,请捕获它,打印错误消息,从消息中提取字节偏移量,打印repr(two_bad_bytes),然后继续进行。我非常想知道这两个字节中的哪一个字节出现了\x80。如果根本没有出现,则“欧元符号”不是您问题的一部分。请注意,\x80可以有效地出现在gb18030文件中,但只能作为以\x81\xfe开头的2字节序列的第2个字节。

在尝试修复问题之前,最好知道您的问题是什么。在“ANSI”模式下使用记事本等来猛击它并不是一个好主意。

您一直对如何确定gb18030解码结果有所保留。特别是,我将密切关注gbk失败但gb18030“正常工作”的行 - 其中必须存在一些极为罕见的中文字符,或者可能是一些非中文非ASCII字符...

以下是一种更好的检查方法:使用raw_bytes.decode(encoding, 'replace')解码每个文件,并将结果(以utf8编码)写入另一个文件。通过result.count(u'\ufffd')计算错误。使用您用于确定gb18030解码结果的任何内容查看输出文件。 U+FFFD字符应显示为黑色钻石内的白色问号。

如果您决定可以丢弃不可解码的部分,则最简单的方法是raw_bytes.decode(encoding, 'ignore')

进一步信息后更新

所有这些\\都令人困惑。似乎“获取字节”涉及repr(repr(bytes))而不是只有repr(bytes)...在交互式提示符下,执行bytes(您将获得一个隐式的repr()),或print repr(bytes)(它不会获得隐式的repr())

空格:我假设您的意思是'\xf8\xf8'.decode('gb18030')被您解释为某种全角空格,并且解释是通过使用某个无法命名的查看器软件进行视觉检查完成的。是这样吗?

实际上,'\xf8\xf8'.decode('gb18030') -> u'\e28b'。U+E28B位于Unicode PUA(专用区域)中。 “空格”可能意味着查看器软件在使用的字体中不会有U+E28B的字形。

也许文件的来源故意使用PUA来表示标准gb18030中不存在的字符、注释或传输伪秘密信息。如果是这样,您将需要使用解码tambourine,这是最近俄罗斯研究的一个分支,详情请参见此处报告。
另一种方法是cp939-HKSCS理论。根据香港政府的说法,HKSCS big5代码FE57曾经映射到U+E28B,但现在映射到U+28804。
关于“欧元符号”:您说:“由于数据我不能分享整行,但我称之为欧元符号的字符是:\xcb\xbe\x80\x80”[我假设\被省略了,而"是字面意思]。当“欧元符号”出现时,总是在我不需要的同一列中,所以我希望只使用“忽略”。不幸的是,由于“欧元符号”紧挨着文件中的引号,有时“忽略”会同时去除欧元符号和引号,这对csv模块确定列造成问题。
如果您能展示这些\x80字节与引号和中文字符的关系模式,将会极大地帮助我们。请保持可读性,仅显示十六进制,并使用C1 C2来代表“我确定表示一个中文字符的两个字节”。例如:
C1 C2 C1 C2 cb be 80 80 22 # `\x22` is the quote character

请提供以下两种情况的示例:(1) 在使用“replace”或“ignore”时,“"”未丢失;(2) 引号丢失的情况。在您迄今为止的唯一示例中,“"”未丢失:
>>> '\xcb\xbe\x80\x80\x22'.decode('gb18030', 'ignore')
u'\u53f8"'

而且,发送一些调试代码的提议(请参见下面的示例输出)仍然有效。

>>> import decode_debug as de
>>> def logger(s):
...    sys.stderr.write('*** ' + s + '\n')
...
>>> import sys
>>> de.decode_debug('\xcb\xbe\x80\x80\x22', 'gb18030', 'replace', logger)
*** input[2:5] ('\x80\x80"') doesn't start with a plausible code sequence
*** input[3:5] ('\x80"') doesn't start with a plausible code sequence
u'\u53f8\ufffd\ufffd"'
>>> de.decode_debug('\xcb\xbe\x80\x80\x22', 'gb18030', 'ignore', logger)
*** input[2:5] ('\x80\x80"') doesn't start with a plausible code sequence
*** input[3:5] ('\x80"') doesn't start with a plausible code sequence
u'\u53f8"'
>>>

Eureka: -- 有时会丢失引号的可能原因 --

似乎在 gb18030 解码器的替换/忽略机制中存在一个 bug: \x80 不是有效的 gb18030 前导字节;当检测到它时,解码器应该尝试与下一个字节重新同步。然而,它似乎忽略了 \x80 和随后的字节:

>>> '\x80abcd'.decode('gb18030', 'replace')
u'\ufffdbcd' # the 'a' is lost
>>> de.decode_debug('\x80abcd', 'gb18030', 'replace', logger)
*** input[0:4] ('\x80abc') doesn't start with a plausible code sequence
u'\ufffdabcd'
>>> '\x80\x80abcd'.decode('gb18030', 'replace')
u'\ufffdabcd' # the second '\x80' is lost
>>> de.decode_debug('\x80\x80abcd', 'gb18030', 'replace', logger)
*** input[0:4] ('\x80\x80ab') doesn't start with a plausible code sequence
*** input[1:5] ('\x80abc') doesn't start with a plausible code sequence
u'\ufffd\ufffdabcd'
>>>

感谢您的建议。1.) 在两个方面上,它都被成功解码了。至少前两个文件是这样的。在第三个文件中,它在被解码的内容得到纠正的意义上是成功的。但对于带有欧元符号的行,它会抛出一个错误。2.) Firefox没有正确识别编码。3.) 我不确定文本编辑器中正在读取哪种解码方式--在记事本/Notepad++中,它只显示ANSI;根据我所读的,这似乎很奇怪/不正确。4.) 这段文本来自中国大陆,没有文档,并且确实是机密的。 - rallen
@Michael Madsen:是的,我应该说“猜测不太准确”。顺便说一下,在Windows上,ANSI表示“系统字符集”,因此它是一个可移动的盛宴;cp1252是通常的嫌疑人,但也可能是cp949(韩语)。 - John Machin
@John:由于数据的原因,我无法分享整行内容,但我所称之为欧元符号的字符在这里:\xcb\xbe\x80\x80"。当“欧元符号”出现时,它总是在我不需要的同一列中,所以我希望只使用“忽略”。不幸的是,由于“欧元符号”紧挨着文件中的引号,有时“忽略”会同时去掉欧元符号和引号,这对于csv模块确定列造成了问题。而“空格”是\xf8\xf8。很抱歉让你等了这么久——我刚刚想到如何获取字节。 - rallen
@John:'\xf8\xf8'.decode('gb18030') 对我确实有效。你曾经问过 gb18030 解码而 gbk 无法解码的字符是什么,如果我没记错,\xf8\xf8 就是唯一的例子。我明天才能检查例子,但我相信当只有一个 \x80 时,“"”会丢失——例如 C1 C2 80 22 -> C1 C2 当我使用“ignore”时,这与你的“Eureka”一致。 - rallen
@John:是的,谢谢你澄清——我之前误读为\x81 & \xfe而不是\x81到\xfe,这导致了(2)带来的问题,所以我删除了我的回答。 - rallen
显示剩余5条评论

0

试试这个:

codecs.open(file, encoding='gb18030', errors='replace')

不要忘记参数errors,你也可以将其设置为'ignore'

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接