BS wandelt die Inhalte nicht (immer) in Unicode?

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 hätte da mal wieder ein Problem:

Ich parse eine HTML-Seite mit BS und frage an einer bestimmten Stelle (Tabelle) auf den Inhalt ab (in diesem Beispiel die linke Seite einer HTML-Tabelle). Wenn da was entsprechendes steht, 'hole' ich mir den Wert der rechten Spalte.
Soweit so gut.
Jetzt habe ich eine Liste mit 30-40 URLs die ich 'abarbeite' und plötzlich, bei URL 20 tritt ein Fehler auf.
Kein Syntax-Fehler sondern der Wert, den ich abfrage, ist plötzlich 'ganz anders'.
Optisch zu sehen ist es das selbe wie bei allen anderen Inhalten, aber es tritt kein 'match' ein (was es aber sollte).

Nun habe ich mir die betreffende Stelle ausgeben lassen und da kommen wirre Zeichen an den Stellen wo deutsche Umlaute stehen würden.
Ich bin eine URL zurückgegangen und da steht das gleiche Wort, wird aber korrekt ausgegeben!
Ich habe in beiden Fällen nun ein "print type(elem.contents[0])" angewiesen und beide male 'unicode' erhalten.
Aber wieso kein Match und wieso plötzlich diese Sonderzeichen?

Ich frage wie folgt ab:

Code: Alles auswählen

if tmpElem=="Größe:".decode("iso-8859-1"):
.
.
wobei tmpElem den Inhalt der linken Spalte beinhaltet.

Was läuft hier schief und wie kann ich es beheben?
Ein Workaround hätte ich, aber ich will ja python lernen (Es ist die einzigste Spalte die als Inhalt 'Gr' beginnend hat. Das würde ja reichen aber ich will es ja korrekt machen).
BlackJack

@kaineanung: Wie passt die Aussage das bei ``print type(elem.contents[0])`` in beiden Fällen 'unicode' ausgegeben wird, zu der Betreffzeile wo steht das „die Inhalte nicht (immer) in Unicode“ umgewandelt werden?

Ich würde ja an der Stelle mal den Quelltext der beiden Seiten vergleichen, denn es kann ja auch sein, dass dort schon irgendwelcher ”Datenmüll” steht. Da kann BS dann auch nichts machen. Garbage in, garbage out.

``"Größe:".decode("iso-8859-1")`` ist übrigens reichlich umständlich. Sinnvoller wäre es in dem Quelltext den Kodierungskommentar richtig zu setzen und dann ein Unicode-Literal zu schreiben: ``u"Größe"``. Ist weniger Schreibarbeit und wenn man die Kodierung mal ändern möchte, dann muss man die Kodierungsangabe nur einmal am Anfang des Quelltextes ändern statt überall nach `decode()`-Aufrufen auf Zeichenkettenliteralen zu suchen.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@BlackJack

Wie gesagt: optisch steht auf der HTML-Seite als auch im HTML-Code eindeutig 'Größe:' da.
Lediglich wenn ich es ausgebe in er Konsole bekomme ich bei den fehlerfreien Durchgängen das Wort so auch ausgegeben und bei dem fehlerhaften Gang eben irgendwelches 'GrĂśĂ�e:' (<-- copy & paste).

Zu der umständlichen codierung:
Habe

Code: Alles auswählen

if tmpElem==u'Größe':
versucht und bekomme dann folgendes um die Ohren geschmissen:
SyntaxError: (unicode error) 'utf8' codec can't decode byte 0xf6 in position 0: invalid start byte
Dann habe ich ein wenig herumprobiert und das mit dem

Code: Alles auswählen

