web-dev-qa-db-fra.com

Écrire une chaîne UTF-8 dans MySQL avec Python

J'essaie de pousser les données de compte d'utilisateur d'un Active Directory vers notre serveur MySQL. Cela fonctionne parfaitement, mais d'une manière ou d'une autre, les chaînes finissent par afficher une version codée des trémas et autres caractères spéciaux.

Active Directory renvoie une chaîne en utilisant cet exemple de format: M\xc3\xbcller

Il s'agit en fait de l'encodage UTF-8 pour Müller, Mais je veux écrire Müller Dans ma base de données et non M\xc3\xbcller.

J'ai essayé de convertir la chaîne avec cette ligne, mais il en résulte la même chaîne dans la base de données: tempEntry[1] = tempEntry[1].decode("utf-8")

Si je lance print "M\xc3\xbcller".decode("utf-8") dans la console python la sortie est correcte.

Existe-t-il un moyen d'insérer cette chaîne correctement? J'ai besoin de ce format spécifique pour un développeur web qui veut avoir ce format exact, je ne sais pas pourquoi il n'est pas capable de convertir la chaîne en utilisant PHP directement.

Informations supplémentaires: j'utilise MySQLdb; Le codage des tables et des colonnes est utf8_general_ci

34
Raptor

J'ai trouvé la solution à mes problèmes. Le décodage de la chaîne avec .decode('unicode_escape').encode('iso8859-1').decode('utf8') a finalement fonctionné. Maintenant, tout est inséré comme il se doit. L'autre solution complète peut être trouvée ici: Travailler avec des chaînes encodées en unicode depuis Active Directory via python-ldap

9
Raptor

Comme le suggère @ marr75, assurez-vous de définir charset='utf8' sur vos connexions. Réglage use_unicode=True n'est pas strictement nécessaire car cela est implicite en définissant le jeu de caractères.

Assurez-vous ensuite que vous passez des objets nicode à votre connexion db car il les encodera en utilisant le jeu de caractères que vous avez passé au curseur. Si vous passez une chaîne encodée en utf8, elle sera doublement encodée lorsqu'elle atteindra la base de données.

Donc, quelque chose comme:

conn = MySQLdb.connect(Host="localhost", user='root', password='', db='', charset='utf8')
data_from_ldap = 'M\xc3\xbcller'
name = data_from_ldap.decode('utf8')
cursor = conn.cursor()
cursor.execute(u"INSERT INTO mytable SET name = %s", (name,))

Vous pouvez également essayer de forcer la connexion à utiliser utf8 en passant le paramètre init_command, bien que je ne sois pas sûr si cela est nécessaire. Un test de 5 minutes devrait vous aider à décider.

conn = MySQLdb.connect(charset='utf8', init_command='SET NAMES UTF8')

De plus, et cela vaut à peine la peine d'être mentionné car la version 4.1 est si ancienne, assurez-vous que vous utilisez MySQL> = 4.1

50
Rob Cowie

En supposant que vous utilisez MySQLdb, vous devez passer use_unicode = True et charset = "utf8" lors de la création de votre connexion.

MISE À JOUR: Si je lance ce qui suit sur une table de test, j'obtiens -

>>> db = MySQLdb.connect(Host="localhost", user='root', passwd='passwd', db='sandbox', use_unicode=True, charset="utf8")
>>> c = db.cursor()
>>> c.execute("INSERT INTO last_names VALUES(%s)", (u'M\xfcller', ))
1L
>>> c.execute("SELECT * FROM last_names")
1L
>>> print c.fetchall()
(('M\xc3\xbcller',),)

C'est "la bonne façon", les caractères sont stockés et récupérés correctement, votre ami qui écrit le script php ne gère tout simplement pas l'encodage correctement lors de la sortie.

Comme le souligne Rob, use_unicode et charset combinés sont verbeux sur la connexion, mais j'ai une paranoïa naturelle même sur les bibliothèques python les plus utiles en dehors de la bibliothèque standard, donc j'essaie d'être explicite pour rendre les bogues faciles à trouver si la bibliothèque change.

18
marr75
import MySQLdb

# connect to the database
db = MySQLdb.connect("****", "****", "****", "****") #don't use charset here

# setup a cursor object using cursor() method
cursor = db.cursor()

cursor.execute("SET NAMES utf8mb4;") #or utf8 or any other charset you want to handle

cursor.execute("SET CHARACTER SET utf8mb4;") #same as above

cursor.execute("SET character_set_connection=utf8mb4;") #same as above

# run a SQL question
cursor.execute("****")

#and make sure the MySQL settings are correct, data too
9
YEH

Récemment, j'ai eu le même problème avec la valeur du champ étant une chaîne d'octets au lieu d'unicode. Voici une petite analyse.

Aperçu

