web-dev-qa-db-ja.com

PythonでMySQLにUTF-8文字列を書き込む

Active DirectoryからMySQLサーバーにユーザーアカウントデータをプッシュしようとしています。これは問題なく機能しますが、文字列はウムラウトやその他の特殊文字のエンコードされたバージョンを表示することになります。

Active Directoryは、次のサンプル形式を使用して文字列を返します:_M\xc3\xbcller_

これは実際には_Müller_のUTF-8エンコードですが、_Müller_ではなく_M\xc3\xbcller_をデータベースに書き込みたいです。

この行で文字列を変換しようとしましたが、データベース内の同じ文字列になります:tempEntry[1] = tempEntry[1].decode("utf-8")

pythonコンソールでprint "M\xc3\xbcller".decode("utf-8")を実行すると、出力は正しくなります。

この文字列を正しい方法で挿入する方法はありますか?この正確な形式を使用したいWeb開発者には、この特定の形式が必要です。PHPを直接使用して文字列を変換できない理由はわかりません。

追加情報:MySQLdbを使用しています。テーブルと列のエンコードはutf8_general_ciです

34
Raptor

問題の解決策を見つけました。 .decode('unicode_escape').encode('iso8859-1').decode('utf8')を使用した文字列のデコードは、ようやく機能しました。これで、すべてが必要に応じて挿入されました。他の完全なソリューションはここにあります: python-ldapを介してActive DirectoryからUnicodeエンコードされた文字列を操作する

9
Raptor

@ marr75が示唆するように、必ずcharset='utf8'接続。設定use_unicode=Trueは、文字セットを設定することで暗示されるため、厳密にである必要はありません。

次に、カーソルに渡した文字セットを使用してエンコードするので、db接続にnicodeオブジェクトを渡してください。 utf8でエンコードされた文字列を渡す場合、データベースに到達すると二重にエンコードされます。

だから、次のようなもの:

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,))

また、init_command paramを渡すことでutf8を使用するように接続を強制することもできますが、これが必要かどうかはわかりません。 5分間のテストが決定に役立ちます。

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

また、4.1は非常に古いため、言及する価値はほとんどありません。MySQL> = 4.1を使用していることを確認してください。

50
Rob Cowie

MySQLdbを使用していると仮定すると、接続を作成するときにuse_unicode = Trueとcharset = "utf8"を渡す必要があります。

更新:私が得るテストテーブルに対して次を実行すると-

>>> 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',),)

これは「正しい方法」であり、文字は正しく保存および取得されますが、PHPスクリプトを記述している友人は、出力時にエンコードを正しく処理していません。

Robが指摘しているように、use_unicodeとcharsetの組み合わせは接続に関して冗長ですが、標準ライブラリ以外の最も有用なpythonライブラリでさえも自然な妄想を持っているので、ライブラリが変更された場合にバグを見つけやすくします。

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

最近、フィールド値がユニコードではなくバイト文字列であるという同じ問題がありました。少し分析します。

概要

一般に、カーソルからユニコード値を得るために必要なことは、charset引数を接続コンストラクターに渡し、非バイナリテーブルフィールド(utf8_general_ciなど)を持たせることです。 use_unicodeを渡すことは、charsetに値がある場合は常にtrueに設定されるため、役に立ちません。

MySQLdbはカーソルの説明フィールドタイプを尊重するため、カーソルにDATETIME列がある場合、値はPython datatime.datetimeインスタンス、DECIMALからdecimal.Decimalなどに変換されます。ほとんどのデコーダーは MySQLdb.converters で定義されており、接続コンストラクターにconv引数を指定することでインスタンスごとにオーバーライドできます。

ただし、Unicodeデコーダーはここでは例外であり、これはおそらく設計上の欠点です。これらは、コンストラクタ内の接続インスタンスコンバータに対して 直接追加 です。したがって、instance-basicでのみオーバーライドできます。

Workaround

課題コードを見てみましょう。

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)

bフィールドがUnicodeではなくバイト文字列として返されることを示しています。ただし、バイナリではなく、MySQLdb.constants.FLAG.BINARY & cursor.description_flags[1]MySQLdbフィールドフラグ )。ライブラリのバグのようです( #9 )。しかし、その理由は、MySQLdb.constants.FIELD_TYPE.LONG_BLOBcursor.description[1][1] == 251MySQLdbフィールドタイプ )にはコンバーターがまったくありません。

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)

したがって、接続インスタンスconverter dictを操作することにより、目的のUnicodeデコード動作を実現できます。

振る舞いをオーバーライドしたい場合は、コンストラクターの後に、可能なテキストフィールドのdictエントリがどのように見えるかを示します。

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。これは、フィールドにバイナリフラグがある場合はstrになり、それ以外の場合はUnicodeデコーダーが適用されることを意味します。したがって、バイナリ値も変換しようとする場合は、最初のタプルをポップできます。

5
saaj

(上記の回答に返信したいが、評判が足りない...)

この場合、Unicodeの結果が得られない理由は次のとおりです。

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

は、MySQLdb 1.2.xからのバグです* _bin照合、参照:

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

この特定のケース(collat​​ionutf8_bin-または[anything] _bin ...)では、「raw」値、ここではutf-8(はい、これは一般的な修正がないので残念です。

2
lacorbeille

およびdb.set_character_set( 'utf8')、use_unicode = Trueを意味しますか?

0
Sérgio

もう少しまれな状況もあります。

最初にmysqlworkbenchでスキーマを作成すると、エンコードエラーが発生し、charset設定を追加しても解決できません。

mysqlworkbenchはデフォルトでlatin1によってスキーマを作成するため、最初に文字セットを設定する必要があるためです! enter image description here

0
dogewang