UnicodeEncodeError: 'ascii'编解码器无法在第20个位置编码字符u'\xa0',该位置的序数不在128范围内。

1495
我在处理从不同网页(不同网站)获取的文本时遇到了Unicode字符的问题。我正在使用BeautifulSoup。
问题在于错误并不总是可重现的;它有时可以与某些页面一起工作,有时会通过抛出 UnicodeEncodeError 而失败。我已经尝试了几乎所有我能想到的方法,但是我还没有找到任何可以在不抛出任何Unicode相关错误的情况下始终正常工作的东西。
以下是引起问题的代码部分之一:
agent_telno = agent.find('div', 'agent_contact_number')
agent_telno = '' if agent_telno is None else agent_telno.contents[0]
p.agent_info = str(agent_contact + ' ' + agent_telno).strip()

当运行上面的代码片段时,以下是某些字符串产生的堆栈跟踪:

Traceback (most recent call last):
  File "foobar.py", line 792, in <module>
    p.agent_info = str(agent_contact + ' ' + agent_telno).strip()
UnicodeEncodeError: 'ascii' codec can't encode character u'\xa0' in position 20: ordinal not in range(128)

我怀疑这是因为有些页面(或更具体地说,来自某些网站的页面)可能被编码,而其他页面可能没有编码。所有网站都位于英国,提供的数据面向英国市场 - 因此不存在与国际化或处理非英语文本相关的问题。

有没有人有任何想法如何解决这个问题,以便我可以一致地修复它?


1
如果您作为用户而不是开发人员遇到这些错误,请查看 https://serverfault.com/questions/54591/how-to-install-change-locale-on-debian 和 https://askubuntu.com/questions/599808/cannot-set-lc-ctype-to-default-locale-no-such-file-or-directory。 - That Brazilian Guy
4
尝试使用以下代码:import os; import locale; os.environ["PYTHONIOENCODING"] = "utf-8"; myLocale=locale.setlocale(category=locale.LC_ALL, locale="en_GB.UTF-8"); ... print(myText.encode('utf-8', errors='ignore')) - hhh
@hhh,我运行了你的代码片段,出现了NameError: name 'myText' is not defined的错误。 - KHAN irfan
27
在执行脚本之前,尝试在shell中设置PYTHONIOENCODING$ export PYTHONIOENCODING=utf8 - Noam Manos
在我的情况下,字符串是 - u'1d6f4975842f050bf6503b19250d09f997b34f4a\n',我只是在同一字符串上使用了.encode('utf-8').strip()。它的作用是从字符串中删除最后一个\n,这之前会出现问题,即使之前使用了encode('utf-8')。 - Indrajeet Gour
34个回答

1518

请阅读 Python Unicode HOWTO。这个错误是第一个例子

不要使用str()将unicode转换为编码文本或字节。

相反,请使用.encode()来编码字符串:

p.agent_info = u' '.join((agent_contact, agent_telno)).encode('utf-8').strip()

或者完全使用Unicode进行工作。


32
同意!我学到的一个好的经验法则是使用“Unicode 三明治”思想。你的脚本接受来自外部世界的字节,但所有处理都应在 Unicode 中完成。只有当你准备输出数据时,它才应该被压回成字节! - Andbdrew
274
如果有人对此感到困惑,我发现了一件奇怪的事情:我的终端使用 UTF-8,当我打印我的 UTF-8 字符串时,它运行得很好。但是当我将程序的输出导向一个文件时,它会抛出一个“UnicodeEncodeError”错误。实际上,当输出被重定向(到文件或管道)时,我发现“sys.stdout.encoding”为“None”!在字符串后面添加“.encode('utf-8')”可以解决这个问题。 - drevicko
108
@drevicko:使用 PYTHONIOENCODING=utf-8,即打印Unicode字符串并让环境设置预期编码。 - jfs
2
@steinar:没有什么是在所有情况下都有效的。通常,用户不应该关心您使用Python来实现您的实用程序(如果您决定以任何原因重新实现它,则接口不应更改),因此您不应该期望用户甚至知道特定于Python的环境变量。强制用户指定字符编码是不好的UI设计;必要时将字符编码嵌入报告格式中。注意:在一般情况下,没有硬编码的编码可以成为“合理的默认值”。 - jfs
24
这是错误且令人困惑的建议。人们使用 str 的原因是对象本身不是字符串,因此没有可调用的 .encode() 方法。 - Cerin
显示剩余14条评论

