Python: Jak przeanalizować treść z nieprzetworzonego e-maila, biorąc pod uwagę, że ten nieprzetworzony e-mail nie ma tagu „Body” ani nic takiego

83

Wydaje się, że łatwo jest uzyskać plik

From
To
Subject

itp. za pośrednictwem

import email
b = email.message_from_string(a)
bbb = b['from']
ccc = b['to']

zakładając, że "a"jest to nieprzetworzony ciąg wiadomości e-mail, który wygląda mniej więcej tak.

a = """From [email protected] Thu Jul 25 19:28:59 2013
Received: from a1.local.tld (localhost [127.0.0.1])
    by a1.local.tld (8.14.4/8.14.4) with ESMTP id r6Q2SxeQ003866
    for <[email protected]>; Thu, 25 Jul 2013 19:28:59 -0700
Received: (from root@localhost)
    by a1.local.tld (8.14.4/8.14.4/Submit) id r6Q2Sxbh003865;
    Thu, 25 Jul 2013 19:28:59 -0700
From: [email protected]
Subject: oooooooooooooooo
To: [email protected]
Cc: 
X-Originating-IP: 192.168.15.127
X-Mailer: Webmin 1.420
Message-Id: <1374805739.3861@a1>
Date: Thu, 25 Jul 2013 19:28:59 -0700 (PDT)
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="bound1374805739"

This is a multi-part message in MIME format.

--bound1374805739
Content-Type: text/plain
Content-Transfer-Encoding: 7bit

ooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooooooo
ooooooooooooooooooooooooooooooooooooooooooooooo

--bound1374805739--"""

PYTANIE

jak otrzymujesz Bodyten e-mail przez Pythona?

Jak dotąd jest to jedyny kod, o którym wiem, ale jeszcze go nie przetestowałem.

if email.is_multipart():
    for part in email.get_payload():
        print part.get_payload()
else:
    print email.get_payload()

czy to jest właściwy sposób?

a może jest coś prostszego, na przykład ...

import email
b = email.message_from_string(a)
bbb = b['body']

?

codegeek
źródło

Odpowiedzi:

95

Użyj Message.get_payload

b = email.message_from_string(a)
if b.is_multipart():
    for payload in b.get_payload():
        # if payload.is_multipart(): ...
        print payload.get_payload()
else:
    print b.get_payload()
falsetru
źródło
113

Aby być bardzo pozytywnym, pracujesz z rzeczywistą treścią wiadomości e-mail (ale nadal istnieje możliwość, że nie analizujesz właściwej części), musisz pominąć załączniki i skupić się na części zwykłej lub html (w zależności od potrzeb) w celu uzyskania dalszych przetwarzanie.

Ponieważ wspomniane wcześniej załączniki mogą i bardzo często składają się z części tekstowej / zwykłej lub tekstowej / html, ta próbka bez punktorów pomija je, sprawdzając nagłówek dyspozycji treści:

b = email.message_from_string(a)
body = ""

if b.is_multipart():
    for part in b.walk():
        ctype = part.get_content_type()
        cdispo = str(part.get('Content-Disposition'))

        # skip any text/plain (txt) attachments
        if ctype == 'text/plain' and 'attachment' not in cdispo:
            body = part.get_payload(decode=True)  # decode
            break
# not multipart - i.e. plain text, no attachments, keeping fingers crossed
else:
    body = b.get_payload(decode=True)

Przy okazji, walk()cudownie iteruje na częściach mime i get_payload(decode=True)wykonuje brudną robotę dekodowania base64 itp. Za Ciebie.

Trochę tła - jak zasugerowałem, wspaniały świat e-maili MIME przedstawia wiele pułapek „błędnego” znajdowania treści wiadomości. W najprostszym przypadku jest to jedyna część "tekstowa / zwykła", a metoda get_payload () jest bardzo kusząca, ale nie żyjemy w prostym świecie - często jest otoczony treściami wieloczęściowymi / alternatywnymi, pokrewnymi, mieszanymi itp. Wikipedia opisuje to ściśle - MIME , ale biorąc pod uwagę wszystkie poniższe przypadki są ważne - i powszechne - należy wziąć pod uwagę wszystkie sieci bezpieczeństwa:

Bardzo powszechne - prawie to, co otrzymujesz w zwykłym edytorze (Gmail, Outlook), wysyłając sformatowany tekst z załącznikiem:

multipart/mixed
 |
 +- multipart/related
 |   |
 |   +- multipart/alternative
 |   |   |
 |   |   +- text/plain
 |   |   +- text/html
 |   |      
 |   +- image/png
 |
 +-- application/msexcel

Stosunkowo proste - tylko alternatywne przedstawienie:

multipart/alternative
 |
 +- text/plain
 +- text/html

Na dobre lub na złe, ta struktura jest również ważna:

multipart/alternative
 |
 +- text/plain
 +- multipart/related
      |
      +- text/html
      +- image/jpeg

Mam nadzieję, że to trochę pomoże.

PS Chodzi mi o to, aby nie podchodzić lekko do e-maila - gryzie, kiedy najmniej się tego spodziewasz :)