if tmpElem=="Größe:".decode("iso-8859-1"):
hat wunderbar funktioniert. Aber vielleicht hängt das Problem mit meinem ersteren Zusammen und wenn ich eines Löse kann ich plötzlich auch das andere Lösen und eben auf 'u"Größe:"' abfragen?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaineanung: kannst Du mal die tatsächliche Repräsentation in Python posten? Du weißt, dass in Unicode Zeichen in verschiedenen Normalformen dargestellt werden können? Es sieht aber so aus, als ob Dein Browser mit dem raten des Encodings mehr Erfolg hat, als Du.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@Sirius3
Mein Browser macht Ratespiele? Was für ein Schlawiner ;)

Im Ernst:
ich versteh nicht ganz was du mit 'tatsächlicher Repräsentation' meinst?
Was soll ich machen? Sobald ich weiß wie, werde ich das nach der Arbeit dann mal posten...
Benutzeravatar
/me
User
Beiträge: 3554
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

kaineanung hat geschrieben:Habe

Code: Alles auswählen

if tmpElem==u'Größe':
versucht und bekomme dann folgendes um die Ohren geschmissen:
SyntaxError: (unicode error) 'utf8' codec can't decode byte 0xf6 in position 0: invalid start byte
Hast du in deinem Code ein passendes Source Code Encoding angegeben und den Sourcecode dann auch wirklich in diesem Encoding gespeichert?
BlackJack

@kaineanung: Wenn Du ``u"Größe"`` in den Quelltext schreibst, dann musst Du die Kodierung als Kodierungskommentar oben im Quelltext angeben. Und zwar die Kodierung in der der Quelltext tatsächlich vorliegt. Sonst kann der Compiler die Bytes im Quelltext nicht passend dekodieren und Du bekommst den gezeigten `SyntaxError`.

Sirius3 meint die Bytewerte an der Stelle im HTML. Also ohne das BS da irgendwas versucht, sondern tatsächlich die Bytes die Du vom Server bekommst in einer Darstellung die eindeutig ist weil sie nicht von irgendwelchen Kodierungen im Terminal, Texteditor, oder Webbrowser abhängen. Also reines ASCII. Das was `repr()` in Python 2 und `ascii()` in Python 3 aus den Daten machen.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@/me

Meinst du das am Anfang einer jeden PY-Quellcodedatei (Shebang oder irgendwie so ähnlich)?

Ja, das sollte korrekt eingegeben sein wie folg:

Code: Alles auswählen

#!/usr/bin/env python
#-*- coding: utf-8 -*-
Ich habe an anderer Stelle, einem anderen Script (gleiches Projekt) jedoch mit der MySQL-DB Probleme gehabt und nach etwas googeln kam mir der Verdacht das mit dem coding im Script-Kopf entwas nicht stimmen könnte.
Ich habe dann mal das Leerzeichen zwischen -*- und coding entfernt und die Probleme waren weg.
Hatte ich dann auch hier im Verdacht aber es hat nichts genützt.
Also egal ob ich

Code: Alles auswählen

#!/usr/bin/env python
#-*- coding: utf-8 -*-
#oder
#!/usr/bin/env python
#-*-coding: utf-8 -*-
stehen habe -> das Ergebnis ist so wie beschrieben...

@BlackJack

Habe es nochmals kontrolliert: Quellcode-Datei habe ich soeben von ASCII nach UTF-8 gewandelt (im UltraEdit). Im Kopt steht die o.g. Codierung UND es passiert erst nach 20 erfolgreichen Durchgängen auf gleicher Seite nur mit neuem Inhalt.

Ich habe versucht mein tmpElem mit dem Zusatz .repr() auszugeben.
Fehler:
AttributeError: 'unicode' object has no attribute 'repr'
BlackJack

@kaineanung: Natürlich ist das Ergebnis immer gleich — falsch. Du hattest vorher `decode()` ja auch nicht mit utf-8 als Kodierung aufgerufen. Du musst in dem Kommentar die Kodierung angeben in der die Datei auch *tatsächlich* kodiert ist.

Und das ändert nichts an dem Problem mit der Webseite. Der Kommentar gilt für die Unicode-Literale in der Quelltextdatei und nur dafür.