En général, tout ce qu'il faut faire pour avoir des valeurs unicode à partir d'un curseur, c'est passer l'argument charset au constructeur de connexion et avoir des champs de table non binaires (par exemple utf8_general_ci). Qui passe use_unicode est inutile car il est défini sur true chaque fois que charset a une valeur.

MySQLdb respecte les types de champs de description du curseur, donc si vous avez une colonne DATETIME dans le curseur, les valeurs seront converties en Python datatime.datetime instances, DECIMAL à decimal.Decimal et ainsi de suite, mais les valeurs binaires seront représentées telles quelles, par des chaînes d'octets. La plupart des décodeurs sont définis dans MySQLdb.converters , et on peut les remplacer en fonction de l'instance en fournissant l'argument conv au constructeur de connexion.

Mais les décodeurs Unicode sont une exception ici, ce qui est probablement un défaut de conception. Ils sont ajoutés directement aux convertisseurs d'instance de connexion dans son constructeur. Il n'est donc possible de les remplacer que sur instance-basic.

Workaround

Voyons le code du problème.

import MySQLdb

connection = MySQLdb.connect(user = 'guest', db = 'test', charset = 'utf8')
cursor     = connection.cursor()

cursor.execute(u"SELECT 'abcdё' `s`, ExtractValue('<a>abcdё</a>', '/a') `b`")

print cursor.fetchone() 
# (u'abcd\u0451', 'abcd\xd1\x91')
print cursor.description 
# (('s', 253, 6, 15, 15, 31, 0), ('b', 251, 6, 50331648, 50331648, 31, 1))
print cursor.description_flags 
# (1, 0)

Il montre que le champ b est retourné sous forme de chaîne d'octets au lieu d'unicode. Mais ce n'est pas binaire, MySQLdb.constants.FLAG.BINARY & cursor.description_flags[1] ( drapeaux de champ MySQLdb ). Cela ressemble à un bug dans la bibliothèque (ouvert # 9 ). Mais la raison pour laquelle je vois que MySQLdb.constants.FIELD_TYPE.LONG_BLOB (cursor.description[1][1] == 251, types de champs MySQLdb ) n'a tout simplement pas de convertisseur du tout.

import MySQLdb
import MySQLdb.converters as conv
import MySQLdb.constants as const

connection = MySQLdb.connect(user = 'guest', db = 'test', charset = 'utf8')
connection.converter[const.FIELD_TYPE.LONG_BLOB] = connection.converter[const.FIELD_TYPE.BLOB]
cursor = connection.cursor()

cursor.execute(u"SELECT 'abcdё' `s`, ExtractValue('<a>abcdё</a>', '/a') `b`")

print cursor.fetchone()
# (u'abcd\u0451', u'abcd\u0451')
print cursor.description
# (('s', 253, 6, 15, 15, 31, 0), ('b', 251, 6, 50331648, 50331648, 31, 1))
print cursor.description_flags
# (1, 0)

Ainsi, en manipulant l'instance de connexion converter dict, il est possible d'obtenir le comportement de décodage Unicode souhaité.

Si vous souhaitez remplacer le comportement, voici à quoi ressemble une entrée dict pour un champ de texte possible après le constructeur.

import MySQLdb
import MySQLdb.constants as const

connection = MySQLdb.connect(user = 'guest', db = 'test', charset = 'utf8')
print connection.converter[const.FIELD_TYPE.BLOB]
# [(128, <type 'str'>), (None, <function string_decoder at 0x7fa472dda488>)]

MySQLdb.constants.FLAG.BINARY == 128. Cela signifie que si un champ a un drapeau binaire, ce sera str, sinon le décodeur unicode sera appliqué. Donc, vous voulez également essayer de convertir des valeurs binaires, vous pouvez faire apparaître le premier tuple.

5
saaj

(Je voudrais répondre à la réponse ci-dessus mais je n'ai pas assez de réputation ...)

La raison pour laquelle vous n'obtenez pas de résultats Unicode dans ce cas:

>>> print c.fetchall()
(('M\xc3\xbcller',),)

est un bogue de MySQLdb 1.2.x avec le classement * _bin, voir:

http://sourceforge.net/tracker/index.php?func=detail&aid=1693363&group_id=22307&atid=374932 http://sourceforge.net/tracker/index.php?func=detail&aid=2663436&group_id= 22307 & atid = 374932

Dans ce cas particulier (classement utf8_bin - ou [n'importe quoi] _bin ...), vous devez vous attendre à la valeur "brute", ici utf-8 ( oui, ça craint car il n'y a pas de correctif générique).

2
lacorbeille

et db.set_character_set ('utf8'), impliquent que use_unicode = True?

0
Sérgio

il y a une autre situation peut-être un peu rare.

si vous créez d'abord un schéma dans mysqlworkbench, vous obtiendrez l'erreur de codage et ne pourrez pas la résoudre en ajoutant une configuration charset.

c'est parce que mysqlworkbench crée un schéma par latin1 par défaut, vous devez donc définir le jeu de caractères au début! enter image description here

0
dogewang