Python脚本输出到文件时出现Unicode错误

17
这是代码:

这是代码:

print '"' + title.decode('utf-8', errors='ignore') + '",' \
      ' "' + title.decode('utf-8', errors='ignore') + '", ' \
      '"' + desc.decode('utf-8', errors='ignore') + '")'

标题和描述由Beautiful Soup 3 (p[0].textp[0].prettify)返回,并且根据BeautifulSoup3文档,它们是UTF-8编码。请注意保留HTML标签。

如果我运行以下命令:

python.exe script.py > out.txt

我遇到了以下错误:
Traceback (most recent call last):
  File "script.py", line 70, in <module>
    '"' + desc.decode('utf-8', errors='ignore') + '")'
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf8' in position 264
: ordinal not in range(128)

然而,如果我运行
python.exe script.py

我没有收到任何错误信息。只有当指定输出文件时才会出现此问题。
如何在输出文件中获得良好的UTF-8数据?

1
你正在违反“不要重复自己”的原则,因为你调用了decode超过一次。事实上,你根本不应该调用它。只需设置标准输出的编码并完成即可。这个错误(Python的错误,而不是你的错误)是Python有这种非常烦人的行为,它将重定向的输出与未重定向的输出区别对待。 - tchrist
2
现在我并没有写完美的代码,我只是尝试从各种教程中掌握一些东西,直到找到可行的方法(我相信这就是巫术编码),然后再将其整理得简洁高效。这是我第一天使用Python,到目前为止我并不是很满意。 - Kaitnieks
2
通常情况下,您也不应该使用errors='ignore',它会隐藏代码中的错误。 - agf
主要是Delphi、PHP和Javascript,但也涉及其他语言。通常我看到有两种处理字符串的模式——它们内部是Unicode编码,在输入/输出时进行解码/编码,或者它们在内部是输入内容的字节表示,并且只有在必要时才进行转换。Python似乎同时做了这两件事情,根据其他评论,解码可能会发生或不发生,这取决于各种隐藏的因素。我还没有尝试完所有的选项(感谢SO),所以我相信解决方案会出现的。 - Kaitnieks
显示剩余7条评论
4个回答

12
你可以使用codecs模块将Unicode数据写入文件。
import codecs
file = codecs.open("out.txt", "w", "utf-8")
file.write(something)

'print' 命令会输出到标准输出,如果你的控制台不支持 utf-8 编码,即使将 stdout 导向文件,也可能导致此类错误。


有没有任何编解码器可以直接输出字节串而不尝试转换它们,比如“原始”或其他什么? - Kaitnieks
@Kaitnieks:这里是所有支持的编码列表http://docs.python.org/library/codecs.html#standard-encodings - Maksym Polshcha
这实际上是行得通的,一旦我将字符串转换为Unicode。我不得不(遗憾地)放弃使用 .prettify(),因为它返回字符串而不是Unicode字符串。谢谢。 - Kaitnieks
实际上,将字符串编码为utf-8并写入控制台可能会显示奇怪,但它不会导致错误,即使您将输出重定向到文件。只有当您尝试写出原始Unicode时,才会触发Python的自动转换,这将在转换为ASCII时失败。 - alexis

7
在这种情况下,Windows的行为有点复杂。你应该听取其他建议,在内部使用unicode来处理字符串,并在输入时进行解码。
对于你的问题,如果stdout被重定向,你需要打印编码后的字符串(只有你知道哪种编码!),但是在简单的屏幕输出情况下,你必须打印unicode字符串(Python或Windows控制台会处理转换到适当的编码)。
我建议按照以下方式组织你的脚本:
# -*- coding: utf-8 -*- 
import sys, codecs
# set up output encoding
if not sys.stdout.isatty():
    # here you can set encoding for your 'out.txt' file
    sys.stdout = codecs.getwriter('utf8')(sys.stdout)

# next, you will print all strings in unicode
print u"Unicode string ěščřžý"

更新:还可以查看其他类似的问题:Python中在管道输出时设置正确的编码


1
谢谢您!如果您想重定向到任何文件,它确实可以工作。 - petrosg

1

将文本转换为Unicode以打印毫无意义。在Unicode中处理数据,将其转换为某种编码进行输出。

您的代码实际上是这样做的:您使用的是Python 2,因此默认的字符串类型(str)是字节串。在语句中,您从一些UTF编码的字节串开始,将它们转换为Unicode,用引号括起来(常规的str被强制转换为Unicode以组合成一个字符串)。然后将此Unicode字符串传递给print,它会将其推送到sys.stdout。为此,它需要将其转换为字节。如果您要写入Windows控制台,则可以通过某种方式进行协商,但如果您重定向到常规的dumb文件,则会退回到ASCII并发出投诉,因为没有无损的方法可以这样做。

解决方案:不要给print一个Unicode字符串。自己“编码”为所选表示形式:

print "Latin-1:", "unicode über alles!".decode('utf-8').encode('latin-1')
print "Utf-8:", "unicode über alles!".decode('utf-8').encode('utf-8')
print "Windows:", "unicode über alles!".decode('utf-8').encode('cp1252')

重定向时,所有这些应该无需投诉即可正常工作。它可能不会在您的屏幕上显示正确,但是使用记事本或其他工具打开输出文件,查看编辑器是否设置为查看格式(Utf-8是唯一有希望被检测到的格式,cp1252是Windows默认格式)。

一旦完成,清理代码并避免使用print进行文件输出。使用codecs模块,并使用codecs.open而不是普通open打开文件。

PS:如果您正在解码utf-8字符串,则转换为unicode应该是无损的:您不需要errors=ignore标志。当您转换为ascii或Latin-2或其他目标代码页中不存在的字符并且您想要删除这些字符时才适用。


哇,这太糟糕了。在其他许多编程语言中,你永远不必做像这样的事情。你真的希望人们为每个输出语句调用两个函数吗?这是一场灾难!你非常非常地违反了“不要重复自己”的原则。你应该只需要设置输出的编码并忘记它。 - tchrist
其实你不必这么做。原帖作者只是在处理Unicode时搞砸了。只要稍微理解一下发生了什么,转换就可以限制在必要的范围内。而且在Python 3中,这个概念更加清晰明了。如果你想在sys.stdout上设置编码,也可以,但那是另一个问题。 - alexis
这正是我所想的,但我不确定他哪里出了问题。我只使用Python3工作,因为我觉得Python2中的Unicode处理太繁琐了。 - tchrist
基本上,将文本转换为Unicode以打印它是没有意义的。如果您有多语言文本,请在输入时将其“解码”为Unicode,在Unicode中进行所有处理,然后再次编码(为UTF-8或其他内容)以进行写出。 - alexis

1
问题: 如果您在Windows上运行:
python.exe script.py

以下内容将生效:

sys.stdout.encoding: utf-8
sys.stdout.isatty(): True

但是,如果你运行以下代码:
python.exe script.py > out.txt

你将有效地拥有这个:

你将有效地拥有这个:

sys.stdout.encoding: cp1252
sys.stdout.isatty(): False

所以,可能的解决方案(使用Python > 3.7):
import sys
if not sys.stdout.isatty():
    sys.stdout.reconfigure(encoding='utf-8')

print '"' + title.decode('utf-8', errors='ignore') + '",' \
      ' "' + title.decode('utf-8', errors='ignore') + '", ' \
      '"' + desc.decode('utf-8', errors='ignore') + '")'

参见: 如何在Python 3中设置sys.stdout编码?


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