`repr()` ist eine Funktion und keine Methode. Ausserdem verwendest Du das falsche Objekt, wir müssen wissen wie die Bytedaten aussehen die der Server liefert. Wenn Du da ein Unicode-Objekt hast, dann ist ja bereits eine Dekodierung geschehen und man kann nicht mehr ohne weitere Angaben sagen wie die ursprünglichen Daten ausgesehen haben.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@BlackJack

Wenn ich
repr(self.html)
in eine Datei schreibe und dann die entsprechende Stelle suche, finde ich folgendes:
Gr\xc3\xb6\xc3\x9fe:
Kannst du damit was anfangen?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Das ist offenbar einUTF-8 codierter String! Dabei werden ö als "c3 b6" und ß als "c3 9f" repräsentiert. Das kannst Du z.B. hier nachgucken.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@Hyperion

Wenn ich aber mit utf-8 decode wie folgt:

Code: Alles auswählen

if tmpElem == "Größer".decode("utf-8"):
  print("match!")
else:
  print("no match!")
dann liefert er mit kein match....
in tmpElem sind nur die entsprechenden Werte vorzufinden welche ja auch bis zu dieser Stelle funktioniert haben.

Wenn ich mit

Code: Alles auswählen

if tmpElem[:2]=="Gr":
dann bekomme ich den match.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Mit welcher Python Version arbeitest Du? Python 2? (Auch wenn Du ``print`` wie eine Funktion verwendest) Denn in Python 3 geht ein ``decode`` auf einem String ja nicht.

Also: Du hast auf der einen Seite einen codierten String (``tmpElem`` - unschöner Name btw!) und vergleichst den auf der anderen mit einem *Unicode* Objekt. Denn genau das bewirkt doch ``decode``: Es wandelt einen codierten String in einen Unicode String um. Ergo sind das zwei verschiedene Typen von Zeichenkettenrepräsentationen - und da ist es nun einmal sehr wahrscheinlich, dass die Bytes darin nicht gleich sind ;-)

Woher stammt denn ``tmpElem``? Ich würde ja eher *das* decodieren und dann mit einem Unicode-String vergleichen also eher so:

Code: Alles auswählen

tmpElem.decode("utf-8") == u"Größer"
Wenn ich den Thread richtig überflogen habe, wurde das doch auch schon mal empfohlen?

Alternativ belässt Du beides in UTF-8 Codierung (was ich i.A. in Python nicht machen würde) und vergleichst die codierten Strings miteinander.

Ich formuliere es mal so pur aus Verdacht heraus: UTF-8 != Unicode!!! Wenn Dir das unklar ist, schau Dir mal die Links in meiner Signatur an :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@Hyperion

Code: Alles auswählen

tmpElem.decode("utf-8") == u"Größer"
habe ich schon probiert bevor ich hier meine Frage gepostet hatte.

Ergebnis:
UnicodeEncodeError: 'ascii' codec can't encode characters in position 2-5: ordinal not in range(128)
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@kaineanung: Du dekodierst einen UTF-8 String als Latin1 und versuchst das dann nochmal zu dekodieren. Wie kommt so ein Chaos zustande?
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@Sirius3

Indem ich es erst mit uft-8 decodiere und dann o.g. Fehlermeldung erhalte.
Dann, probeweise, einige andere Codesets probiere und siehe da -> es hat geklappt.
Warum? Weis ich nicht -> dachte mir das der Inhalt den ich von BS bekommen habe (in diesem tmpElem) dann schon irgendwo und irgendwie in das iso-8859-1 codiert wurde? k.A. aber das Ergebnis hat gestimmt bis irgendwann mal (also Nummer 21 von 30 parsevorgängen die ich jederzeit wiederholen kann und es immer nur an diesem 21. scheitert)
Als Anfänger weis man ja nicht welches Objekt was genau wo macht (codiert oder der Gleichen)..
BlackJack

