Как заставить SQLAlchemy правильно вставлять многоточие Unicode в таблицу mySQL?

Я пытаюсь разобрать RSS-канал с помощью feedparser и вставить его в таблицу mySQL с помощью SQLAlchemy. На самом деле мне удалось заставить это работать нормально, но сегодня в ленте был элемент с многоточием в описании, и я получаю следующую ошибку:

UnicodeEncodeError: кодек 'latin-1' не может кодировать символ u'…' в позиции 35: порядковый номер не в диапазоне (256)

Если я добавлю параметр convert_unicode=True в движок, я смогу заставить вставку пройти, но многоточие не отображается, это просто странные символы. Это кажется логичным, поскольку, насколько мне известно, в latin-1 нет горизонтального многоточия. Даже если я установил кодировку utf-8, похоже, это не имеет значения. Если я делаю вставку с помощью phpmyadmin и включаю многоточие, все проходит нормально.

Я думаю, что просто не понимаю кодировки символов или как заставить SQLAlchemy использовать ту, которую я укажу. Кто-нибудь знает, как заставить текст входить без странных символов?

ОБНОВИТЬ

Я думаю, что понял это, но я не совсем уверен, почему это важно...

Вот код:

import sys
import feedparser
import sqlalchemy
from sqlalchemy import create_engine, MetaData, Table

COMMON_CHANNEL_PROPERTIES = [
  ('Channel title:','title', None),
  ('Channel description:', 'description', 100),
  ('Channel URL:', 'link', None),
]

COMMON_ITEM_PROPERTIES = [
  ('Item title:', 'title', None),
  ('Item description:', 'description', 100),
  ('Item URL:', 'link', None),
]

INDENT = u' '*4

def feedinfo(url, output=sys.stdout):
  feed_data = feedparser.parse(url)
  channel, items = feed_data.feed, feed_data.entries

  #adding charset=utf8 here is what fixed the problem

  db = create_engine('mysql://user:pass@localhost/db?charset=utf8')
  metadata = MetaData(db)
  rssItems = Table('rss_items', metadata,autoload=True)
  i = rssItems.insert();

  for label, prop, trunc in COMMON_CHANNEL_PROPERTIES:
    value = channel[prop]
    if trunc:
      value = value[:trunc] + u'...'
    print >> output, label, value
  print >> output
  print >> output, "Feed items:"
  for item in items:
    i.execute({'title':item['title'], 'description': item['description'][:100]})
    for label, prop, trunc in COMMON_ITEM_PROPERTIES:
      value = item[prop]
      if trunc:
        value = value[:trunc] + u'...'
      print >> output, INDENT, label, value
    print >> output, INDENT, u'---'
  return

if __name__=="__main__":
  url = sys.argv[1]
  feedinfo(url)

Вот вывод/трассировка при запуске кода без опции charset:

Channel title: [H]ardOCP News/Article Feed
Channel description: News/Article Feed for [H]ardOCP...
Channel URL: http://www.hardocp.com

Feed items:
     Item title: Windows 8 UI is Dropping the 'Start' Button
     Item description: After 15 years of occupying a place of honor on the desktop, the "Start" button will disappear from ...
     Item URL: http://www.hardocp.com/news/2012/02/05/windows_8_ui_dropping_lsquostartrsquo_button/
     ---
     Item title: Which Crashes More? Apple Apps or Android Apps
     Item description: A new study of smartphone apps between Android and Apple conducted over a two month period came up w...
     Item URL: http://www.hardocp.com/news/2012/02/05/which_crashes_more63_apple_apps_or_android/
     ---
Traceback (most recent call last):
  File "parse.py", line 47, in <module>
    feedinfo(url)
  File "parse.py", line 36, in feedinfo
    i.execute({'title':item['title'], 'description': item['description'][:100]})
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/sql/expression.py", line 2758, in execute
    return e._execute_clauseelement(self, multiparams, params)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 2304, in _execute_clauseelement
    return connection._execute_clauseelement(elem, multiparams, params)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1538, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_context
    context)
  File "/usr/local/lib/python2.7/site-packages/sqlalchemy/engine/default.py", line 330, in do_execute
    cursor.execute(statement, parameters)
  File "build/bdist.linux-i686/egg/MySQLdb/cursors.py", line 159, in execute
  File "build/bdist.linux-i686/egg/MySQLdb/connections.py", line 264, in literal
  File "build/bdist.linux-i686/egg/MySQLdb/connections.py", line 202, in unicode_literal
UnicodeEncodeError: 'latin-1' codec can't encode character u'\u2026' in position 35: ordinal not in range(256)

Итак, похоже, что добавление кодировки в строку подключения mysql сделало это. Я полагаю, по умолчанию используется латиница-1? Я попытался установить флаг кодирования на content_engine в utf8, и это ничего не дало. Кто-нибудь знает, почему он будет использовать латиницу-1, когда таблицы и поля настроены на юникод utf8? Я также пробовал кодировать item['description] с помощью .encode('cp1252') перед его отправкой, и это сработало даже без добавления параметра charset в строку подключения. Это не должно было работать с латиницей-1, но, видимо, сработало? У меня есть решение, но хотелось бы ответа :)


person kvedananda    schedule 06.02.2012    source источник
comment
Можете ли вы показать код, который вы используете для его вставки? Откуда взялась строка с многоточием? В сообщении об ошибке при использовании utf-8 также говорится о кодеке «latin-1»?   -  person Wooble    schedule 06.02.2012
comment
Пожалуйста, предоставьте данные, которые создают проблему. Если вы можете предоставить код, который вы используете, будет полезно понять, что вы пытаетесь сделать. :)   -  person Nilesh    schedule 06.02.2012
comment
Я добавил код над строкой с многоточием с сайта hardocp.com. Вот фрагмент с многоточием: Microsoft ищет несколько хороших…..идей. Я включил свой код выше.   -  person kvedananda    schedule 06.02.2012


