spezielle zeichen und encodierung

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
Sunjy
User
Beiträge: 31
Registriert: Dienstag 3. März 2009, 19:13
Wohnort: 127.0.0.1
Kontaktdaten:

Hallo Leute,
Ich verzweifel gerade an einem "ö" und bitte um eure Hilfe dabei.
Der Buchstabe liegt mir in der Form "ö" vor.

Die Meldung ist bei jedem versuch die selbe:

Code: Alles auswählen

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
Meine Ratlosigkeit, wie ich den Buchstaben lesbar mache ist wohl auch damit zu begründen, dass ich mich im Gebiet der Schriftkodierungen recht wenig auskenne.
Irgendwann ist man dann aber auch so genervt, dass man einfach alles versucht (ohne Erfolg):

Code: Alles auswählen

"ö".encode("ascii") #Mit "ignore" bzw "replace" parametern auch schon versucht
"ö".encode("utf-8")
str("ö").encode("utf-8")
str("ö").encode("ascii") #Mit "ignore" bzw "replace" parametern auch schon versucht
Wenn mir da Jemand weiterhelfen könnt wäre echt klasse. :)

Grüße,
Sunjy
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich hätte ja eher zu ``decode()`` mit ``utf-8`` oder ``iso-8859-1`` geraten, aber da kommt auch nichts sinnvolles raus. Wäre halt praktisch, das verwendete Encoding zu wissen.
BlackJack

@Sunjy: Erst einmal solltest Du heraus finden welche Daten du *wirklich* hast. Denn "ö" ist ja schon eine Darstellung der Daten bei der irgendeine Kodierung angenommen wurde. Welche war das? Wie sehen die Bytewerte aus, die tatsächlich gespeichert sind?
Benutzeravatar
Sunjy
User
Beiträge: 31
Registriert: Dienstag 3. März 2009, 19:13
Wohnort: 127.0.0.1
Kontaktdaten:

Also ich parse mit dem Modul html5lib einen Text, vllt hilft das schonmal weiter.

zusätzlich hab ich mir jetzt mal ein Modul namens chardet zur Hilfe genommen und auf das Objekt, welches Probleme macht angewendet, dabei kommt

Code: Alles auswählen

{'confidence': 1.0, 'encoding': 'ascii'}
als Antwort heraus. Kann das überhaupt sein ? Wenns Ascii wäre, sollte es doch eigentlich keine Probleme machen ?!

Bzgl. Bytewerte musst du mir auf die Sprünge helfen, wie ich an die rankomm.
Ich hab da nur jetzt was von wegen hex(ord(buchstabe)) im Kopf, aber kann auch gut, sein dass ich da in nem komplett anderen Bereich bin :O
lunar

@Sunjy: chardet wird Dir nicht helfen. Kodierung sind nicht sicher zu bestimmen, folglich rat auch chardet nur. Manchmal klappt das, und manchmal eben nicht…

Lass Dir die Bytewerte mittels "ord()" oder die Literaldarstellung mittels "repr()" ausgeben. Diese Darstellungen sind im Gegensatz zu dem, was Du im ersten Beitrag gezeigt hast, roh und unkodiert, so dass die Daten nicht durch die erneute Umkodierung verstümmelt werden.

Es wäre vielleicht auch sinnvoll, die Quelle dieser Daten zu erklären.
BlackJack

@Sunjy: Worauf hast Du `chardet` denn angewendet? Auf den HTML-Quelltext? Der kann ja durchaus nur ASCII sein, denn Umlaute lassen sich in HTML auch durch Entity-Referenzen kodieren, also zum Beispiel 'ö' für ein 'ö'. Da sind nur ASCII-Zeichen enthalten.

`html5lib` sollte doch eigentlich schon Unicode liefern? Klappt dann vielleicht die Erkennung der Kodierung des HTMLs nicht richtig? Das wäre Pech, denn da könnte man dann nicht wirklich etwas gegen machen. Ausser raten. Beziehungsweise das raten `chardet` überlassen.
Benutzeravatar
Sunjy
User
Beiträge: 31
Registriert: Dienstag 3. März 2009, 19:13
Wohnort: 127.0.0.1
Kontaktdaten:

die Quelle ist im Grunde nur ein HTML String der mittels html5lib und beautifulsoup geparst wird. Also nichts besonderes, weshalb genau dieser bei mir solche Probleme macht kann ich leider nicht wirklich sagen, ich weis nur, dass bei einem Tag dessen Inhalt ein "ö" enthält eben immer besagte Fehlermeldung kommt.

Code: Alles auswählen

>>> import html5lib
>>> text = "<p>Möge es Strings regnen</p>"
>>> parsed = html5lib.parse(text, "beautifulsoup")
>>> print parsed.findAll("p")

Traceback (most recent call last):
  File "<pyshell#32>", line 1, in <module>
    print parsed.findAll("p")
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf6' in position 4: ordinal not in range(128)
Ich hab mir jetzt mal nen Weg mit regulären ausdrücken gemacht, das war zwar net geplant aber naja, macht fast keinen unterschied.

