无法使用Python将文本以UTF-8编码写入文件

1 投票
2 回答
2911 浏览
提问于 2025-04-17 03:31

我正在做一个程序,这个程序可以读取一个下载下来的网页(存储为'something.html'),然后对它进行解析。不过,我在处理编码和解码时遇到了一些麻烦。我了解到大多数网页都是用ISO-8859-1编码的,我检查了这个页面的响应,确实得到了这个字符集:

>>> print r.info()
Content-Type: text/html; charset=ISO-8859-1
Connection: close
Cache-Control: no-cache
Date: Sun, 20 Feb 2011 15:16:31 GMT
Server: Apache/2.0.40 (Red Hat Linux)
X-Accel-Cache-Control: no-cache

但是,在这个页面的meta标签中,它声明使用'utf-8'作为编码:

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

所以在Python中,我尝试了很多方法来读取这些页面,解析它们,并以utf-8格式写入,包括正常读取文件和正常写入:

with open('../results/1.html','r') as f:                                   
    page = f.read()
...
with open('../parsed.txt','w') as f:
    for key in fieldD:
        f.write(key+'\t'+fieldD[key]+'\n')

我还尝试明确告诉文件在读取和写入过程中使用哪种编码:

with codecs.open('../results/1.html','r','utf-8') as f:                                
    page = f.read()
...
with codecs.open('../parsed.txt','w','utf-8') as f:                                  
    for key in fieldD:
        f.write(key+'\t'+fieldD[key]+'\n')

明确告诉文件从'iso-8859-1'读取,并写入'utf-8':

with codecs.open('../results/1.html','r','iso_8859_1') as f:
    page = f.read()
...
with codecs.open('../parsed.txt','w','utf-8') as f:                        
    for key in fieldD:
        f.write(key+'\t'+fieldD[key]+'\n')

我还尝试了这些想法的各种组合,包括以utf-16写入,在将每个字符串添加到字典之前单独编码它们,以及其他一些错误的想法。我不太确定这里的最佳方法是什么。似乎我在不使用任何编码的情况下运气最好,因为这样至少会让一些文本编辑器正确显示结果(比如emacs和textwrangler)。

我在这里读过几篇关于这个话题的帖子,但还是搞不清楚到底发生了什么。

谢谢。

2 个回答

1
>>> url = 'http://213.97.164.119/ABSYS/abwebp.cgi/X5104/ID31295/G0?ACC=DCT1'
>>> data = urllib2.urlopen(url).read()[4016:4052]; data
'Iglesia+Cat%f3lica">Iglesia Cat\xf3lica'

>>> data.decode('latin-1')
u'Iglesia+Cat%f3lica">Iglesia Cat\xf3lica'

>>> data.decode('latin-1').encode('utf-8')
'Iglesia+Cat%f3lica">Iglesia Cat\xc3\xb3lica'

你会得到什么?

2

我按照你的指示操作了。显示的页面并不是用UTF-8编码的;用UTF-8解码会失败。根据我偶尔使用的一个字符集检测工具,它是用一种基于拉丁字母的编码……可能是ISO-8859-1、cp1252或ISO-8859-15,而语言看起来是“es”(西班牙语)或“fr”(法语)。我看了一下,觉得是西班牙语。Firefox(查看 >>> 查看编码)显示它是ISO-8859-1。

所以现在你需要做的是尝试找出哪些工具可以正确显示你保存的文件。如果找不到合适的工具,你需要把文件转码为UTF-8,也就是用data.decode('ISO-8859-1').encode('UTF-8'),然后找一个可以正确显示UTF-8的工具。这应该不难。Firefox几乎可以正确识别我给它的任何编码并显示出来。

更新:应请求提供“直觉”:

在你的第三个代码块中,你只包含了输入和输出,中间用“...”隔开。输入代码应该能正常生成unicode对象。然而在输出代码中,你使用了str函数(为什么???)。假设在“...”之后你仍然有unicode对象,应用str()会在你的系统默认编码是'ascii'(应该是这样)时引发异常,或者在默认编码是'utf8'(不应该是这样)时默默地搞坏你的数据。请发布(1)“...”的内容(2)执行import sys; print sys.getdefaultencoding()的结果(3)你在输出文件中看到的内容,而不是预期中的“ó”在“Iglesia Católica”中——是ó吗?(4)文件中的实际字节(使用print repr(the data))而不是预期中的“ó”。

解决了你在评论中说你看到Iglesia Cat√ɬ≥lica……注意显示的字符有四个,而不是预期的一个。这表明是UTF-8编码了两次。下一个难题是显示这些字符的工具,其中两个在ISO-8859-1和cp1252中没有映射。我尝试了旧的DOS代码页cp437和cp850,这在Windows的命令提示符窗口中仍然使用,但不适合。koi8r也不适合;它需要一个基于拉丁字母的字符集。嗯,macroman呢?太好了!!你把双重编码的内容发送到了Mac终端的标准输出。下面是演示。

>>> from unicodedata import name
>>> oacute = u"\xf3"
>>> print name(oacute)
LATIN SMALL LETTER O WITH ACUTE
>>> guff = oacute.encode('utf8').decode('latin1').encode('utf8')
>>> guff
'\xc3\x83\xc2\xb3'
>>> for c in guff.decode('macroman'):
...     print name(c)
...
SQUARE ROOT
LATIN CAPITAL LETTER E WITH ACUTE
NOT SIGN
GREATER-THAN OR EQUAL TO
>>>

检查保存的文件我也把网页保存到了一个文件中(还有一个包含*.jpg、css文件等的目录)——使用Firefox的“另存为”功能。试试这个,看看你保存的页面,发布结果。

>>> data = open('g0.htm', 'rb').read()
>>> uc = data.decode('utf8')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "c:\python27\lib\encodings\utf_8.py", line 16, in decode
    return codecs.utf_8_decode(input, errors, True)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xb7 in position 1130: invalid start byte
>>> pos = data.find("Iglesia Cat")
>>> data[pos:pos+20]
'Iglesia Cat\xf3lica</a>'
>>> # Looks like one of ISO-8859-1 and its cousins to me.

请仔细注意:如果你的文件是用UTF-8编码的,那么用UTF-8编解码器读取它会生成unicode。如果你在解析时没有搞坏数据,并且用UTF-8编解码器写入解析后的unicode,它就不会被双重编码。你需要仔细检查你的代码,看看有没有“str”(记得那个“拼写错误”吗?)、“unicode”、“encode”、“decode”、“utf”、“UTF”等。你是否调用了第三方库来进行解析?在写入输出文件之前,当你执行print repr(key), repr(field[key])时,你看到的是什么?

这变得有些繁琐。考虑把你的代码和保存的页面放到网上,让我们可以查看,而不是猜测。

32766.html:我刚意识到你就是那个因为在vfat文件系统上尝试写入太多文件而耗尽所有inode的人(或者类似的情况)。所以你并不是手动“另存为”。请发布你用来“保存”这些文件的代码。

撰写回答