Seite 1 von 1

Umlaute und Sonderzeichen imaplib / email

Verfasst: Dienstag 12. Juli 2022, 18:15
von Grobekelle
Guten Abend zusammen,

mit dem nachfolgenden Code rufe ich Mails mit einem bestimmten Betreff ab und ziehe mir dann aus dem Text einen Namen.
Soweit klappt das auch, solange der Name keine Umlaute hat oder ggf. Sonderzeichen.

Wie bekommen ich das gelöst?

Vorab vielen Dank für Tipps und Ratschläge!

Code: Alles auswählen

with imaplib.IMAP4_SSL(host="mail.server.de", port=imaplib.IMAP4_SSL_PORT) as imap_ssl:

    resp_code, response = imap_ssl.login("xyz@xyz.de", "Passwort")

    resp_code, mail_count = imap_ssl.select(mailbox="Inbox", readonly=True)

    resp_code, mails = imap_ssl.search(None, '(UNSEEN SUBJECT "%s")' % subject)

    for mail_id in mails[0].decode().split()[-2:]:
        resp_code, mail_data = imap_ssl.fetch(mail_id, '(RFC822)') ## Fetch mail data.
        message = email.message_from_bytes(mail_data[0][1]) ## Construct Message from mail data
        for part in message.walk():
            if part.get_content_type() == "text/plain":
                body_lines = part.as_string().split("\n")
                content = "\n".join(body_lines)
                #Get customer name
                startStringBefore = 0 
                stopStringBefore = content.index(stringBefore) + len(stringBefore)
                if len(content) > stopStringBefore:
                    content = content[0: startStringBefore:] + content[stopStringBefore +1::]
                start1 = content.index(stringAfter)
                stop1 = len(content)
                if len(content) > stopStringBefore:
                    content = content[0: start1:] + content[stop1 +1::]
                content = content.strip()
                print(content)
    imap_ssl.close()

Re: Umlaute und Sonderzeichen imaplib / email

Verfasst: Mittwoch 13. Juli 2022, 06:54
von Grobekelle
UPDATE:

Workaround, unter Verwendung der imap_tools Bibliothek ist die Ausgabe wie gewünscht.

Ich habe jedoch Stunden lang recherchiert um dem Problem auf die Schliche zu kommen.

Dabei habe ich folgendes herausgefunden:

Es handelt sich um die Codierung cp1252. Müller wird zu M=FCller
Decode und Encode sollten unter Angabe des Zeichensatzes das Problem lösen.
Für den Zeichensatz gibt es entsprechende Header. Zum Beispiel # -*- coding: utf-8 -*-
Dabei muss die Datei dem Zeichensatz entsprechend gespeichert werden.

Ich habe alles erdenkliche ausprobiert, nichts brachte das gewünschte Resultat.
Auch wenn ich das Problem mit dem Workaround lösen konnte, möchte ich doch gerne wissen, wie ich das Problem gelöst bekomme.

PS: Ich komme ursprünglich aus dem .Net Bereich - Das hier sind meine ersten Gehversuche in Python.

VIele Grüße

Re: Umlaute und Sonderzeichen imaplib / email

Verfasst: Mittwoch 13. Juli 2022, 08:16
von __deets__
Du zeigst nicht, was du dann getan hast. Darum kann man da auch schwer was zu sagen. Außer eines: deine Anmerkungen zum Header sind nicht korrekt. Der hat keinen Einfluss auf die Ein- und Ausgaben deines programs. Sondern bezieht sich ausschließlich auf etwaige Unicode-Literale (also zb „hallo“) in deinem Code selbst. Das wars.

Re: Umlaute und Sonderzeichen imaplib / email

Verfasst: Mittwoch 13. Juli 2022, 12:15
von Sirius3
Ich habe erst lange gebraucht, um überhaupt zu verstehen, was das Problem ist, und dass es sich eigentlich nur um die imap_sll.search-Zeile handelt.

Erster Tipp wäre immer, in die Dokumentation zu schauen, was denn dieser erste Parameter denn bedeutet: IMAP4.search(charset, criterion[, ...])
Und wenn man ein Beispiel braucht: https://stackoverflow.com/questions/564 ... -1#5970286

Den Mailinhalt erst per `split` in Zeilen aufzuteilen um gleich danach die Zeilen wieder in einen String zu packen, ist ziemlich unsinnig.
Variablennamen schreibt man komplett klein.
startStringBefore ist immer 0, so dass ein content[0:0:] immer leer ist.
Die erste if-Abfrage `len(content) > stopStringBefore` ist immer wahr, weil die Länge von content immer größer ist, als der Index des Ende des Wortes, das man gerade gesucht hat, weil sonst wäre das Wort ja nicht in `content`.
Die zweite if-Abfrage `len(content) > stopStringBefore` macht keinen Sinn, weil sich die Länge von `content` geändert hat.
stop1 macht auch keinen Sinn, denn wenn das die Länge des Strings ist, dann ist content[len(content)+1::] immer leer.
Die zweiten : in den Slices sind unnötig.
Das führt zu:

