[Python] Email vernünftig decodieren

Roin

Freier Denker
Registriert
22 Juli 2013
Beiträge
581
Hallo Leute,

ich muss für ein Projekt ein Programm in Python schreiben.
Als einer der ersten Schritte muss ich Dateien einlesen (.eml) und einige Zeilen daraus extrahieren.

Klappt auch mit einer kleinen for-Schleife.

Einziges Problem dabei ist, dass ich die Zeichenketten nicht vernünftig decodiert kriege.

Der Header der Emails sieht wie folgt aus:

Return-Path: <*>
Received: from * (*)
by * (Cyrus v2.4.16-Debian-2.4.16-4+deb7u1) with LMTPA;
Sun, 29 Mar 2015 16:16:14 +0200
X-Sieve: CMU Sieve 2.4
Received: from * ([*] helo=*)
by * with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:256)
(Exim 4.80 nylar)
id 1YcE0b-0006Li-NL
for *; Sun, 29 Mar 2015 16:16:13 +0200
To: * <*>
Subject: Betreff
From: * <*>
ReplyTo: * <*>
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
Date: Sun, 29 Mar 2015 16:16:11 +0200 (CEST)
Message-Id: <*>
X-IMT-Spam-Score: 0.8 ()
X-PMX-Version: 6.2.0.2453472, Antispam-Engine: 2.7.2.2107409, Antispam-Data: 2015.3.29.140620
X-PerlMx-Spam: Gauge=IIIIIIII, Probability=8%, Report='
FROM_NAME_ONE_WORD 0.05, HTML_00_01 0.05, HTML_00_10 0.05, SUPERLONG_LINE 0.05, URI_ENDS_IN_HTML 0, __ANY_URI 0, __CANPHARM_UNSUB_LINK 0, __CP_URI_IN_BODY 0, __CT 0, __CTE 0, __CT_TEXT_PLAIN 0, __FRAUD_INTRO 0, __HAS_FROM 0, __HAS_MSGID 0, __HIGHBITS 0, __MIME_TEXT_ONLY 0, __SANE_MSGID 0, __STOCK_PHRASE_7 0, __TO_MALFORMED_2 0, __URI_NS '
X-IMT-Authenticated-Sender:
(Sterne ersetzten diverse sensible Daten)

Ein einfaches einlesen mit UTF-8 als Codierung funktioniert leider nicht. Dann ist weiterhin alles "quoted-printable", also alle Sonderzeichen mit =xx ersetzt und das '=' mit =3D ersetzt.


[src=python]import codecs
import quopri
from email import quoprimime

def load(filename):
file = codecs.open(filename, "r", "utf-8");
# file = open(filename)
#
dec_file = ""
lines = ""
for line in file:
lines += line
#
dec_file = quopri.decodestring(lines)
# dec_file = quoprimime.decode(lines)
#
#
print dec_file[/src]
Damit sind zwar die '=' wieder in Ordnung, aber die Umlaute sind "kaputt"
ü = ü, ° = °
und so weiter ...

Gibt es da eine Möglichkeit das vernünftig zu machen oder muss ich mir die entsprechenden Ersetzungen durch dieses quoted-printable raussuchen und selbstständig ersetzten lassen?
 
Das scheint mir auf den ersten Blick nicht falsch zu sein. Das ist wie im Header angegeben die UTF-8 kodierung von "ü", wenn man es fälschlicherweise als ASCII interpretiert. Dein Problem ist also eher, dass print mit UTF-8 nichts anfangen kann. Der String an sich ist korrekt.
Um den String in das "default encoding" zu übersetzen kannst du eventuell sie String .decode() Methode verwenden:
(mit Beispiel für UTF-8)
 
  • Thread Starter Thread Starter
  • #3
Wenn ich bei der Ausgabe oder auch bereits vorher die .decode("UFT-8")-Methode nutze, bekomme ich allerdings immernoch keine vernünftige Ausgabe.

Nun werden alle Sonderzeichen und Umlaute als Kästchen dargestellt.
Scheint also auch nicht zu klappen.
 
UTF-8 oder Windows CP-*(Whatever) Codepages?
Wenn man das unter Windows versucht macht es halt mal schnell "boom" :D

Edit:
Code:
Expand Collapse Copy
locale charmap
zeigt Dir das aktuelle Encoding ... (Manpages mussten berufen werden..) auf Linux/UNIX/BSD an.
 
  • Thread Starter Thread Starter
  • #5
Wenn ich in der Konsole [src=python]locale.getdefaultlocale()[/src] aufrufe bekomme ich
('de_DE', 'cp1252')

Wenn ich nun allerdings in meinem Code den String mit dem Encoding ausgeben will, passiert da noch mehr Unfug mit.

Falls es hilft: Ich benutze Netbeans 8.1 mit dem Plugin Jython 2.7.x


EDIT:
Ich habe gerade einfach mal meinen Code in der Console Zeile für Zeile eingegeben, wie ich da gesehen habe, scheint es richtig ausgeführt zu werden.
Hat Netbeans irgendwo im Hintergrund eine Standard-Kodierung angegeben, mit der es alles kaputt macht?

