我在处理从不同网页(在不同的网站上)获取的文本中的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)

我怀疑这是因为某些页面(或者更具体地说,来自某些网站的页面)可能是编码的,而其他页面可能是未编码的。所有的网站都设在英国,并提供英国消费的数据,因此不存在与内化或处理非英语文本相关的问题。

有没有人对如何解决这个问题有什么想法,以便我能够始终如一地解决这个问题?


当前回答

我在尝试将Unicode字符输出到stdout时遇到了这个问题,但使用sys.stdout.write,而不是打印(这样我也可以支持输出到其他文件)。

从BeautifulSoup自己的文档中,我用编解码器库解决了这个问题:

import sys
import codecs

def main(fIn, fOut):
    soup = BeautifulSoup(fIn)
    # Do processing, with data including non-ASCII characters
    fOut.write(unicode(soup))

if __name__ == '__main__':
    with (sys.stdin) as fIn: # Don't think we need codecs.getreader here
        with codecs.getwriter('utf-8')(sys.stdout) as fOut:
            main(fIn, fOut)

其他回答

导致打印失败的一个微妙问题是环境变量设置错误,例如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à

我们在Django中使用本地化的fixture运行manage.py迁移时遇到了这个错误。

我们的源代码包含#-*-coding:utf-8-*-声明,MySQL为utf8正确配置,Ubuntu具有适当的语言包和/etc/default/locate中的值。

问题很简单,Django容器(我们使用docker)缺少LANG env变量。

将LANG设置为en_US.UTF-8并在重新运行迁移之前重新启动容器解决了问题。

这将起作用:

 >>>print(unicodedata.normalize('NFD', re.sub("[\(\[].*?[\)\]]", "", "bats\xc3\xa0")).encode('ascii', 'ignore'))

输出:

>>>bats

这是一个经典的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à

Voil\u00E0!

问题是,当您调用str()时,python使用默认的字符编码来尝试对您给它的字节进行编码,在您的情况下,这些字节有时是unicode字符的表示。要解决这个问题,你必须告诉python如何使用.encode('hatever_unicode')处理你给它的字符串。大多数时候,你应该可以使用utf-8。

有关此主题的精彩阐述,请参阅Ned Batchelder的PyCon演讲:http://nedbatchelder.com/text/unipain.html

这里是一些其他所谓的“逃避”答案的翻版。尽管这里有抗议声,但在某些情况下,扔掉麻烦的字符/字符串是一个很好的解决方案。

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字符所在的位置,请在调用中指定替换而不是忽略,以对错误处理程序进行编码。

建议:您可能希望将此函数命名为Ascii?这是一个偏好问题。。。

最后,这里有一个更强大的PY2/3版本,它使用了六个,我选择使用替换,并添加了一些字符交换,以用作为ascii集合一部分的简单垂直引号来替换花哨的unicode引号和撇号,这些引号或撇号向左或向右卷曲。您可以自己扩展此类掉期:

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' ) )