Ответы (2)


Сообщение об ошибке

UnicodeEncodeError: 'latin-1' codec can't encode character u'\u2026' 
in position 35: ordinal not in range(256)

кажется, указывает на то, что некоторый код языка Python пытается преобразовать символ \u2026 в строку Latin-1 (ISO8859-1), и это не удается. Неудивительно, что этот символ U+2026 HORIZONTAL ELLIPSIS не имеет ни одного эквивалентного символа в ISO8859-1. .

Вы устранили проблему, добавив запрос ?charset=utf8 в свой вызов соединения SQLAlchemy:

import sqlalchemy
from sqlalchemy import create_engine, MetaData, Table

db = create_engine('mysql://user:pass@localhost/db?charset=utf8')

В разделе URL-адреса базы данных документации SQLAlchemy рассказывается Нам известно, что URL-адрес, начинающийся с mysql, указывает на диалект MySQL с использованием драйвера mysql-python.

Следующий раздел, Пользовательские аргументы DBAPI connect() сообщает нам, что аргументы запроса передаются базовому DBAPI.

Итак, что делает драйвер mysql-python с параметром {charset: 'utf8'}? Раздел Функции и атрибуты их документации говорит о charset атрибут "...Если присутствует, набор символов соединения будет изменен на этот набор символов, если они не равны."

Чтобы узнать, что означает набор символов соединения, мы обратимся к 10.1.4. Наборы символов соединения и сопоставления справочного руководства по MySQL 5.6. Короче говоря, MySQL может интерпретировать входящие запросы как кодировку, отличную от набора символов базы данных, и отличную от кодировки возвращаемых результатов запроса.

Поскольку сообщение об ошибке, о котором вы сообщили, выглядит как сообщение об ошибке Python, а не сообщение об ошибке SQL, я предполагаю, что что-то в SQLAlchemy или mysql-python пытается преобразовать запрос в кодировку соединения по умолчанию latin-1 перед его отправкой. Это то, что вызывает ошибку. Однако строка запроса ?charset=utf8 в вашем вызове connect() изменяет кодировку соединения, и U+2026 HORIZONTAL ELLIPSIS может пройти.

Обновление: вы также спрашиваете: «Если я удалю параметр charset, а затем закодирую описание с помощью .encode('cp1252'), все пройдет нормально. Как многоточие может пройти через cp1252? но не юникод?"

кодировка cp1252 содержит символ горизонтального многоточия со значением байта \x85. Таким образом, можно закодировать строку Unicode, содержащую U+2026 HORIZONTAL ELLIPSIS, в cp1252 без ошибок.

Помните также, что в Python строки Unicode и строки байтов — это два разных типа данных. Разумно предположить, что MySQLdb может иметь политику отправки только строк байтов через соединение SQL. Таким образом, он будет кодировать запрос, полученный в виде строки Unicode, в строку байтов, но оставит запрос, полученный в виде строки байтов, в одиночку. (Это предположение, я не смотрел исходный код.)

В опубликованной вами трассировке последние две строки (ближайшие к месту возникновения ошибки) показывают имена методов literal, за которыми следует unicode_literal. Это поддерживает теорию о том, что MySQLdb кодирует запрос, который он получает, как строку Unicode в строку байтов.

Когда вы сами кодируете строку запроса, вы обходите часть MySQLdb, которая делает это кодирование по-другому. Обратите внимание, однако, что если вы кодируете строку запроса иначе, чем требует кодировка соединения MySQL, тогда у вас будет несоответствие кодировки, и ваш текст, вероятно, будет сохранен неправильно.

person Jim DeLaHunt    schedule 06.02.2012
comment
Это почти отвечает на него. Во-первых, если я удалю параметр charset, а затем закодирую описание с помощью .encode('cp1252'), все пройдет нормально. Как многоточие может пройти через cp1252, но не через юникод? Я знаю, что что-то упускаю, но не уверен, что именно. - person kvedananda; 06.02.2012
comment
+1 за указание на то, что вы исправили проблему, добавив запрос ?charset=utf8 в свой вызов соединения SQLAlchemy - person btk; 03.09.2012
comment
что, если он все еще не работает? с арабским словом. (мой клиент хочет на арабском) вот так من سار على الدرب وصل - person Foggy Minded Greenhorn; 14.11.2020
comment
@lone_coder, отличный вопрос. Задайте его как отдельный вопрос, покажите свой код и контекст и отметьте меня в комментарии. Я постараюсь ответить там. - person Jim DeLaHunt; 15.11.2020

Добавление charset=utf8 в строку подключения определенно помогает, но я столкнулся с ситуациями в Python 2.7, когда добавление convert_unicode=True к create_engine также было необходимо. В документации SQLAlchemy говорится, что это только для повышения производительности, но в моем случае это фактически решило проблему использования неправильного кодировщика.

person Piotr Zioło    schedule 10.08.2017