Seite 1 von 1

XML-Daten vor dem parsen encoden

Verfasst: Freitag 16. April 2010, 09:45
von sparrow
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

Verfasst: Freitag 16. April 2010, 10:08
von 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.

Verfasst: Freitag 16. April 2010, 10:25
von sparrow
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

Verfasst: Freitag 16. April 2010, 10:54
von 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.

Verfasst: Freitag 16. April 2010, 11:06
von sparrow
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.

Verfasst: Freitag 16. April 2010, 12:51
von Dav1d
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

Verfasst: Freitag 16. April 2010, 14:05
von sparrow
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

Verfasst: Freitag 16. April 2010, 18:16
von ms4py
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

Verfasst: Freitag 16. April 2010, 18:48
von sparrow
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]

Verfasst: Freitag 16. April 2010, 21:33
von BlackJack
@sparrow: Wie ich schon mal sagte: Der Parser will kein `unicode` haben sondern `str`.

Verfasst: Freitag 16. April 2010, 21:53
von ms4py
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'))

Verfasst: Freitag 16. April 2010, 21:55
von ms4py
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')) 

Verfasst: Samstag 17. April 2010, 12:29
von sparrow
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

Verfasst: Samstag 17. April 2010, 12:38
von sma
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

Verfasst: Samstag 17. April 2010, 13:06
von 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`.