EDIT2:
Wie kann ich nun den decodierten String "Zeilenweise" durchgehen?
Mit der obigen for-Schleife geht der ja Zeichenweise durch.
Müsste ich in der oben for-Schleife noch Umbrüche einfügen lassen?
 
Schreib mal folgende Zeile:
[src=python]dec_file = quopri.decodestring(lines)[/src]

zu folgendem um:

[src=python]dec_file = quopri.decodestring( codecs.encode(lines, "utf-8") )[/src]

Da du sonst im normalen Python Interpreter folgende Fehlermeldung bekommen wirst, in Python 2.7.x.

Zum Beispiel:
UnicodeEncodeError: 'ascii' codec can't encode characters in position 1613-1614: ordinal not in range(128)

Das bekomme ich beispielsweise in dieser Zeile ohne das zusätzliche codecs.encode(lines, "utf8")
[src=python]dec_file = quopri.decodestring(lines)[/src]

Bei mir funktioniert die Ausgabe mit print lines bzw. print dec_file allerdings dann bestens als UTF-8, checke ich gerade mit einer EML-Datei - allerdings erst nach dem Codecs fix.

Ist in Idle unter Linux alles korrekt.

---

Ich würde dir vorschlagen, installiere dir die normale Windows Version von Python 2.7.x und arbeite, so naiv es sich anhört, mit Idle.
Bei einem kleinen Skript tut das nicht weh und du hast auch nicht die Probleme das Netbeans oder Jython irgendwas verhunzen was nicht in einer normalen Python Installation der Fall wäre.
 
Die Linux Konsole kann im Gegensatz zu Windows mit Unicode umgehen.
Python verwendet wohl intern Unicode für strings. Für Windows könnte es vielleicht auch helfen vor der Ausgabe explizit auf cp1252 zu kodieren.
 
Unicode für Strings gibt es erst ab Version 3. Python 2.7.x hat als Standard noch Ascii.

Die Ausgabe war jetzt aber nicht in der Konsole, sondern im Shell-Ausgabefenster von Idle, in meinem Testdurchlauf.
 
  • Thread Starter Thread Starter
  • #9
[src=python]# -*- coding: utf-8 -*-

import codecs
import quopri
from email import quoprimime

def load(filename):
# Datei einlesen
file = codecs.open(filename, "r", "utf-8");
# file = open(filename)
#
dec_file = ""
txt = ""
# txt = file.readlines()

# Zusammenhängenden Text aus der Email machen
for line in file:
# print line
if line.strip().endswith("="):
txt += line.strip()[:-1]
else:
txt += line.strip() + "\r\n"
# Quoted-Pintable entfernen
dec_file = quopri.decodestring(codecs.encode(txt, "utf-8"))

# dec_file = dec_file.decode('UTF-8', 'strict')
# dec_file = quoprimime.decode(lines)
#
#
# dec_file = dec_file.decode("UTF-8", "ignore")

# Text nun zeilenweise verarbeiten
lines = dec_file.split("\r\n")

print lines
print dec_file

curr_line = 0
first_line = 0
last_line = 0
significant_lines = []

# print len(lines)


for line in lines[:]:
# Leere Zeilen entfernen
if line == u"":
lines.remove(line)
curr_line = curr_line - 1

# Erste Zeile bestimmen
elif "Wettermeldungen" in line:
first_line = curr_line + 1

# Letzte Zeile bestimmen
elif first_line > 0 and last_line == 0 and "=" in line:
last_line = curr_line - 1
# if "ü".decode("utf-8") in line:
# print line

curr_line = curr_line + 1

# print len(lines)
# print first_line
# print last_line

# Interessante Zeilen in eine Liste einlesen
significant_lines = lines[first_line:last_line+1]

# print len(significant_lines)
print significant_lines[/src]

Die Ausgabe von dec_file sieht jetzt gut aus.
Die Ausgabe von significant_lines sieht halt jetzt wie ne Liste aus, in der die Umlaute noch als \xc3 oder so stehen, aber solange der damit vernünfig arbeiten kann, bin ich erstmal zufrieden.

Damit habe ich dann schonmal aus ein paar Millionen Zeilen nur noch ein paar Tausend gemacht. (Ich muss über 2000 Emails mit den Datensätzen durch das Programm durchjagen).
Die Daten dann weiter zu extrahieren sollte erstmal kein weiteres Problem sein.

Ist es eventuell sinnvoll, die Daten noch weiter explizit auf eine Codierung festzulegen?
Da Netbeans ja anscheinend kein Python 3.x als Plugin hat, muss ich wohl vorerst in 2.7 programmieren, da ich auf meine gewohnte Umgebung erstmal nicht verzichten möchte.
 
Was dir vielleicht noch etwas entgegenkommt, das hier zu überfliegen:


Speziell hier dieser Part:


Ein minimaler Auszug:
Much Python code that operates on strings will therefore work with Unicode strings without requiring any changes to the code. (Input and output code needs more updating for Unicode; more on this later.)

Beantwortet das deine Frage bezüglich einer festgelegten Kodierung?
 
  • Thread Starter Thread Starter
  • #11
Also muss ich im Endeffekt einmal beim Einlesen von Daten sagen, dass das auch in UTF-8 sein soll (wenn die Text-Datei anders kodiert sein sollte) und das wars dann. Klingt doch nicht schlecht. :)
 
Zurück
Oben