Problem mit deutschen Umlauten und MySQL.

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

Hallo Leute,

ich habe da mal wieder ein Problem.
Ich habe zwar ein Buch/Tutorial zu Python gelesen (Galileo OpenBook Python) und auch die Stelle mit den Unicodes und dem Encodeing und Decodeing gelesen, aber ehrlich gesagt nicht alles verstanden.

Aktuelles Problem:
Ich habe eine Textvariable welche ich in meine MySQL-Datenbank inserten möchte.
Der SQL-Befehl ist auch schnell und einfach erstellt mit

Code: Alles auswählen

"Insert into tablename (feld1, feld2) values ('" + value1 + "','" + value2 + "')"
feld1 und feld2 sind jeweils textfelder (vchar(11) und textfeld).

So, beim ausführen kommt solange kein Fehler bis ich auf ein deutschen Umlaut im value2 stosse. Also sobald ein deutscher Umlaut enthalten ist gibt es eine Fehlermeldung:
"UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 336: ordinal not in range(128)"

Ok, habe mir gedacht ich wandel den String erst einmal um nach UTF-8, meine MySQL-DB bzw. die Felder sind ja alle in utf-8-general_ci (oder andersherum geschrieben?).

Code: Alles auswählen

mein_str = mein_str.decode('utf-8')
# habe esauch mit mein_str = mein_str.decode('utf-8', 'replace') versucht
Aber ich bekomme wieder eine Fehlermeldung:

"UnicodeDecodeError: 'ascii' codec can't encode character u'\xdf' in position 371: ordinal not in range(128)"


Könnt ihr mir bitte helfen? Ich habe grade kein Plan was zu tun ist?
Ich danke euch schonmal im Voraus für eure Mühe!
BlackJack

@kaineanung: Erstmal vorweg: Nein so erstellt man keine SQL-Befehle! Nie! Wer Werte von Hand in einen SQL-Befehl in Form einer Zeichenkette einfügt, kann sich auch gleich in den Fuss schiessen. Das ist eine riesen Sicherheitslücke und ineffizient dazu. In die SQL-Anweisung gehören Platzhalter und die Werte lässt man dann vom Datenbankmodul *sicher* dort einfügen in dem man bei `execute()` die SQL-Anweisung und die Daten als getrennte Argumente übergibt.

Man muss das mit Unicode und den Kodierungen in der Regel *komplett* verstehen und wissen was an welcher Stelle in welcher Form erwartet wird. Wenn man mit Text arbeitet dann möchte man intern in der Regel mit Unicode arbeiten und nicht mit Bytestrings. Das heisst wenn `value1` oder `value2` Text sind der als Bytestrings irgendwie in das Programm gelangt, dann sollte man die so früh wie möglich zu Unicode dekodieren. Welche Kodierung man dafür benutzen muss, muss man *wissen*!

Wenn man die Werte dann an die Datenbank übergibt muss die wissen wie sie mit Unicode-Objekten umgehen soll/muss. Es kann sein dass die Datenbank damit automatisch umgehen kann, es kann aber auch sein das man diverse Einstellungen vornehmen muss die sich von Datenbank zu Datenbank aber auch von Modul zu Modul für die selbe Datenbank unterscheiden. Für MySQL gibt es ja einen ganzen Zoo von Modulen welche die DB-API V2 umsetzen. Also müsstest Du dort in der Dokumentation mal schauen was da zu dem Thema steht. Soweit ich weiss kann man bei MySQL kann man für die Tabellen oder sogar Spalten eine Kodierung setzen, für die Serverseite, für die Verbindung, und für die Clientseite. Bei `MySQLdb` kann man beim `connect()`-Aufruf beispielsweise ein `charset`-Argument übergeben was dann auch gleichzeitig dazu führt das die Datenbank Texte auch als Unicode-Objekte liefert.

Ich persönlich mag ja SQLAlchemy als Abstraktionsschicht über verschiedene Datenbanksoftware und SQL. :-)
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@BlackJack

Meinst du mit dem Einfügen mit Zeichenketten den Ablauf wie man auch formatierten Text ausgeben würde?

Also:

Code: Alles auswählen