Ich finde es nur schade, es anders zu lösen, weil mich die Lösung interessieren würde. Irgendwann könnt ichs bestimmt mal gebrauchen und käme net anders dran vorbei.

/edit: chardet hab ich net auf den html-string angewendet, das is ja klar dass des nix bringt ^^ ich habs glaub auf parsed.findAll(...)[0] oder .contents ich bin mir nimmer 100% sicher, jedenfalls auf ein Element der Ausgabe von findall
BlackJack

@Sunjy: Wie ist `text` denn überhaupt kodiert? Das sieht man hier ja nicht. Und Dir ist klar dass das Hauptproblem hier gar nicht das parsen ist, sondern dass hier versucht wird implizit Unicode zu kodieren was bei Python mit 'ascii' passiert und damit immer auf die Nase fällt wenn etwas ausserhalb von ASCII im Unicode-Objekt steht!?

Du solltest Dich mal ganz dringend mit Unicode und Kodierungen und dem Unterschied zwischen Zeichen und Bytes befassen und Dir klar machen was Du in welchem Verarbeitungsschritt genau vor Dir hast.
Benutzeravatar
Sunjy
User
Beiträge: 31
Registriert: Dienstag 3. März 2009, 19:13
Wohnort: 127.0.0.1
Kontaktdaten:

BlackJack hat geschrieben:@Sunjy: Wie ist `text` denn überhaupt kodiert? Das sieht man hier ja nicht. Und Dir ist klar dass das Hauptproblem hier gar nicht das parsen ist, sondern dass hier versucht wird implizit Unicode zu kodieren was bei Python mit 'ascii' passiert und damit immer auf die Nase fällt wenn etwas ausserhalb von ASCII im Unicode-Objekt steht!?
ich kanns dir nicht sagen... einfach mal in der idle so eingetippt halt... x)
BlackJack hat geschrieben: Du solltest Dich mal ganz dringend mit Unicode und Kodierungen und dem Unterschied zwischen Zeichen und Bytes befassen und Dir klar machen was Du in welchem Verarbeitungsschritt genau vor Dir hast.
wie bereits erwähnt ich kenn mich da bisher garnicht aus. vllt sollte ich das lieber doch einmal angehen, bevor ich hier versuche eine Lösung zu bekommen die ich dann im Endeffeckt eh net verstehn werd.


Also Ich danke euch für die bisherige Hilfe. Ich befass mich mich demnächst mal etwas mehr mit dem Thema, alles andere bringt jetzt nichts.
Da ich das Problem jetzt sowieso umgangen habe, kann der Thread im Grunde geschlossen werden. Oder zur weiteren Diskussion geöffnet bleiben, mir egal. Ich denk nur ich klinke mich dann jetzt aus.

Grüße
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Die Bytefolge, um die es geht, scheint 195, 131, 194, 182 zu sein.

Wenn überhaupt, ist "decode()" und nicht "encode()" richtig.

Eine HTML-Seite hat standardmäßig das Encoding ISO-8859-1 a.k.a. ISO Latin-1 (ein Superset von ASCII), häufig wird aber fälschlicherweise CP1252 oder ISO-8859-15 aka ISO Latin-9 benutzt. ISO-8859-1 bzw -15 (das ist die Variante mit dem Euro-Zeichen) sind typisch für Westeuropa. In Osteuropa gibt es andere Varianten - im Rest der Welt ist es noch mal komplett anders. CP1252 ist das westeuropäische Encoding von Windows, sehr ähnlich zu ISO-8859-1, doch der dort leere Bereich zwischen 128 und 159 ist hier auch belegt (z.B. mit einem Euro-Zeichen).

Heutzutage sind die Seiten häufig UTF-8-kodiert.

Und danach sieht das aus.

Das Beispiel b"\xc3\x83\xc2\xb6" decodiert gemäß UTF-8 ist 'ö' bzw. die Bytefolge 195, 182. Das ist wiederum eine UTF-8-Kodierung, denn dekodiert man b"\xc3\xb6", ist das ein kleines ö.

Mit anderen Worten, das ist doppelt kodiertes UTF-8 oder ziemlicher Murks.

Um eine HTML-Seite korrekt zu parsen, muss man zunächst einmal alles als Bytefolgen und nicht als Zeichen interpretieren. Python 2.x ist da leider suboptimal, denn es nennt das einen String (genauer Bytestring), was eine Bytefolge ist und das, was eigentlich ein richtiger String ist, ein Unicode-Objekt. Wer also mit str-Objekten hantiert, sollte immer davon ausgehen, dass dies Bytefolgen sind und keine Strings. Sie werden erst durch dekodieren mit einem Encoding zu einem String.

Der HTTP-Header kann nun sagen, wie die Bytes einer HTML-Seite zu interpretieren sind.

