XML-Daten vor dem parsen encoden

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
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Hallo Forum,

ich sitze hier im Augenblick an einem gar garstigen Problem. Und zwar möchte ich zu Testzwecken gerne XML Parsen um mich der Unicode-Problematik anzunähern.

Ich versuche dafür die Wetter-Api von Google auszulesen:

Code: Alles auswählen

# -*- coding: utf-8 -*-

import urllib
import xml.dom.minidom as dom

def getWeather(village):
    try:
        params = urllib.urlencode({'weather': village})
        f = urllib.urlopen("http://www.google.com/ig/api?%s" % params)
        tree = dom.parse(f)
        retstr = "!OK!"
    except Exception, e:
        if "list index out of range" in str(e):
            retstr = "No information found"
        else:
            retstr = "!FAILED!"
            raise
    return retstr

if __name__ == "__main__":
    v = "Lübeck"
    print getWeather(v)
Das funktioniert eigentlich mit allen Orten ganz gut, solange in der Antwort, also den XML-Daten keine Umlaute enthalten sind. Da aber in diesem Beispiel in den XML-Daten steht, dass es sich um den Ort "Lübeck" handelt, müsste das ganze als Unicode interpretiert werden.

Fogender Fehler tritt auf:

Code: Alles auswählen

Traceback (most recent call last):
  File "weather.py", line 22, in <module>
    print getWeather(v)
  File "weather.py", line 10, in getWeather
    tree = dom.parse(f)
  File "/usr/lib/python2.5/xml/dom/minidom.py", line 1915, in parse
    return expatbuilder.parse(file)
  File "/usr/lib/python2.5/xml/dom/expatbuilder.py", line 928, in parse
    result = builder.parseFile(file)
  File "/usr/lib/python2.5/xml/dom/expatbuilder.py", line 207, in parseFile
    parser.Parse(buffer, 0)
xml.parsers.expat.ExpatError: not well-formed (invalid token): line 1, column 171
Ich habe bereits versucht den f in unicode zu wandeln, das funktioniert leider auch nicht:

Code: Alles auswählen

# -*- coding: utf-8 -*-

import urllib
import xml.dom.minidom as dom

def getWeather(village):
    try:
        params = urllib.urlencode({'weather': village})
        f = urllib.urlopen("http://www.google.com/ig/api?%s" % params)
        fu = unicode(f.read(), "utf-8", errors="replace")
        tree = dom.parseString(fu)
        retstr = "!OK!"
    except Exception, e:
        if "list index out of range" in str(e):
            retstr = "No information found"
        else:
            retstr = "!FAILED!"
            raise
    return retstr

if __name__ == "__main__":
    v = "Lübeck"
    print getWeather(v)

Code: Alles auswählen

Traceback (most recent call last):
  File "weather.py", line 23, in <module>
    print getWeather(v)
  File "weather.py", line 11, in getWeather
    tree = dom.parseString(fu)
  File "/usr/lib/python2.5/xml/dom/minidom.py", line 1925, in parseString
    return expatbuilder.parseString(string)
  File "/usr/lib/python2.5/xml/dom/expatbuilder.py", line 940, in parseString
    return builder.parseString(string)
  File "/usr/lib/python2.5/xml/dom/expatbuilder.py", line 223, in parseString
    parser.Parse(string, True)
UnicodeEncodeError: 'ascii' codec can't encode character u'\ufffd' in position 171: ordinal not in range(128)

Könnte mir hier vielleicht jemand freundlicherweise auf die Sprünge helfen?

Ich danke im Voraus,


sparrow
BlackJack

@sparrow: Da würde ich gar nichts machen ausser vielleicht Google eine E-Mail schicken, dass sie *immer noch* kaputtes XML rausschicken. Da kommt nämlich XML ohne Kodierungsangabe zurück das nicht UTF-8-kodiert ist, wie der Standard das in dem Fall vorsieht.

Da das Problem aber schon ziemlich lange besteht, scheint Google das einfach egal zu sein.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Das die, eigentlich nötige, Information über die Kodierung nicht mit in der XML-Datei steht ist mir auch schon aufgefallen.