487

这是Python中一个经典的Unicode问题!考虑以下情况:

a = u'bats\u00E0'
print a
 => batsà

到目前为止一切都很好,但是如果我们调用str(a),让我们看看会发生什么:
str(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe0' in position 4: ordinal not in range(128)

哦,糟了,这对任何人都没有好处!要修复错误,请使用.encode显式地对字节进行编码,并告诉Python要使用的编解码器:

a.encode('utf-8')
 => 'bats\xc3\xa0'
print a.encode('utf-8')
 => batsà

就是在调用 str() 方法时,Python 会使用默认的字符编码来尝试对你给它的字节进行编码,而在你的情况下,有时这些字节表示 Unicode 字符。为解决问题,你需要告诉 Python 如何处理你给它的字符串,可以使用 .encode('whatever_unicode'),大多数情况下,你使用 utf-8 应该没什么问题。

如果你想深入了解这个主题,可以参考 Ned Batchelder 的 PyCon 演讲:http://nedbatchelder.com/text/unipain.html


96
个人记录:在尝试键入“.encode”时,不要不小心键入“.unicode”,然后想知道为什么什么都不起作用。 - Skip Huffman
13
好的建议。但是,如果您使用 str(x) 打印可能是字符串也可能不是字符串的对象时,该怎么办呢?如果 x 是数字、日期时间、布尔值或普通字符串,则 str(x) 可以工作。但是,如果它是 unicode,则会停止工作。是否有一种方法可以获得相同的行为,或者我们现在需要添加一个 IF 检查来测试对象是否为字符串,以便使用.encode,否则使用 str()? - Dirk R
同样的问题可以用“None”值来提出。 - Vadorequest

253

我发现了一个优雅的方法,可以去除符号并继续保留字符串作为字符串:

yourstring = yourstring.encode('ascii', 'ignore').decode('ascii')

需要注意的是,使用忽略选项是危险的,因为它会在代码中悄无声息地丢弃任何Unicode(和国际化)支持,如下所示(转换Unicode):

>>> u'City: Malmö'.encode('ascii', 'ignore').decode('ascii')
'City: Malm'

19
你让我开心极了!对于 utf-8 编码,只需执行以下操作即可:yourstring = yourstring.encode('utf-8', 'ignore').decode('utf-8') - luca76
对我来说这个方法可行,但我的情况有所不同,我保存的文件名中有“/”,而路径不存在,所以我必须使用.replace("/",""),从而保存了我的脚本。忽略ASCII也适用于'utf-8'的情况。 - Akash Kandpal
1
@harrypotter0 如果你想正确地连接文件路径,建议使用 os.path.join(),这是一种非常好的习惯,特别是在进行跨平台编程时。 :) - login_not_failed

191

我试过了所有的方法都不起作用,后来在网上搜索后发现以下内容,这解决了问题。 Python版本为2.7。


# encoding=utf8
import sys
reload(sys)
sys.setdefaultencoding('utf8')

8
不要这样做。虽然当你搜索错误时,像这个答案https://dev59.com/VF0Z5IYBdhLWcg3wvCSd#31137935出现在结果的顶部,我可以理解为什么这似乎是一个好主意。 - Padraic Cunningham
33
我尝试了这个主题中几乎所有的建议,但真的没有一个对我有效。最后,我尝试了这一个。这确实是唯一一个简单且有效的方法。如果有人说“不要这样做,然后提供一个简单的解决方案”,否则请使用这个,因为它是一个有效的复制和粘贴解决方案。 - Richard de Ree
4
这可以在Python3中如何完成?很高兴得知。 - Kanerva Peter
7
我只会添加一个 if sys.version_info.major < 3: - Prof. Falken
1
这个解决方案的问题在于它更改了Python的所有编码,这意味着可能使用ASCII作为默认编码来编写的外部模块现在正在使用不同的编码。我不够勇敢去冒险追踪我特定应用程序所使用的所有外部模块中的错误。因此,如果这对您有效,可能只是因为您没有使用这种脆弱方式的外部模块。 - Iron Pillow
显示剩余4条评论