Funktioniert das nicht, kann man man vorsichtig schauen, ob man am Anfang der Datei einem BOM - einen Byte-Order-Marker stehen hat. Daran lassen sich verschiedene UTF-Kodierungen erkennen (neben UTF-8 gibt es auch UTF-16 und UTF-16 in zwei Geschmacksrichtungen und noch ein fast unbekanntes UTF-7).

Gibt es so etwas nicht (denn eigentlich hat das da nichts zu suchen), kann man nach der Processing Instruction <?xml ..?> suchen, denn vielleicht soll das ja ein XHTML-Dokument sein. Auch nicht ganz standardkonform (denn es müsste als "application/xml" und nicht "text/html" ausgeliefert werden, was sich nie durchgesetzt hat), kommt aber vor und in der PI kann das Encoding stehen.

Man sollte aber besser davon ausgehen, das der erste Teil des Dokuments wohl ISO-8859-1 ist und nach einem <meta>-Tag im <head> suchen, der über HTTP-EQUIV oder das ab HTML5 gültige charset-Attribut das Encoding definiert. Dann hat man's auch schon.

In der Regel versuchen Browser aber auch zu raten, denn viele Webseiten sind leider falsch und benutzen ein Encoding (wie z.B. CP1252) ohne es auch anzugeben, da die Autoren ungebildete Idioten, äh, streichen, ich meine natürlich normale Menschen sind, die den ganzen Standard- und Encoding-Wirrwarr nicht verstehen.

Langer Rede kurzer Sinn: Alles was man lädt als Bytefolge in den HTML-Parser stopfen und wenn er gut ist, wird dieser das "heavy lifting" übernehmen. Im HTTP-Header wird meist sowieso nix angegeben oder es stimmt nicht, weil das nicht der Autor der Seite beeinflussen kann, sondern vom Webserver so (und falsch) vorgegeben wird.

Stefan
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Kannst du vielleicht einfach mal die Website zeigen, um die es geht? Was übrigens auch viel kann, wenn es um das Parsen von HTML-Kauderwelsch geht, ist lxml.html.soupparser. Dazu müssen selbstredend LXML und BeautifulSoup installiert sein. Dein Bespiel sähe dann so aus:

Code: Alles auswählen

>>> from lxml.html import soupparser
>>> doc = soupparser.fromstring('<p>Möge es Strings regnen</p>')
>>> doc.findall('p')
[<Element p at b7426b9c>]
>>> doc.findall('p')[0].text
u'M\xf6ge es Strings regnen'
Anmerkend sei erwähnt, dass bei nur einem ``p``-Element kein ``.findall()`` genutzt werden muss:

Code: Alles auswählen

>>> doc.find('p').text
u'M\xf6ge es Strings regnen'
Benutzeravatar
Sunjy
User
Beiträge: 31
Registriert: Dienstag 3. März 2009, 19:13
Wohnort: 127.0.0.1
Kontaktdaten:

snafu hat geschrieben:Kannst du vielleicht einfach mal die Website zeigen, um die es geht?
http://www.mtv.de/charts/germany
vorgehen ist das aller selbe wie in meinem obigen Beispiel, nur mit nem "td" statt "p" und eben statt text = blabla nutz ich mechanize zum öffnen der Seite (open().read())

Worum es aber mittlerweile geht ist ja schon lange nicht mehr die Website. sogar eine normale Zuweisung Text > Variable macht ja in meinem Beispiel schon ärger.
Sprich, womit ich mich beschäftigt habe ist, wie ich aus einem "\x-bla" ein "ö" oder "ä" oder sonstiges mache.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Was in deinem Beispiel Ärger macht ist aber nicht die Zuweisung, sondern der Aufruf von ``str()`` auf das Objekt, welcher implizit durch das ``print``-Statement getätigt wird. Python will hier halt ASCII nutzen, das allerdings hat keinen ausreichenden Zeichensatz, so dass ein Fehler geworfen wird. Wenn du die Daten von vornherein im richtigen Encoding vorliegen hättest, müsstest du dich darum nicht mehr kümmern. Daher mein Vorschlag.

Falls es dir jetzt sozusagen lowlevelmäßig um das Dekodieren bzw Enkodieren von Zeichenketten geht, dann muss man wie gesagt wissen, welche Kodierung vorliegt. Alles andere (also Raten) macht wenig Sinn. Bei einem HTML-Dokument könntest du die ganz unten auf der von mir verlinkten Seite erwähnte BeautifulSoup-Klasse ``UnicodeDammit`` zur Erkennung mal ausprobieren. Diese probiert erst einige eigene Erkennungsmethoden aus und fällt später auf ``chardet`` zurück, falls vorhanden.

Auch hier ein Beispiel:

Code: Alles auswählen

from BeautifulSoup import UnicodeDammit
html = dein_ungeparster_seiteninhalt
encoding = UnicodeDammit(html).triedEncodings[-1]
Benutzeravatar
Sunjy
User
Beiträge: 31
Registriert: Dienstag 3. März 2009, 19:13
Wohnort: 127.0.0.1
Kontaktdaten:

ich schaus mir morgen nach der arbeit mal an, für heute is schluss ^^
Antworten