Wenn ich die entsprechende XML im Firefox öffne werden die Umlaute korrekt dargestellt. Die Seiteninformation gibt dann Auskunft darüber, dass das angezeigt Format "UTF-8" ist. Daher bin ich davon ausgegangen, dass die Seite mit entsprechender Kodierung ausgeliefert wird.

Ist es vielleicht möglich, dass innerhalb des XML-Parsers vielleicht das Problem auftritt? Wenn ich die Fehlermeldung richtig lesen 'ascii' codec can't encode) wird die XML-Datei dort als ASCII-Format behandelt, obwohl ich vorher ja extra einen Unicode-String erzeugt habe?
Gibt es vielleicht die Möglichkeit den String einmal zu encoden und anschließend wieder zu decoden? Mir ist gar nich wichtig, dass die Umlaute erhalten bleiben, aber ein Fehler bedeutet halt, dass es gar keine Rückgabge gibt.


Gruß
Sparrow
BlackJack

@sparrow: XML-Parser wollen `str` und nicht `unicode` deshalb versucht Python hier Dein `unicode` wieder in `str` zu wandeln bevor der Parser das zu sehen bekommt. Und dabei wird ASCII als Kodierung angenommen und wenn etwas ausserhalb von ASCII in dem `unicode`-Objekt vorkommt, dann kracht's.
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Habe ich dann im Vorfeld die Möglichkeit etwas dagegen zu tun, dass es kracht? Quasi einen String zu bauen der da durchläuft?
Vielleicht irgendwie mit dem magischen "errors='raplace'"

So fit bin ich der Unicode-Geschichte leider nicht.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Bei mir funktioniert es: http://blog.dav1d.de/code/google-weather-api-parsen/
Das Beispiel arbeitet mit lxml und/oder BeautifulSoup, man kann allerdings auch xml.etree.cElementTree als Parser benutzen
the more they change the more they stay the same
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Dav1d hat geschrieben:Bei mir funktioniert es: http://blog.dav1d.de/code/google-weather-api-parsen/
leider nein.
Ich musste zu Beginn den import ändern damit das Programm unter 2.6 überhaupt funktioniert (da scheint bei dir im Blog eh ein Fehler zu sein, denn wenn ich das richtig sehe gibt es noch keine Python-Version größer > 3).

Bei der Ausführung:

Code: Alles auswählen

>>> ================================ RESTART ================================
>>> 
Ort/Location: Berlin
Sprache/Language: de
{'current_conditions': {u'condition': u'Mostly Cloudy'},
 'forecast_conditions': [{u'day_of_week': u'Fri'},
                         {u'day_of_week': u'Sat'},
                         {u'day_of_week': u'Sun'},
                         {u'day_of_week': u'Mon'}],
 'forecast_information': {u'city': u'Berlin, Berlin'}}
>>> ================================ RESTART ================================
>>> 
Ort/Location: Lübben
Sprache/Language: de
Error: 
>>> ================================ RESTART ================================
>>> 
Ort/Location: München
Sprache/Language: de
Error: 
Das mit Umlauten klappt hier also auch nicht.

Gruß
Sparrow
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

So funktioniert bei mir das Dekodieren:

Code: Alles auswählen

# coding: utf-8

import urllib

params = urllib.urlencode({'weather': 'Lübeck'})
f = urllib.urlopen("http://www.google.com/ig/api?%s" % params)
content = f.read()
content.decode('latin-1')
Noch ein paar Infos zu deinem Code:
* Nie "Exception" abfangen, wenn nicht wirklich nötig, sondern immer eine spezifische
* Der Erfolg einer Operation in einem Return-String zurückzugeben ist extremer Code-Smell. Dazu gibt es Exceptions oder "None", wenn du keine Daten auslesen konntest
* Schau dir mal PEP-8 an
„Lieber von den Richtigen kritisiert als von den Falschen gelobt werden.“
Gerhard Kocher

http://ms4py.org/
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Ah super.

