Python: cómo analizar el cuerpo de un correo electrónico sin formato, dado que el correo electrónico sin formato no tiene una etiqueta "Cuerpo" ni nada

81

Parece fácil conseguir

From
To
Subject

etc vía

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

asumiendo que "a"es la cadena de correo electrónico sin formato que se parece a esto.

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--"""

LA PREGUNTA

¿Cómo se obtiene la información Bodyde este correo electrónico a través de Python?

Hasta ahora, este es el único código que conozco, pero aún tengo que probarlo.

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

¿Es esta la forma correcta?

o quizás hay algo más simple como ...

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

?

codegeek
fuente

Respuestas:

94

Utilice 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
fuente
111

Para ser muy positivo, trabaja con el cuerpo del correo electrónico real (sin embargo, aún con la posibilidad de que no esté analizando la parte correcta), debe omitir los archivos adjuntos y concentrarse en la parte simple o html (según sus necesidades) para más Procesando.

Como los archivos adjuntos mencionados anteriormente pueden ser y muy a menudo son de texto / plano o texto / html, esta muestra no a prueba de balas los omite al verificar el encabezado de disposición de contenido:

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)

Por cierto, walk()itera maravillosamente en las partes de mímica y get_payload(decode=True)hace el trabajo sucio en la decodificación de base64, etc. por ti.

Algunos antecedentes: como he insinuado, el maravilloso mundo de los correos electrónicos MIME presenta muchos peligros para encontrar "incorrectamente" el cuerpo del mensaje. En el caso más simple, está en la única parte "text / plain" y get_payload () es muy tentador, pero no vivimos en un mundo simple - a menudo está rodeado de contenido multiparte / alternativo, relacionado, mixto, etc. Wikipedia lo describe de manera estricta: MIME , pero considerando que todos estos casos a continuación son válidos y comunes, uno debe considerar las redes de seguridad por todas partes:

Muy común: prácticamente lo que obtienes en el editor normal (Gmail, Outlook) enviando texto formateado con un archivo adjunto:

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

Relativamente simple, solo representación alternativa:

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

Para bien o para mal, esta estructura también es válida:

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

Espero que esto ayude un poco.

PD Mi punto es que no te acerques al correo electrónico a la ligera: muerde cuando menos lo esperas :)

Todor Minakov
fuente
5
Gracias por este ejemplo completo y por deletrear una advertencia, en contra de la respuesta aceptada. Creo que este es un enfoque mucho mejor y más seguro.
Simon Steinberger
1
¡Ah, muy bien! .get_payload(decode=True)en lugar de simplemente .get_payload()ha hecho la vida mucho más fácil, ¡gracias!
Mark
9

Hay un paquete muy bueno disponible para analizar el contenido del correo electrónico con la documentación adecuada.

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)

Cómo utilizar:

mail.attachments: list of all attachments
mail.body
mail.to
Amit Sharma
fuente
2
La biblioteca es genial, pero tuve que crear mi propia clase que herede MailParsery anule el método body , porque une las partes del cuerpo del correo electrónico con "\ n --- mail_boundary --- \ n", lo cual no era ideal para mí.
Avram
hola @avram, ¿podrías compartir la clase que has escrito?
Amey P Naik
Logré dividir el resultado en "\ n --- mail_boundary --- \ n".
Amey P Naik
1
@AmeyPNaik Aquí hice una breve descripción de github: gist.github.com/aleksaa01/ccd371869f3a3c7b3e47822d5d78ccdf
avram
1
@AmeyPNaik en su documentación , dice: mail-parser puede analizar el formato de correo electrónico de Outlook (.msg). Para utilizar esta función, debe instalar el paquete libemail-outlook-message-perl
Ciprian Tomoiagă
6

Python 3.6+ proporciona métodos de conveniencia incorporados para encontrar y decodificar el cuerpo del texto sin formato como en @Todor Minakovla respuesta. Puede utilizar los métodos EMailMessage.get_body()y 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)

Tenga en cuenta que esto se dará Nonesi no hay una parte del cuerpo de texto sin formato (obvio).

Si está leyendo, por ejemplo, de un archivo mbox, puede darle al constructor del buzón una EmailMessagefábrica:

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

Tenga en cuenta que debe pasar email.policy.defaultcomo política, ya que no es la predeterminada ...

Doctor J
fuente
2
¿Por qué no es email.policy.defaultel predeterminado? Parece que debería ser.
PartialOrder
4

No hay b['body']en Python. Tienes que usar 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...

Buena suerte.

Jimmy Lin
fuente
0

Si los correos electrónicos son el marco de datos de pandas y los correos electrónicos, envíe un mensaje a la columna para el texto del correo electrónico

## 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
fuente
-3

Aquí está el código que me funciona siempre (para correos electrónicos de 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
fuente
4
Quizás explique que esto es para Outlook en Windows, no para correo electrónico real.
tripleee