Todor Minakov
źródło
6
Dziękuję za ten dokładny przykład i za przeliterowanie ostrzeżenia - wbrew przyjętej odpowiedzi. Myślę, że jest to znacznie lepsze / bezpieczniejsze podejście.
Simon Steinberger
1
Ach, bardzo dobrze! .get_payload(decode=True)zamiast tylko .get_payload()znacznie ułatwiło życie, dzięki!
Mark
11

Dostępny jest bardzo dobry pakiet do analizowania treści wiadomości e-mail z odpowiednią dokumentacją.

import mailparser

mail = mailparser.parse_from_file(f)
mail = mailparser.parse_from_file_obj(fp)
mail = mailparser.parse_from_string(raw_mail)
mail = mailparser.parse_from_bytes(byte_mail)

Jak używać:

mail.attachments: list of all attachments
mail.body
mail.to
Amit Sharma
źródło
2
Biblioteka jest świetna, ale musiałem stworzyć własną klasę, która dziedziczy MailParseri zastępuje metodę body , ponieważ łączy ona części treści wiadomości e-mail z „\ n --- mail_boundary --- \ n”, co nie było dla mnie idealne.
avram
cześć @avram, czy mógłbyś podzielić się swoją klasą, którą napisałeś?
Amey P Naik
Udało mi się podzielić wynik na „\ n --- mail_boundary --- \ n”.
Amey P Naik
1
@AmeyPNaik Tutaj utworzyłem krótki opis na githubie
avram
1
@AmeyPNaik w ich dokumentacji mówi: parser poczty może analizować format wiadomości e-mail Outlooka (.msg). Aby skorzystać z tej funkcji, musisz zainstalować pakiet libemail-outlook-message-perl
Ciprian Tomoiagă,
6

Python 3.6+ zapewnia wbudowane wygodne metody wyszukiwania i dekodowania treści zwykłego tekstu, tak jak w @Todor Minakovodpowiedzi. Możesz użyć metod EMailMessage.get_body()i get_content():

msg = email.message_from_string(s, policy=email.policy.default)
body = msg.get_body(('plain',))
if body:
    body = body.get_content()
print(body)

Zauważ, że da to, Nonejeśli nie ma (oczywistej) części treści w postaci zwykłego tekstu.

Jeśli czytasz np. Z pliku mbox, możesz nadać konstruktorowi skrzynki pocztowej EmailMessagefabrykę:

mbox = mailbox.mbox(mboxfile, factory=lambda f: email.message_from_binary_file(f, policy=email.policy.default), create=False)
for msg in mbox:
    ...

Uwaga należy zdać email.policy.defaultjak polityka, ponieważ jest to nie domyślny ...

Doktorze J.
źródło
2
Dlaczego nie jest email.policy.defaultto ustawienie domyślne? Wydaje się, że tak powinno być.
Częściowe zamówienie
4

W b['body']Pythonie nie ma . Musisz użyć get_payload.

if isinstance(mailEntity.get_payload(), list):
    for eachPayload in mailEntity.get_payload():
        ...do things you want...
        ...real mail body is in eachPayload.get_payload()...
else:
    ...means there is only text/plain part....
    ...use mailEntity.get_payload() to get the body...

Powodzenia.

Jimmy Lin
źródło
0

Jeśli e-maile to panda dataframe, a e-maile. Wiadomość w kolumnie dla tekstu wiadomości e-mail

## Helper functions
def get_text_from_email(msg):
    '''To get the content from email objects'''
    parts = []
    for part in msg.walk():
        if part.get_content_type() == 'text/plain':
            parts.append( part.get_payload() )
    return ''.join(parts)

def split_email_addresses(line):
    '''To separate multiple email addresses'''
    if line:
        addrs = line.split(',')
        addrs = frozenset(map(lambda x: x.strip(), addrs))
    else:
        addrs = None
    return addrs 

import email
# Parse the emails into a list email objects
messages = list(map(email.message_from_string, emails['message']))
emails.drop('message', axis=1, inplace=True)
# Get fields from parsed email objects
keys = messages[0].keys()
for key in keys:
    emails[key] = [doc[key] for doc in messages]
# Parse content from emails
emails['content'] = list(map(get_text_from_email, messages))
# Split multiple email addresses
emails['From'] = emails['From'].map(split_email_addresses)
emails['To'] = emails['To'].map(split_email_addresses)

# Extract the root of 'file' as 'user'
emails['user'] = emails['file'].map(lambda x:x.split('/')[0])
del messages

emails.head()
Ajay Ohri
źródło
-3

Oto kod, który działa dla mnie za każdym razem (w przypadku wiadomości e-mail programu Outlook):

#to read Subjects and Body of email in a folder (or subfolder)

import win32com.client  
#import package

outlook = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")  
#create object

#get to the desired folder ([email protected] is my root folder)

root_folder = 
outlook.Folders['[email protected]'].Folders['Inbox'].Folders['SubFolderName']

#('Inbox' and 'SubFolderName' are the subfolders)

messages = root_folder.Items

for message in messages:
if message.Unread == True:    # gets only 'Unread' emails
    subject_content = message.subject
# to store subject lines of mails

    body_content = message.body
# to store Body of mails

    print(subject_content)
    print(body_content)

    message.Unread = True         # mark the mail as 'Read'
    message = messages.GetNext()  #iterate over mails
Deepesh Verma
źródło
4
Być może przeliteruj, że dotyczy to Outlooka w systemie Windows, a nie prawdziwej poczty e-mail.
tripleee