107

即使是打印操作也会失败的一个微妙问题是环境变量设置错误,例如这里的LC_ALL设置为"C"。在Debian中,他们不建议进行此设置:Locale的Debian Wiki页面

$ echo $LANG
en_US.utf8
$ echo $LC_ALL 
C
$ python -c "print (u'voil\u00e0')"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe0' in position 4: ordinal not in range(128)
$ export LC_ALL='en_US.utf8'
$ python -c "print (u'voil\u00e0')"
voilà
$ unset LC_ALL
$ python -c "print (u'voil\u00e0')"
voilà

遇到了完全相同的问题,太糟糕了,我在报告之前没有检查过。非常感谢。顺便说一下,您可以使用env|grep -E '(LC|LANG)'替换前两个命令。 - Dmitry Verhoturov
关于错误编码问题,我想提出我的两分意见。我经常在“子shell模式”(Ctrl-O)下使用mc,而且我也忘了我添加了以下别名到bash中:alias mc="LANG=en_EN.UTF-8 mc"。所以当我尝试运行依赖于内部ru_RU.UTF-8的糟糕写法脚本时,它们就会死掉。在发现真正的问题之前,我尝试了很多这个线程上的东西。 :) - login_not_failed
你太棒了。在GSUTIL中,我的rsync因为这个问题而失败了。修复了LC_ALL,一切都像葡萄酒一样顺利。<3谢谢你<3 - dsignr

41

问题在于你试图打印一个unicode字符,但你的终端不支持它。

你可以尝试安装language-pack-en软件包来解决这个问题:

sudo apt-get install language-pack-en

该软件包提供了所有支持的软件包(包括Python)的英语翻译数据更新。如果必要,请安装不同的语言包(具体取决于您要打印哪些字符)。

在某些Linux发行版中,为了确保默认的英语区域设置正确设置(以便shell/终端能够处理Unicode字符),它是必需的。有时候安装它比手动配置更容易。

然后,在编写代码时,请确保在您的代码中使用正确的编码方式。

例如:

open(foo, encoding='utf-8')

如果您仍然遇到问题,请仔细检查您的系统配置,例如:
  • Your locale file (/etc/default/locale), which should have e.g.

    LANG="en_US.UTF-8"
    LC_ALL="en_US.UTF-8"
    

    or:

    LC_ALL=C.UTF-8
    LANG=C.UTF-8
    
  • Value of LANG/LC_CTYPE in shell.

  • Check which locale your shell supports by:

    locale -a | grep "UTF-8"
    
展示在全新 VM 中出现的问题和解决方案。
  1. Initialize and provision the VM (e.g. using vagrant):

    vagrant init ubuntu/trusty64; vagrant up; vagrant ssh
    

    See: available Ubuntu boxes..

  2. Printing unicode characters (such as trade mark sign like ):

    $ python -c 'print(u"\u2122");'
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
    UnicodeEncodeError: 'ascii' codec can't encode character u'\u2122' in position 0: ordinal not in range(128)
    
  3. Now installing language-pack-en:

    $ sudo apt-get -y install language-pack-en
    The following extra packages will be installed:
      language-pack-en-base
    Generating locales...
      en_GB.UTF-8... /usr/sbin/locale-gen: done
    Generation complete.
    
  4. Now problem should be solved:

    $ python -c 'print(u"\u2122");'
  5. Otherwise, try the following command:

    $ LC_ALL=C.UTF-8 python -c 'print(u"\u2122");'

1
language-pack-en与Python或这个问题有什么关系?据我所知,它可能为消息提供语言翻译,但与编码无关。 - Alastair McCormack
3
在某些Linux发行版上,为了确保默认的英语区域设置正确配置,特别是在终端上运行Python脚本时,需要这样做。我曾经尝试过这种方法,它对我有效。参见:字符编码 - kenorb
啊,好的。你的意思是如果想使用非英语环境?我猜用户还需要编辑 /etc/locale.gen 确保他们的环境在使用之前已经构建好了? - Alastair McCormack
1
@AlastairMcCormack 从/etc/default/locale中注释掉了LANG(因为/etc/locale.gen不存在),并运行了locale-gen,但没有帮助。我不确定language-pack-en到底是做什么的,因为我没有找到太多文档,列出其内容也没有什么帮助。 - kenorb
1
在桌面系统上通常已经存在UTF-8本地化环境,因此您可能不需要安装任何东西,只需配置 LANG/ LC_CTYPE/ LC_ALL 即可(例如,LANG=C.UTF-8)。 - jfs
显示剩余2条评论

