Python - 使用Django将Unicode字符存储到MySQL时出现问题

7
我有一个字符串

 u"Played Mirror's Edge\u2122"

应该显示为

 Played Mirror's Edge™

但这是另一个问题。我现在的问题是将它放入模型中,然后尝试将其保存到数据库中。也就是说:

a = models.Achievement(name=u"Played Mirror's Edge\u2122")
a.save()

我正在获得:
'ascii' codec can't encode character u'\u2122' in position 13: ordinal not in range(128)

完整的堆栈跟踪(如所请求):

Traceback:
File "/var/home/ptarjan/django/mysite/django/core/handlers/base.py" in get_response
  86.                 response = callback(request, *callback_args, **callback_kwargs)
File "/var/home/ptarjan/django/mysite/yourock/views/alias.py" in import_all
  161.     types.import_all(type, alias)
File "/var/home/ptarjan/django/mysite/yourock/types/types.py" in import_all
  52.     return modules[type].import_all(siteAlias, alias)
File "/var/home/ptarjan/django/mysite/yourock/types/xbox.py" in import_all
  117.             achiever = self.add_achievement(dict, siteAlias, alias)
File "/var/home/ptarjan/django/mysite/yourock/types/base_profile.py" in add_achievement
  130.                 owner       = siteAlias,
File "/var/home/ptarjan/django/mysite/django/db/models/query.py" in get
  304.         num = len(clone)
File "/var/home/ptarjan/django/mysite/django/db/models/query.py" in __len__
  160.                 self._result_cache = list(self.iterator())
File "/var/home/ptarjan/django/mysite/django/db/models/query.py" in iterator
  275.         for row in self.query.results_iter():
File "/var/home/ptarjan/django/mysite/django/db/models/sql/query.py" in results_iter
  206.         for rows in self.execute_sql(MULTI):
File "/var/home/ptarjan/django/mysite/django/db/models/sql/query.py" in execute_sql
  1734.         cursor.execute(sql, params)
File "/var/home/ptarjan/django/mysite/django/db/backends/util.py" in execute
  19.             return self.cursor.execute(sql, params)
File "/var/home/ptarjan/django/mysite/django/db/backends/mysql/base.py" in execute
  83.             return self.cursor.execute(query, args)
File "/usr/lib/pymodules/python2.5/MySQLdb/cursors.py" in execute
  151.             query = query % db.literal(args)
File "/usr/lib/pymodules/python2.5/MySQLdb/connections.py" in literal
  247.         return self.escape(o, self.encoders)
File "/usr/lib/pymodules/python2.5/MySQLdb/connections.py" in string_literal
  180.                 return db.string_literal(obj)

Exception Type: UnicodeEncodeError at /import/xbox:bob
Exception Value: 'ascii' codec can't encode character u'\u2122' in position 13: ordinal not in range(128)

以下是模型的相关部分:

class Achievement(MyBaseModel):
    name = models.CharField(max_length=100, help_text="A human readable achievement name")

我在settings.py中使用的是MySQL后端。
DEFAULT_CHARSET = 'utf-8'

基本上,我该如何处理所有这些Unicode相关的问题?我本来希望只要避免使用奇怪的字符集并坚持使用UTF8,一切都会“自动解决”。但很遗憾,事实似乎并不是那么简单。


听起来好像它不喜欢单引号(')字符... - colithium
2
怎么回事?我认为它在 \u2122 上出问题了... - Paul Tarjan
你能提供剩余的堆栈跟踪吗?实际的数据库代码可能正确处理了你的Unicode字符串,但某些日志记录代码可能出了问题。 - Deestan
尝试最小化问题: models.Achievement.objects.create(name=u"\u2122") models.Achievement.objects.create(name=u"玩过Mirror's Edge") - Mikhail Polykovskii
你使用的数据库排序规则是什么? - Carson
7个回答

12
感谢所有在此发帖的人。这真的有助于我的Unicode知识(希望其他人也学到了一些东西)。
我们似乎都在错误的方向上努力,因为我试图简化我的问题并没有提供所有信息。看起来我没有使用“REAL” Unicode字符串,而是BeautifulSoup.NavigableString,它们将自己表示为Unicode字符串。所以所有的打印输出看起来像Unicode,但实际上不是。
在MySQLDB库的深处,它们无法处理这些字符串。
这个有效:
>>> Achievement.objects.get(name = u"Mirror's Edge\u2122")
<Achievement: Mirror's Edge™>

另一方面:
>>> b = BeautifulSoup(u"<span>Mirror's Edge\u2122</span>").span.string
>>> Achievement.objects.get(name = b)
... Exceptoins ...
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2122' in position 13: ordinal not in range(128)

但是这个可以工作:
>>> Achievement.objects.get(name = unicode(b))
<Achievement: Mirror's Edge™>

非常感谢你提供的Unicode帮助,我相信它会很有用。但是现在...

警告:BeautifulSoup不返回真正的Unicode字符串,在对其进行任何有意义的操作之前应该使用unicode()进行强制转换。


谢谢,我是通过谷歌找到这个内容的,并从答案中学到了很多。后来我看到您在最后使用了Beautiful Soup。和我一样 :) - Andy Hume
即使在BeautifulSoup返回的所有值上强制使用unicode,我仍然无法使其工作。 我在将其打印到终端和插入MySQL时都遇到了错误。 错误的形式为“'latin-1'编解码器无法在位置545处对字符u'\u03bc'进行编码:序数不在范围内(256)”。 - rohitmishra
顺便提一下,lxml也会出现同样的情况。如果你直接将lxml中的文本传递给MySQLdb(其类型为<type 'lxml.etree._ElementUnicodeResult'>),那么你会得到相同的错误信息。 - Jeeyoung Kim

