2012-02-06 11 views
15

Sto cercando di analizzare un feed RSS con feedparser e inserirlo in una tabella mySQL utilizzando SQLAlchemy. In realtà sono riuscito a farlo funzionare correttamente ma oggi il feed aveva un carattere con un puntino di sospensione nella descrizione e ricevo il seguente errore:Come si ottiene SQLAlchemy per inserire correttamente i puntini di sospensione di unicode in una tabella mySQL?

UnicodeEncodeError: il codec 'latin-1' non può codificare il carattere u ' \ u2026 'in posizione 35: ordinale non compreso nell'intervallo (256)

Se aggiungo l'opzione convert_unicode = True al motore, riesco a far passare l'inserto ma i puntini di sospensione non vengono visualizzati è solo strano personaggi. Questo sembra avere senso dato che, per quanto ne so, non ci sono ellissi orizzontali in latino-1. Anche se imposto la codifica su utf-8, non sembra che faccia la differenza. Se faccio un inserto usando phpmyadmin e includo i puntini di sospensione va bene.

Sto pensando di non capire le codifiche dei caratteri o come ottenere SQLAlchemy per usarne uno che specifichi. Qualcuno sa come ottenere il testo senza caratteri strani?

UPDATE

Credo di aver capito questo fuori, ma io non sono davvero sicuro perché è importante ...

Ecco il codice:

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) 

ecco l'output/traceback di eseguire il codice senza l'opzione 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) 

così sembra che l'aggiunta della c harset alla stringa mysql connect lo ha fatto. Suppongo che sia impostato su Latin-1? Ho provato a impostare il flag di codifica su content_engine su utf8 e questo non ha fatto nulla. Qualcuno sa perché userebbe latin-1 quando le tabelle e i campi sono impostati su unicode utf8? Ho anche provato a codificare l'oggetto ['description] usando .encode (' cp1252 ') prima di inviarlo e anche quello ha funzionato senza aggiungere l'opzione charset alla stringa di connessione. Questo non avrebbe dovuto funzionare con Latin-1 ma apparentemente lo ha fatto? Ho la soluzione, ma mi piacerebbe una risposta :)

+0

Puoi mostrare il codice che stai utilizzando per inserirlo? Dov'è la corda con l'ellissi? Il messaggio di errore quando si usa utf-8 dice anche "codec" latin-1 "? – geoffspear

+0

Si prega di fornire i dati che creano problemi. Se riesci a fornire il codice che stai usando, sarà utile capire cosa stai cercando di fare. :) – Nilesh

+0

Ho aggiunto il codice sopra la stringa con i puntini di sospensione proveniente dal sito Web hardocp.com. Ecco uno snippet con i puntini di sospensione: Microsoft sta cercando alcuni buoni ... ..ideas. Ho incluso il mio codice qui sopra. – kvedananda

risposta

29

Il messaggio di errore

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

sembra indicare che un codice linguaggio Python sta cercando di convertire il carattere \u2026 in un Latin-1 (ISO8859- 1) stringa, e sta fallendo. Non a caso, quel personaggio è U+2026 HORIZONTAL ELLIPSIS, che non ha un singolo carattere equivalente in ISO8859-1.

È risolto il problema aggiungendo la query ?charset=utf8 nel vostro SQLAlchemy chiamata collegamento:

import sqlalchemy 
from sqlalchemy import create_engine, MetaData, Table 

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

La sezione Database Urls della documentazione SQLAlchemy ci dice che un URL che inizia con mysql indica un dialetto MySQL, utilizzando il driver mysql-python .

La seguente sezione, Custom DBAPI connect() arguments, indica che gli argomenti di query vengono passati al DBAPI sottostante.

Quindi, che cosa fa il driver mysql-python di un parametro {charset: 'utf8'}? La sezione Functions and attributes della loro documentazione dice dell'attributo charset "...Se presente, il set di caratteri di connessione sarà cambiato in questo set di caratteri, se non sono uguali ".

per scoprire che cosa il mezzo set di caratteri di connessione, si rivolgono a 10.1.4. Connection Character Sets and Collations del manuale di riferimento di MySQL 5.6. Per fare una Per farla breve, MySQL può interpretare le query in entrata come una codifica diversa dal set di caratteri del database e diversa dalla codifica dei risultati della query restituita

Poiché il messaggio di errore che hai segnalato sembra un errore Python piuttosto che un errore SQL messaggio, speculerò che qualcosa in SQLAlchemy o mysql-python sta tentando di convertire la query in una codifica di connessione predefinita di latin-1 prima di inviarlo. Questo è ciò che attiva l'errore.nella tua chiamata connect() modifica la codifica della connessione e lo U+2026 HORIZONTAL ELLIPSIS è in grado di passare.

Aggiornamento:. anche voi chiedete, "se rimuovere l'opzione charset e quindi codificare la descrizione utilizzando .encode ('CP1252') si passerà attraverso bene Come è puntini di sospensione in grado di ottenere attraverso con CP1252 ma non unicode? "

Il encoding cp1252 has un carattere di ellissi orizzontale al valore di byte \x85. Pertanto è possibile codificare una stringa Unicode contenente U+2026 HORIZONTAL ELLIPSIS in cp1252 senza errori.

Ricorda anche che in Python, stringhe Unicode e stringhe di byte sono due diversi tipi di dati. È ragionevole ipotizzare che MySQLdb possa avere una politica di invio di sole stringhe di byte su una connessione SQL. Pertanto codificherebbe una query ricevuta come una stringa Unicode in una stringa di byte, ma lascerebbe una query ricevuta come una stringa di byte sola. (Questa è la speculazione, non ho guardato il codice sorgente.)

Nella traceback hai postato, le ultime due righe (più vicino a dove si verifica l'errore) mostrano i nomi dei metodi literal, seguita da unicode_literal. Ciò tende a supportare la teoria che MySQLdb codifica la query che riceve come una stringa Unicode in una stringa di byte.

Quando si codifica la stringa di query manualmente, si ignora la parte di MySQLdb che esegue questa codifica in modo diverso. Si noti, tuttavia, che se si codifica la stringa di query in modo diverso rispetto al collegamento con la connessione MySQL, si avrà una mancata corrispondenza della codifica e il testo verrà probabilmente memorizzato in modo errato.

+0

Questo quasi risponde. L'unica cosa è che se rimuovo l'opzione charset e poi codifico la descrizione usando .encode ('cp1252') andrà bene. In che modo i puntini di sospensione possono passare attraverso cp1252 ma non unicode? So che mi manca qualcosa ma non sono sicuro di cosa sia. – kvedananda

+0

Grazie! Sta iniziando a dare un senso ... – kvedananda

+0

+1 per indicare "hai risolto il problema aggiungendo la query? Charset = utf8 nella tua chiamata di connessione SQLAlchemy" – btk

0

Aggiunta charset=utf8 nella stringa di connessione aiuta sicuramente, ma ho incontrato situazioni in Python 2.7 Quando si aggiungono convert_unicode=True a create_engine stato anche necessario. La documentazione di SQLAlchemy dice che è solo per migliorare le prestazioni, ma nel mio caso ha effettivamente risolto il problema dell'encoder sbagliato utilizzato.