Wenn ich das richtig sehe ist das XML-Kram von Google als Latin-1 kodiert.

Danke.


Edit: zu früh gefreut. Funktioniert nicht. Dein Code ist zu kurz. Der Fehler tritt erst auf sobald der Parser aufgerufen wird.

Code: Alles auswählen

# -*- coding: utf-8 -*-

import urllib
import xml.dom.minidom as dom

def getWeather(village):
    params = urllib.urlencode({'weather': village})
    f = urllib.urlopen("http://www.google.com/ig/api?%s" % params)
    content = f.read()
    tree = dom.parseString(content.decode('latin-1'))

if __name__ == "__main__":
    v = "Lübeck"
    print getWeather(v)

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/sparrow/test.py", line 14, in <module>
    print getWeather(v)
  File "/home/sparrow/test.py", line 10, in getWeather
    tree = dom.parseString(content.decode('latin-1'))
  File "/usr/lib/python2.6/xml/dom/minidom.py", line 1928, in parseString
    return expatbuilder.parseString(string)
  File "/usr/lib/python2.6/xml/dom/expatbuilder.py", line 940, in parseString
    return builder.parseString(string)
  File "/usr/lib/python2.6/xml/dom/expatbuilder.py", line 223, in parseString
    parser.Parse(string, True)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xfc' in position 171: ordinal not in range(128)
[/code]
BlackJack

@sparrow: Wie ich schon mal sagte: Der Parser will kein `unicode` haben sondern `str`.
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Mit lxml geht es:

Code: Alles auswählen

# coding: utf-8

import urllib

from lxml import etree

params = urllib.urlencode({'weather': 'Lübeck'})
f = urllib.urlopen("http://www.google.com/ig/api?%s" % params)
content = f.read()
etree.fromstring(content.decode('latin-1'))
„Lieber von den Richtigen kritisiert als von den Falschen gelobt werden.“
Gerhard Kocher

http://ms4py.org/
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

Oder so mit minidom ;)

Code: Alles auswählen

# coding: utf-8

import urllib
import xml.dom.minidom as dom

params = urllib.urlencode({'weather': 'Lübeck'})
f = urllib.urlopen("http://www.google.com/ig/api?%s" % params)
content = f.read()
dom.parseString(content.decode('latin-1').encode('utf-8')) 
„Lieber von den Richtigen kritisiert als von den Falschen gelobt werden.“
Gerhard Kocher

http://ms4py.org/
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Ah, super danke!

Verstehe ich das jetzt richtig, dass der Ablauf in etwa so ist:

das XML-Dokument wird gelesen und daraus unter Benutzung von der Kodierung "latin-1" ein Unicode-Objekt erstellt.
Anschließend wird das Unicode Objekt (das "utf-8"-kodiert ist) mit .decode wieder in str umgewandelt?

Demnach haben Unicode-Objekte immer UTF-8-Kodierung?

Danke!

sparrow
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Wie schon gesagt, ist das Wetter-API ein ganz schlechtes Beispiel, um etwas über Zeichenkodierungen zu lernen, denn die Antwort von Google ist schlichtweg falsch. Sie behaupten im XML-Header, dass sie UTF-8 schicken werden, tuen es aber offensichtlich nicht. Das ist falsch!

Ich würde das `<?xml version="1.0"?>` durch `<?xml version="1.0" encoding="ISO-8859-1"?>` ersetzen und dann erst das Dokument einem XML-Parser übergeben.

Dann sollte es funktionieren.

Zu beachten ist noch, dass alles, was in den XML-Parser geht, kein String ist sondern ein Byte-Array (Typ bytes gemäß Python 3.x). Erst der Parser macht daraus dann unter Berücksichtigung der Zeichenkodierung Strings (Typ unicode in Python 2.x und Typ str in Python 3.x).

Stefan
BlackJack

@sparrow: `unicode`-Objekte haben keine Kodierung. Die werden intern irgendwie dargestellt -- wie genau, dass kann Dir als Programmierer egal sein. Und `decode()` ist für den Weg `str` nach `unicode`.
Antworten