4

几点说明:

  • Python 2.x有两种字符串类型

    • "str",基本上是一个字节数组(所以你可以在其中存储任何东西)
    • "unicode",内部编码为UCS2 / UCS4编码的Unicode
  • 这些类型的实例被视为“解码”数据。内部表示是参考,因此您需要将外部数据“解码”到其中,并“编码”成某种外部格式。

  • 一个好策略是在数据进入系统时尽早进行解码,并在尽可能晚的时候进行编码。尽可能多地使用Unicode来处理系统中的字符串。(在这方面我不同意Nikolai的看法)。

  • 这个编码方面适用于Nicolai的回答。他将原始的Unicode字符串编码为utf-8。但是这并没有解决问题(至少不是普遍的),因为结果的字节缓冲区仍然可以包含范围(127)之外的字节(我没有检查\u2122),这意味着你会再次遇到相同的异常。

  • 尽管如此,Nicolai的分析认为你正在传递一个Unicode字符串,但是在系统中的某个地方,它被认为是str实例。如果你的Unicode参数在某个地方应用了str()函数,那么就足够了。

  • 在这种情况下,Python使用所谓的默认编码,如果不更改,则为ascii。有一个函数sys.setdefaultencoding,您可以使用它来切换到utf-8等,但是该函数仅在有限的上下文中可用,因此您不能轻松地在应用程序代码中使用它。

  • 我的感觉是问题可能出现在您调用的更深层次。不幸的是,我无法评论Django或MySQL / SQLalchemy,但是我想知道在声明模型中的'name'属性时是否可以指定unicode类型。在字段级别处理类型信息是良好的数据库实践。也许有CharField的替代方法?!

  • 是的,你可以安全地将单引号(')嵌入双引号(")字符串中,反之亦然。


谢谢,信息很有用。实际上,UTF8也与ASCII存在相同的问题:unicode.encode(u"Played Mirror's Edge\u2122", 'utf8') "Played Mirror's Edge\xe2\x84\xa2"。我试图一直使用Unicode(我以为我一直在这样做),而我的数据库是以UTF8编码的。 - Paul Tarjan

3

您正在使用“unicode”类型的字符串。如果您的模型或SQL后端不支持它们,或者不知道如何转换为UTF-8,请自行进行转换。请使用简单字符串(Python类型str)并像这样进行转换:

a = models.Achievement(name=u"Played Mirror's Edge\u2122".encode("UTF-8"))

为什么对这个答案进行了负投票?在我看来,尼古拉伊指出Unicode与UTF-8不同是正确的。 - Mark van Lent
如果我这样做,那么在插入后尝试打印模型时,会从force_unicode中得到DjangoUnicodeDecodeError。如果我从数据库中获取它,那么就是完美的,但是打印最初插入的对象会抛出DjangoUnicodeDecodeError。:( - Paul Tarjan

1
昨天我在处理这个问题,发现在连接字符串中添加 "charset=utf8" 和 "use_unicode=1" 可以解决问题(使用 SQLAlchemy,猜测是同样的问题)。
所以我的字符串看起来像这样: "mysql://user:pass@host:3306/database?use_unicode=1&charset=utf8"。

查看Django文件, 在./db/backends/mysql/base.py中有 kwargs = { 'conv': django_conversions, 'charset': 'utf8', 'use_unicode': True, } 所以我认为它已经连接了。 - Paul Tarjan

0

我同意Nikolai的观点。即使在纯Python(2.5)中,我也遇到了使用UTF-8的问题。

最终我使用了unicode函数:

entry    = unicode(sys.stdin, ENCODING)

编码是根据区域设置来确定的,如果我记得没错:

import sys, locale

ENCODING    = locale.getdefaultlocale()[1]
DEFAULT_ENCODING    = sys.getdefaultencoding()

也许可以看一下Python Unicode HOWTO


0

我在使用mysql和postgres时遇到了类似的问题,但在使用sqllite时没有问题。

这是我如何解决postgres的问题(我没有测试过这个技巧是否适用于mysql,但我认为它也可以解决问题)

在处理Unicode字符串的文件中执行以下操作:

from django.utils.safestring import SafeUnicode

假设unistr是包含字符串的变量,则执行

unistr = SafeUnicode(unistr)

在我的情况下,我正在从一个网站上爬取数据。

原始代码存在问题(ht 是 BeautifulSoup 对象):

keyword = ht.a.string

解决方法:

keyword = SafeUnicode(ht.a.string)

我不知道SafeUnicode在做什么,也不知道它是怎么解决我的问题的,但我知道它确实解决了我的问题。


从SafeUnicode文档中: """ 一个Unicode子类,被特别标记为在HTML输出方面是“安全”的。 """ 我认为你只是使用函数将其转换为Unicode。实际上我正在使用多种方式获取数据,包括urllib2.open().read()一些正则表达式和beautiful soup。我认为Beautiful Soup默认使用Unicode。 - Paul Tarjan

-1

对我来说,这个撇号看起来很奇怪,难道不应该像这样转义:

u"Played Mirror\'s Edge\u2122"

1
给定的字符串和你的字符串是等价的。在Python解释器中输入它。
u"Played Mirror's Edge\u2122" u"Played Mirror's Edge\u2122" u"Played Mirror's Edge\u2122" u"Played Mirror's Edge\u2122"
- Paul Tarjan
撇号不需要转义。转义不必转义的字符只会使代码变得混乱。但你说得对,应该在给出反对意见时加上评论。 - Nikolai Ruhe

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