con.execute("Insert into tablename (field1, field2) vaules ('%s', '%s')" % (value1, value2)
?

Bitte geschwind auch die Hochkommata beachten denn in einem Beispiel habe ich das ohne Hochkommata gefunden trotz eines Text-Feldes?

Was das Unicode angeht:
Ich habe mein Skript im 'Header' als Unicode definitert: # coding=utf-8
Ich befülle die String-Variable mit Werten aus dem gescrapten HTML-Dokument und wende an einer Stelle das Skriptes so an (habe ich mir aus einem Beispiel adaptiert):

Code: Alles auswählen

		word = ''
		for tmp in result.findAll(text=True):
			word = unicode(tmp).encode("utf-8")
			if source == '':
				source = word
			else:
				blocks.append(word)

                completed_str = analyse("".join(blocks))
Das ist die Stelle nachdem ich das per BeutifulSoup geparst habe und das Ergebnis der result-Liste zurückgeliefert habe.
Ich wandle also eine Zeile (ein Element der result-Liste) in eine tmp-Variable mit uft-8-Zeichencode um.
Dann rufe ich meine Funktion 'analyse' auf und übergebe den 'fertigen' Block (als Liste) als Parameter ohne das ich irgendwas mehr am Zeichencode ändere. Dort bearbeite ich es (filtere ungewünschte Zeilen heraus) und liefere eine (neue) Zeichenkette zurück. Beim schreiben fällt mir dabei auf das ich diese neue Zeichenkette aber so zurückliefere wie erstellt. Vielleicht da nochmals Zeichenkette wandeln?

Jedenfalls soll der Rückgabewert (Zeichenkette) in die DB inserted werden und da hapert es (s.o.).

Ausser an der gerade erkannten Stelle fällt mir sonst nirgends auf das ich Zeichenketten benutze und wandeln sollte?
Was die DB angeht: Standard-Installation von LAMP (ich benutze als Plattform einen Ubuntu Core-Server) mit utf-8-general-ci Tabellen und Colletaration.

Nebenbei:
Ubuntu-Core-Sever ist auch die Plattform auf welchem mein Python-Programm läuft. Ich habe eine Samba-Verezeichnisfreigabe in welcher ich dann von meinem Windows-Rechner aus die Python-Scripte per UltraEdit 'bearbeite'. Aber das sollte ja nichts ausmachen was den Zeichencode in meinen Zeichenketten-Variablen angeht.

Wo soll ich hier nun ansetzen? Sollte ich die Zeichenkette vor der Rückgabe in meiner Funktion 'analyse' noch umkodieren? Das sollte doch nichts ausmachen wenn ich es nachdem ich es zurückgeliefert habe, umkodiere? Also nach de. oben geposteten Code folgendes mache:

Code: Alles auswählen

completed_str = completed_str.decode('utf-8')
Da ich das ja bereits mache kann ich sagen: das bringt mir aber nichts :cry:
BlackJack

@kaineanung: Du bastelst die Werte ja immer noch *selbst* in die Zeichenkette *bevor* das dann an `execute()` übergeben wird. Schau Dir das Beispiel ohne Hochkommata noch mal an. Wenn da statt des ``%`` etwas anderes steht, dann ist dass das richtige. Die Werte müssen ein eigenes, getrenntes Argument für den `execute()`-Aufruf sein. Die Dokumentation zum `execute()`-Aufruf, notfalls direkt im PEP das die DB-API V2 beschreibt oder für ein anderes Datenbankmodule (SQLite in der Standardbibliothek beispielsweise), sollte weiterhelfen.

Der Kodierungskommentar am Anfang eines Moduls sagt dem Compiler wie der Quelltext des Moduls kodiert ist. Das hat zur Laufzeit sonst keine weiteren Auswirkungen.

BeautifulSoup liefert Texte als Unicode, warum wandelst Du die in Bytes um? Nochmal: Wenn Du mit Texten arbeitest, dann willst Du Unicode haben und keine Bytestrings. Auf keinen Fall möchtest Du innerhalb der Verarbeitung an mehreren Stellen hin und her wandeln. Wenn man mit Text arbeitet sollte man die Bytes sobald sie ins Programm kommen in Unicode-Objekte umwandeln und erst so spät wie möglich bevor sie das Programm verlassen in Bytes. In Deinem Fall sollte gar kein eigenes (de)kodieren nötig sein weil BeautifulSoup das dekodieren übernimmt und die Datenbank das enkodieren wenn man sie entsprechend konfiguriert hat.

Bei der Datenbank hast Du jetzt noch nicht verraten welches der vielen Python-Module Du verwendest. Bei `MySQLdb` muss man beim `connect()` wie schon gesagt die Zeichensatzkodierung angeben damit sich das Modul bei Texten automatisch ums (de)kodieren kümmert. UTF-8 ist da die naheliegende Wahl.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@BlackJAck

Ok, ich habe ja bereits gesagt das ich mein Code an der Stelle des parsens aus einem Beispiel 'adaptiert' hatte. Da war das encoden auf utf-8 angegeben und ich habe es übernommen.
Habe ich jetzt alles rausgemacht weil BeautifulSoup ja schon in utf-8 Zeichenketten bereitstellt.

Die Hochkommatas habe ich rausgemacht aus meinem Beispiel weil mit funktioniert es nicht, ohne schon.

Ich benutze MySQLdb als Bibliothek.
Wenn ich das aber so alles peichere, also die Zeichenkette aus BeutifulSoup nicht in utf-8 konvertiere und auch beim inserten in der DB ohne jegliche konvertierung arbeite, dann gibt es wieder Fehler?!

Also nochmals für mein Verständnis:
1. BeuatifulSoup liefert schon alles in UTF-8
2. Ich muss in meinem ganzen Programm NICHT konvertieren solange ich mit dem String daraus arbeite. Was passiert mit Variablen denen ich diesen String zuweise?
3. da nachwievor alles in UTF-8 ist muss ich auch beim übergeben in die DB nicht konvertieren?

Naja, so in die Richtung habe ich es jetzt angepasst und dennoch gibts fehlermeldungen :cry:
BlackJack

@kaineanung: 1. Nein BeautifulSoup liefert *Unicode*-Objekte, also echte *Zeichen*ketten, nicht *Byte*ketten die vielleicht Text in irgendeiner Kodierung darstellen. Die haben keine Kodierung, die brauchen auch keine. Kodierung braucht man nur wenn man die internen Zeichen(ketten) als Bytes speichern möchte, denn dann muss man sagen wie die einzelnen Zeichen denn als Byte(folge) kodiert werden sollen.

2. Was soll mit ”Variablen” passieren? Was ist für Dich hier eine Variable?

3. Wie bei 1.: Nichts ist zwischen BeautifulSoup und Cursor-Objekt UTF-8 kodiert sondern idealerweise sind Texte die ganze Zeit Unicode-Objekte. Damit `MySQLdb` die dann verarbeiten kann (und bei Abfragen auch welche liefert) muss man beim `connect()`-Aufruf eine Kodierung angeben. UTF-8 bietet sich an weil man damit jedes Unicode-Zeichen kodieren kann.
Antworten