33
在shell中:
  1. 通过以下命令找到支持的UTF-8语言环境:

    locale -a | grep "UTF-8"
    
  2. 在运行脚本之前,先导出它,例如:

    export LC_ALL=$(locale -a | grep UTF-8)
    

    或者手动操作:

    export LC_ALL=C.UTF-8
    
  3. 通过打印特殊字符进行测试,例如

    python -c 'print(u"\u2122");'
    

已在Ubuntu中测试。


是的,这是最好的简短回答,我们不能修改源代码来使用.encode。 - Neo.Mxn0
我在Python3中使用它,设置了LC_ALL后现在可以正常工作。谢谢。 - Ajay

30

实际上,在我大多数的案例中,只需剥离这些字符就简单得多:

s = mystring.decode('ascii', 'ignore')

31
“Perfectly”通常并不是它所表现出的那样。它会抛弃一些你应该学会如何正确处理的东西。 - tripleee
8
仅仅删除“那些”(非英文)字符并不是解决方案,因为Python必须支持所有语言,你认为呢? - alemol
8
被踩了。这完全不是正确的解决方案。学习如何使用Unicode:http://www.joelonsoftware.com/articles/Unicode.html - Andrew Ferrier
4
看,呈现这个特定答案最明智的方式是这样的:认识到ASCII为某些语言和用户赋予了某种特权 - 这是可以被利用的“逃生通道”,对于那些可能在实施完整的Unicode支持之前草率地编写脚本进行初步工作的用户来说,这是一个潜在的选择。 - lol
7
如果我正在编写一个脚本,只需在公司内部应用程序中将英文文本打印到标准输出,我只想让问题消失。任何可行的方法都可以。 - kagronick
显示剩余2条评论

29

对我而言,有效的方法是:

BeautifulSoup(html_text,from_encoding="utf-8")

希望这能对某人有所帮助。


22
这是一些其他所谓的“懒惰”答案的重新阐述。尽管此处提出了抗议,但在某些情况下,简单地丢弃麻烦的字符/字符串是一个不错的解决方案。
def safeStr(obj):
    try: return str(obj)
    except UnicodeEncodeError:
        return obj.encode('ascii', 'ignore').decode('ascii')
    except: return ""

测试它:

if __name__ == '__main__': 
    print safeStr( 1 ) 
    print safeStr( "test" ) 
    print u'98\xb0'
    print safeStr( u'98\xb0' )

结果:

1
test
98°
98

更新: 我原来的答案是针对 Python 2 写的。针对 Python 3

def safeStr(obj):
    try: return str(obj).encode('ascii', 'ignore').decode('ascii')
    except: return ""

注意:如果您希望在“不安全”的 Unicode 字符处留下一个“?”指示符,请在调用编码的错误处理程序时指定 replace 而不是 ignore
建议:也许您想将此函数命名为 toAscii?这是个人偏好问题...
最后,这是一个更加健壮的 PY2/3 版本,使用了 six,我选择使用了 replace 并添加了一些字符替换,以将卷曲左或右的花式 Unicode 引号和撇号替换为纵向简单的 ASCII 部分中的引号和撇号。您可以自己扩展此类交换:
from six import PY2, iteritems 

CHAR_SWAP = { u'\u201c': u'"'
            , u'\u201D': u'"' 
            , u'\u2018': u"'" 
            , u'\u2019': u"'" 
}

def toAscii( text ) :    
    try:
        for k,v in iteritems( CHAR_SWAP ): 
            text = text.replace(k,v)
    except: pass     
    try: return str( text ) if PY2 else bytes( text, 'replace' ).decode('ascii')
    except UnicodeEncodeError:
        return text.encode('ascii', 'replace').decode('ascii')
    except: return ""

if __name__ == '__main__':     
    print( toAscii( u'testin\u2019' ) )

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