2012-02-06 18 views
15

Estoy tratando de analizar una fuente RSS con el verificador de información e insertarlo en una tabla mySQL usando SQLAlchemy. De hecho, pude hacer que funcionara bien, pero hoy el feed tenía un elemento con un carácter de elipsis en la descripción y aparece el siguiente error:¿Cómo obtengo SQLAlchemy para insertar correctamente una elipsis unicode en una tabla mySQL?

UnicodeEncodeError: el códec 'latin-1' no puede codificar el carácter u ' \ u2026 'en la posición 35: ordinal no está dentro del rango (256)

Si agrego la opción convert_unicode = True al motor, puedo obtener el inserto, pero las elipsis no aparecen, es simplemente extraño caracteres. Esto parece tener sentido ya que, a mi leal saber y entender, no hay elipsis horizontal en latin-1. Incluso si configuro la codificación para utf-8, no parece marcar la diferencia. Si hago una inserción usando phpmyadmin e incluyo las elipsis, pasa bien.

Estoy pensando que simplemente no entiendo las codificaciones de caracteres o cómo obtener SQLAlchemy para usar uno que especifique. ¿Alguien sabe cómo hacer que el texto entre sin personajes extraños?

ACTUALIZACIÓN

Creo que he descubierto éste hacia fuera, pero no estoy realmente seguro de por qué es importante ...

Aquí está el código:

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:[email protected]/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) 

Aquí está la salida/traceback de ejecutar el código sin la opción de juego de caracteres:

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) 

Parece que se debe agregar la c harset a la cadena de conexión mysql lo hizo. Supongo que por defecto es latin-1? Había intentado configurar la bandera de codificación en content_engine para utf8 y eso no hizo nada. ¿Alguien sabe por qué usaría latin-1 cuando las tablas y los campos están configurados en utf8 unicode? También intenté codificar el elemento ['descripción] usando .encode (' cp1252 ') antes de enviarlo y eso funcionó bien incluso sin agregar la opción de juego de caracteres a la cadena de conexión. Eso no debería haber funcionado con latin-1 pero aparentemente lo hizo? Tengo la solución, pero le encantaría una respuesta :)

+0

¿Puedes mostrar el código que estás usando para insertarlo? ¿De dónde viene la cuerda con la elipsis? ¿El mensaje de error al usar utf-8 también dice "'latin-1' codec"? – geoffspear

+0

Proporcione los datos que crean el problema. Si puede proporcionar el código que está utilizando, será útil comprender lo que intenta hacer. :) – Nilesh

+0

He agregado el código sobre la cadena con la elipsis viene del sitio web hardocp.com. Aquí hay un fragmento con las elipsis: Microsoft está buscando algunas buenas ... ..ideas. He incluido mi código anterior. – kvedananda

Respuesta

29

El mensaje de error

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

parece indicar que algún código de lenguaje Python está tratando de convertir el carácter \u2026 en un Latin-1 (ISO8859- 1) cadena, y está fallando. No es sorprendente que ese personaje sea U+2026 HORIZONTAL ELLIPSIS, que no tiene un solo carácter equivalente en ISO8859-1.

Se solucionó el problema mediante la adición de la consulta ?charset=utf8 en su SQLAlchemy llamada de conexión:

import sqlalchemy 
from sqlalchemy import create_engine, MetaData, Table 

db = create_engine('mysql://user:[email protected]/db?charset=utf8') 

La sección Database Urls de la documentación SQLAlchemy nos dice que una dirección URL que empieza con mysql indica un dialecto MySQL, utilizando el controlador mysql-python .

La siguiente sección, Custom DBAPI connect() arguments, nos dice que los argumentos de consulta se pasan al DBAPI subyacente.

Entonces, ¿qué hace el controlador mysql-python de un parámetro {charset: 'utf8'}? La sección Functions and attributes de su documentación dice del atributo charset "...Si está presente, el juego de caracteres de conexión se cambiará a este conjunto de caracteres, si no son iguales. "

Para saber qué significa el conjunto de caracteres de conexión, pasamos al 10.1.4. Connection Character Sets and Collations del manual de referencia de MySQL 5.6. Para abreviar, MySQL puede interpretar las consultas entrantes como una codificación diferente al juego de caracteres de la base de datos y diferente de la codificación de los resultados de la consulta devuelta.

Dado que el mensaje de error que informó parece un Python en lugar de un error SQL mensaje, voy a especular que algo en SQLAlchemy o mysql-python está intentando convertir la consulta a una codificación de conexión predeterminada de latin-1 antes de enviarlo. Esto es lo que desencadena el error. Sin embargo, la cadena de consulta ?charset=utf8 en su llamada connect() cambia la codificación de la conexión, y el U+2026 HORIZONTAL ELLIPSIS es capaz de pasar.

Actualización:. también se pregunta: "si me quito la opción de juego de caracteres y luego codificar la descripción mediante .encode ('CP1252') que va a ir a través de bien ¿Cómo es una elipsis capaz de obtener a través de CP1252, pero no unicode? "

El encoding cp1252 has un carácter de elipsis horizontal en el valor de \x85 bytes. Por lo tanto, es posible codificar una cadena Unicode que contenga U+2026 HORIZONTAL ELLIPSIS en cp1252 sin error.

Recuerde también que en Python, las cadenas Unicode y de bytes son dos tipos de datos diferentes. Es razonable especular que MySQLdb podría tener una política de enviar solo cadenas de bytes a través de una conexión SQL. Por lo tanto, codificaría una consulta recibida como una cadena Unicode en una cadena de bytes, pero dejaría una consulta recibida solo como una cadena de bytes. (Esto es especulación, no he mirado el código fuente.)

En el rastreo informados, las dos últimas líneas (más cercano a donde se produce el error) muestran los nombres de los métodos literal, seguido por unicode_literal. Eso tiende a respaldar la teoría de que MySQLdb está codificando la consulta que recibe como una cadena Unicode en una cadena de bytes.

Cuando codifica la secuencia de consulta usted mismo, omite la parte de MySQLdb que hace esta codificación de manera diferente. Sin embargo, tenga en cuenta que si codifica la cadena de consulta de forma diferente a la que establece el juego de caracteres MySQL, entonces tendrá una discrepancia de codificación y su texto probablemente se almacenará incorrectamente.

+0

Esto casi lo resuelve. Una cosa es que si elimino la opción de juego de caracteres y luego codigo la descripción usando .encode ('cp1252'), pasará muy bien. ¿Cómo es posible lograr una elipsis con cp1252 pero no unicode? Sé que me estoy perdiendo algo, pero no estoy seguro de qué se trata. – kvedananda

+0

¡Gracias! Esto está empezando a tener sentido ... – kvedananda

+0

+1 para señalar "corrigió el problema agregando la consulta? Charset = utf8 en su llamada de conexión SQLAlchemy" – btk

0

Añadiendo charset=utf8 en la cadena de conexión definitivamente ayuda, pero encontré situaciones en Python 2.7 cuando también era necesario agregar convert_unicode=True a create_engine. La documentación de SQLAlchemy dice que es solo para aumentar el rendimiento, pero en mi caso realmente resolvió el problema del codificador incorrecto que se utiliza.

Cuestiones relacionadas