Как получить SQLAlchemy для правильной вставки многоточия Юникода в таблицу mySQL? - PullRequest
16 голосов
/ 06 февраля 2012

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

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

Если я добавлю опцию convert_unicode = True к движку, я смогу получить вставку, чтобы пройти, но многоточие не показываетэто просто странные персонажи.Кажется, это имеет смысл, поскольку, насколько мне известно, в латинице-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 unicode?Я также пытался кодировать элемент ['description] с помощью .encode (' cp1252 ') перед его отправкой, и это работало также даже без добавления опции charset в строку подключения.Это не должно было работать с латиницей-1, но, видимо, это сработало?У меня есть решение, но я бы с удовольствием ответил:)

Ответы [ 2 ]

32 голосов
/ 06 февраля 2012

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

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, у вас будет несоответствие кодировки, и ваш текст, скорее всего, будет сохранен неправильно.

0 голосов
/ 10 августа 2017

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

...