Code: Alles auswählen

content = part.as_string()

# Get customer name
start_index = content.index(string_before) + len(string_before)
stop_index = content.index(string_after, start_index)
content = content[start_index:stop_index].strip()
print(content)
Statt mit Slicing und Index Strings zu verarbeiten, kennt Python viel mächtigere Methoden:

Code: Alles auswählen

content = content.partition(string_before)[2].partition(string_after)[0].strip()

Re: Umlaute und Sonderzeichen imaplib / email

Verfasst: Mittwoch 13. Juli 2022, 13:33
von __blackjack__
@Sirius3: Ist die Zeile tatsächlich das Problem? Ich dachte es ging um den Namen *in* der Mail. Da würde ich eher sagen das `part.as_string()` ist das Problem, weil man da eigentlich gar nicht den kompletten Part inklusive Headerzeilen möchte, sondern eigentlich den Payload von dem Part und am besten schon mal eine ”Stufe” dekodiert, also ohne das „Content-Transfer-Encoding“. Was `get_payload()` mit entsprechendem Argument macht. Und das müsste man dann noch von `bytes` in eine Zeichenkette dekodieren, mit der Zeichenkodierung die in den Headern angegeben ist. Also so etwas wie ``content = part.get_payload(decode=True).decode(part.get_content_charset())``.

Re: Umlaute und Sonderzeichen imaplib / email

Verfasst: Mittwoch 13. Juli 2022, 13:37
von Sirius3
@__blackjack__: beide Stellen sind ein Problem.

Re: Umlaute und Sonderzeichen imaplib / email

Verfasst: Mittwoch 13. Juli 2022, 15:41
von __blackjack__
@Grobekelle: Zusätzlich zu dem was schon gesagt wurde: Schlüsselwort-Argumente können Code manchmal klarer machen, aber wo das nicht wirklich einen Mehrwert bringt, kann man Argumente auch einfach nur über die Position angeben. Und Defaultwerte zu übergeben macht nicht so wirklich Sinn, denn genau dafür das man das nicht machen muss, gibt es ja Defaultwerte.

Wenn man mit Rückgabewerten nichts macht, muss man sie auch nicht an Namen binden. Sollte man das trotzdem tun, ist es nicht unüblich Namen die nicht benutzt werden mit einem führenden Unterstrich zu kennzeichnen, damit der Leser (und statische Code-Analyse) weiss, dass es weder ein Fehler noch unfertiger Code ist, sondern dass der Name tatsächlich nicht (wirklich) verwendet wird.

Wenn man sich keinen sinnvollen Namen ausdenken möchte oder auch einfach den Namen so ”unsichtbar” wie möglich zu machen, ist übliche Konvention einfach einen nur einen Unterstrich als Namen zu verwenden.

Den ``%``-Operator zur Zeichenkettenformatierung würde ich nicht mehr verwenden wenn es dafür nicht einen guten Grund gibt. Dafür gibt es f-Zeichenkettenliterale oder die `format()`-Methode.

Der `close()`-Aufruf ist überflüssig, dafür ist ja die ``with``-Anweisung da.

Ungetestet:

Code: Alles auswählen

    with IMAP4_SSL("mail.server.de") as imap:
        imap.login("xyz@xyz.de", "Passwort")
        imap.select("Inbox", readonly=True)
        _, mails = imap.search(None, f'(UNSEEN SUBJECT "{subject}")')
        for mail_id in mails[0].decode().split()[-2:]:
            _, mail_data = imap.fetch(mail_id, "(RFC822)")
            for part in email.message_from_bytes(mail_data[0][1]).walk():
                if part.get_content_type() == "text/plain":
                    print(
                        part.get_payload(decode=True)
                        .decode(part.get_content_charset())
                        .partition(text_before)[2]
                        .partition(text_after)[0]
                        .strip()
                    )
Mir persönlich ist `imaplib` in der Regel ein bisschen zu „low level“ und ich verwende das darauf aufbauende IMAPClient. Ungetestet:

Code: Alles auswählen

    with IMAPClient("mail.server.de") as client:
        client.login("xyz@xyz.de", "Passwort")
        client.select_folder("Inbox", readonly=True)
        mail_ids = client.search(["UNSEEN", "SUBJECT", subject], "utf-8")
        for mail_data in client.fetch(mail_ids[-2:], ["RFC822"]).values():
            for part in email.message_from_bytes(mail_data["RFC822"]).walk():
                if part.get_content_type() == "text/plain":
                    print(
                        part.get_payload(decode=True)
                        .decode(part.get_content_charset())
                        .partition(text_before)[2]
                        .partition(text_after)[0]
                        .strip()
                    )