An der Stelle wollte ich jetzt nur noch mal anmerken das BS eigentlich immer Unicode liefert wenn das eine Rolle spielt, also sollte man `tmpElem` nicht dekodieren, denn das ist ja schon dekodiert. So hat kaineanung das ja irgendwo am Anfang ja auch gesagt. Andererseits wissen wir nicht wirklich was für komische Sachen vorher vielleicht schon mit den Daten angestellt wurden und die Informationen sind hier auch nicht wirklich zuverlässig, denn bei ``tmpElem.decode("utf-8") == u"Größer"`` hat man auch kein ``True`` als Ergebnis wenn `tmpElem` tatsächlich eine Bytezeichenkette ist die UTF-8 kodiert ist, aber 'Gr\xc3\xb6\xc3\x9fe:' enthält, denn 'Größe:' ist nun mal was anderes als 'Größer'.
kaineanung
User
Beiträge: 145
Registriert: Sonntag 5. April 2015, 20:57

@BlackJack

Das mit dem Größe und Größer war nur ein 'Verschreiber' von mir.
Natürlich prüfe ich auf ein "Größe:"
Und das habe ich jetzt nochmals nachgeprüft.

repr() auf die gesamten HTML liefert im entsprechenden Bereich:

Code: Alles auswählen

Gr\xc3\xb6\xc3\x9fe:
Ich benutze BeuatifulSoup um mich an die Stelle (Tabelle) 'heranzuschachteln' mit

Code: Alles auswählen

#mit repr() ist also verifiziert das im entsprechendem tmpElem "Gr\xc3\xb6\xc3\x9fe:" enthalten ist.
#Folgendes mache ich:
#Liste mit allen TR-Tags (mit dessen TH und TD)
result = self.soup.find("div", {"class": "css-klassenname"}).find("table, {"class": "css-tabellenklassenname"}).find_all("tr")
  #Die Liste wird durchgeschleift
  for elem in result:
    #und bei jedem Schleifendurchgang der Content des TH-Tag extrahiert und dem tmpElem zugewiesen
    tmpElem = elem.find("th").contents[0].strip()
    #Nun wird auf den Inhalt des tmpElem (also des TH-Tags der TR der Tabelle) geprüft ob sich das Wort 'Größe:' darin befindet
    if tmpElem == u"Größe:":
      #Das Wort 'Größe:' befindet sich im TH -> Hole den Content des dazugehörigen TDs und mach was auch immer damit
      meineVariable = elem.find("td").contents[0].strip()
#usw.
So, am Inhalt von BS habe ich demzufolge nichts geändert. Es hat auch funktioniert (sonst würde es ja bei den anderen zu parsenden HTML-Seiten ja auch nicht klappen).
Es ergibt kein True/Match obwohl es sollte.
Auch wenn ich

Code: Alles auswählen

if tmpElem == "Größe:".decode("iso-8859-1"):
#oder
if tmpElem == "Größe:".decode("utf-8"):
verwende -> Kein match. Bei den vorherigen 20 Seiten hat es aber an dieser Stelle einen Match gegeben! Die Seite hat sich ja nicht geändert sondern lediglich die dynamsichen Inahlte?

Wenn ich

Code: Alles auswählen

if tmpElem.decode("utf-8") == u"Größe:":
Gibt es die Fehlermeldung:
UnicodeEncodeError: 'ascii' codec can't encode characters in position 2-5: ordinal not in range(128)
Also egal wie rum, bei dieser speziellen Stelle gibt es kein Match!

Ich kann natürlich meinen Workaround machen und nur auf tmpElem[:2] == "Gr" abfragen. Aber das Projekt mache ich um python zu erlernen und halbwegs zu verstehen. Damit würde ich mir ja selber keinen Gefallen machen...
BlackJack

@kaineanung: Und was wird ausgegeben wenn Du vor dem ``if`` die `repr()`-Darstellung von `tmpElem` und ``u"Größe:"`` ausgibst?

Und wie sehen die jeweiligen Daten bei den anderen Seiten aus?
Antworten