Få fuld kontrol over Pythons 'email'-pakke. Lær at konstruere komplekse MIME-beskeder og parse indgående e-mails effektivt og globalt for dataudtræk.
Mestring af Pythons 'email'-pakke: Kunsten at konstruere MIME-beskeder og robust parsing
E-mail forbliver en hjørnesten i global kommunikation, uundværlig for personlig korrespondance, forretningsdrift og automatiserede systemmeddelelser. Bag hver rig-tekst e-mail, hver vedhæftning og hver omhyggeligt formateret signatur ligger kompleksiteten af Multipurpose Internet Mail Extensions (MIME). For udviklere, især dem der arbejder med Python, er det en kritisk færdighed at mestre, hvordan man programmatisk konstruerer og parser disse MIME-beskeder.
Pythons indbyggede email
-pakke tilbyder et robust og omfattende rammeværk til håndtering af e-mailbeskeder. Det er ikke kun til afsendelse af simpel tekst; det er designet til at abstrahere de indviklede detaljer ved MIME, hvilket giver dig mulighed for at oprette sofistikerede e-mails og udtrække specifikke data fra indgående e-mails med bemærkelsesværdig præcision. Denne guide vil tage dig med på et dybt dyk ned i de to primære aspekter af denne pakke: konstruktion af MIME-beskeder til afsendelse og parsing af dem til dataudtræk, hvilket giver et globalt perspektiv på bedste praksis.
At forstå både konstruktion og parsing er afgørende. Når du konstruerer en besked, definerer du i det væsentlige dens struktur og indhold, så et andet system kan fortolke det. Når du parser, fortolker du en struktur og et indhold defineret af et andet system. En dyb forståelse af det ene hjælper i høj grad med at mestre det andet, hvilket fører til mere modstandsdygtige og interoperable e-mailapplikationer.
Forståelse af MIME: Rygraden i moderne e-mail
Før vi dykker ned i Python-specifikationer, er det vigtigt at forstå, hvad MIME er, og hvorfor det er så afgørende. Oprindeligt var e-mailbeskeder begrænset til almindelig tekst (7-bit ASCII-tegn). MIME, introduceret i begyndelsen af 1990'erne, udvidede e-mailens muligheder til at understøtte:
- Ikke-ASCII-tegn: Tillader tekst på sprog som arabisk, kinesisk, russisk, eller ethvert andet sprog, der bruger tegn uden for ASCII-sættet.
- Vedhæftninger: Afsendelse af filer som dokumenter, billeder, lyd og video.
- Rich-tekst formatering: HTML-e-mails med fed skrift, kursiv, farver og layout.
- Flere dele: Kombinering af almindelig tekst, HTML og vedhæftninger i en enkelt e-mail.
MIME opnår dette ved at tilføje specifikke headers til en e-mailbesked og strukturere dens brødtekst i forskellige "dele". Nøgle MIME-headers, du vil støde på, inkluderer:
Content-Type:
Angiver datatypen i en del (f.eks.text/plain
,text/html
,image/jpeg
,application/pdf
,multipart/alternative
). Den indeholder ofte også encharset
-parameter (f.eks.charset=utf-8
).Content-Transfer-Encoding:
Angiver, hvordan e-mailklienten skal afkode indholdet (f.eks.base64
for binære data,quoted-printable
for mest tekst med nogle ikke-ASCII-tegn).Content-Disposition:
Foreslår, hvordan modtagerens e-mailklient skal vise delen (f.eks.inline
til visning i meddelelsesbrødteksten,attachment
for en fil, der skal gemmes).
Pythons email
-pakke: Et dybtgående kig
Pythons email
-pakke er et omfattende bibliotek designet til programmatisk at oprette, parse og modificere e-mailbeskeder. Det er bygget op omkring konceptet med Message
-objekter, som repræsenterer strukturen af en e-mail.
Nøglemoduler i pakken inkluderer:
email.message:
Indeholder den centraleEmailMessage
-klasse, som er den primære grænseflade til at oprette og manipulere e-mailbeskeder. Det er en yderst fleksibel klasse, der håndterer MIME-detaljer automatisk.email.mime:
Tilbyder ældre klasser (somMIMEText
,MIMEMultipart
), der giver mere eksplicit kontrol over MIME-strukturen. SelvomEmailMessage
generelt foretrækkes til ny kode på grund af dens enkelhed, kan det være en fordel at forstå disse klasser.email.parser:
Tilbyder klasser somBytesParser
ogParser
til at konvertere rå e-mail-data (bytes eller strenge) tilEmailMessage
-objekter.email.policy:
Definerer politikker, der styrer, hvordan e-mailbeskeder konstrueres og parses, hvilket påvirker header-kodning, linjeskift og fejlhåndtering.
For de fleste moderne brugsscenarier vil du primært interagere med email.message.EmailMessage
-klassen til både konstruktion og et parset meddelelsesobjekt. Dens metoder forenkler i høj grad, hvad der tidligere var en mere omstændelig proces med de ældre email.mime
-klasser.
MIME-beskedkonstruktion: Bygning af e-mails med præcision
Konstruktion af e-mails involverer samling af forskellige komponenter (tekst, HTML, vedhæftninger) til en gyldig MIME-struktur. EmailMessage
-klassen strømliner denne proces betydeligt.
Grundlæggende tekst-e-mails
Den enkleste e-mail er almindelig tekst. Du kan oprette en og indstille grundlæggende headers ubesværet:
from email.message import EmailMessage
msg = EmailMessage()
msg['Subject'] = 'Greetings from Python'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('Hello, this is a plain text email sent from Python.\n\nBest regards,\nYour Python Script')
print(msg.as_string())
Forklaring:
EmailMessage()
opretter et tomt meddelelsesobjekt.- Ordbogs-lignende adgang (
msg['Subject'] = ...
) indstiller almindelige headers. set_content()
tilføjer e-mailens primære indhold. Som standard udledesContent-Type: text/plain; charset="utf-8"
.as_string()
serialiserer meddelelsen til et strengformat, der er egnet til afsendelse via SMTP eller lagring til en fil.
Tilføjelse af HTML-indhold
For at sende en HTML-e-mail angiver du blot indholdstypen, når du kalder set_content()
. Det er god praksis at give et almindeligt tekstalternativ til modtagere, hvis e-mailklienter ikke gengiver HTML, eller af hensyn til tilgængelighed.
from email.message import EmailMessage
msg = EmailMessage()
msg['Subject'] = 'Your HTML Newsletter'
msg['From'] = 'newsletter@example.com'
msg['To'] = 'subscriber@example.com'
html_content = """
<html>
<head></head>
<body>
<h1>Welcome to Our Global Update!</h1>
<p>Dear Subscriber,</p>
<p>This is your <strong>latest update</strong> from around the world.</p>
<p>Visit our <a href="http://www.example.com">website</a> for more.</p>
<p>Best regards,<br>The Team</p>
</body>
</html>
"""
# Add the HTML version
msg.add_alternative(html_content, subtype='html')
# Add a plain text fallback
plain_text_content = (
"Welcome to Our Global Update!\n\n"
"Dear Subscriber,\n\n"
"This is your latest update from around the world.\n"
"Visit our website for more: http://www.example.com\n\n"
"Best regards,\nThe Team"
)
msg.add_alternative(plain_text_content, subtype='plain')
print(msg.as_string())
Forklaring:
add_alternative()
bruges til at tilføje forskellige repræsentationer af det *samme* indhold. E-mailklienten vil vise den "bedste", den kan håndtere (normalt HTML).- Dette opretter automatisk en
multipart/alternative
MIME-struktur.
Håndtering af vedhæftninger
Vedhæftning af filer er ligetil ved hjælp af add_attachment()
. Du kan vedhæfte enhver filtype, og pakken håndterer de passende MIME-typer og kodninger (normalt base64
).
from email.message import EmailMessage
from pathlib import Path
# Create dummy files for demonstration
Path('report.pdf').write_bytes(b'%PDF-1.4\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n2 0 obj<</Count 0>>endobj\nxref\n0 3\n0000000000 65535 f\n0000000009 00000 n\n0000000052 00000 n\ntrailer<</Size 3/Root 1 0 R>>startxref\n104\n%%EOF') # A very basic, invalid PDF placeholder
Path('logo.png').write_bytes(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0cIDAT\x08\x99c`\x00\x00\x00\x02\x00\x01\xe2!\x00\xa0\x00\x00\x00\x00IEND\xaeB`\x82') # A 1x1 transparent PNG placeholder
msg = EmailMessage()
msg['Subject'] = 'Important Document and Image'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('Please find the attached report and company logo.')
# Attach a PDF file
with open('report.pdf', 'rb') as f:
file_data = f.read()
msg.add_attachment(
file_data,
maintype='application',
subtype='pdf',
filename='Annual_Report_2024.pdf'
)
# Attach an image file
with open('logo.png', 'rb') as f:
image_data = f.read()
msg.add_attachment(
image_data,
maintype='image',
subtype='png',
filename='CompanyLogo.png'
)
print(msg.as_string())
# Clean up dummy files
Path('report.pdf').unlink()
Path('logo.png').unlink()
Forklaring:
add_attachment()
tager filindholdets rå bytes.maintype
ogsubtype
angiver MIME-typen (f.eks.application/pdf
,image/png
). Disse er afgørende for, at modtagerens e-mailklient korrekt kan identificere og håndtere vedhæftningen.filename
angiver navnet, under hvilket vedhæftningen gemmes af modtageren.- Dette opsætter automatisk en
multipart/mixed
-struktur.
Oprettelse af Multipart-beskeder
Når du har en besked med både en HTML-brødtekst, en almindelig tekst-fallback og inline-billeder eller relaterede filer, har du brug for en mere kompleks multipart-struktur. EmailMessage
-klassen håndterer dette intelligent med add_related()
og add_alternative()
.
Et almindeligt scenarie er en HTML-e-mail med et billede indlejret direkte i HTML'en (et "inline"-billede). Dette bruger multipart/related
.
from email.message import EmailMessage
from pathlib import Path
# Create a dummy image file for demonstration (a 1x1 transparent PNG)
Path('banner.png').write_bytes(b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\x0cIDAT\x08\x99c`\x00\x00\x00\x02\x00\x01\xe2!\x00\xa0\x00\x00\x00\x00IEND\xaeB`\x82')
msg = EmailMessage()
msg['Subject'] = 'Inline Image Example'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
# Plain text version (fallback)
plain_text = 'Check out our amazing banner!\n\n[Image: Banner.png]\n\nVisit our site.'
msg.set_content(plain_text, subtype='plain') # Set initial plain text content
# HTML version (with CID for inline image)
html_content = """
<html>
<head></head>
<body>
<h1>Our Latest Offer!</h1>
<p>Dear Customer,</p>
<p>Don't miss out on our special global promotion:</p>
<img src="cid:my-banner-image" alt="Promotion Banner">
<p>Click <a href="http://www.example.com">here</a> to learn more.</p>
</body>
</html>
"""
msg.add_alternative(html_content, subtype='html') # Add HTML alternative
# Add the inline image (related content)
with open('banner.png', 'rb') as img_file:
image_data = img_file.read()
msg.add_related(
image_data,
maintype='image',
subtype='png',
cid='my-banner-image' # This CID matches the 'src' in HTML
)
print(msg.as_string())
# Clean up dummy file
Path('banner.png').unlink()
Forklaring:
set_content()
etablerer det oprindelige indhold (her, almindelig tekst).add_alternative()
tilføjer HTML-versionen og opretter enmultipart/alternative
-struktur, der indeholder den almindelige tekst og HTML-delene.add_related()
bruges til indhold, der er "relateret" til en af meddelelsesdelene, typisk inline-billeder i HTML. Den tager encid
(Content-ID)-parameter, som derefter refereres i HTML-tagget<img src="cid:my-banner-image">
.- Den endelige struktur vil være
multipart/mixed
(hvis der var eksterne vedhæftninger), der indeholder enmultipart/alternative
-del, som igen indeholder enmultipart/related
-del.multipart/related
-delen indeholder HTML'en og inline-billedet.EmailMessage
-klassen håndterer denne indlejringskompleksitet for dig.
Kodning og tegnsæt for global rækkevidde
For international kommunikation er korrekt tegnkodning altafgørende. email
-pakken er som standard meget bestemt med hensyn til at bruge UTF-8, som er den universelle standard for håndtering af forskellige tegnsæt fra hele verden.
from email.message import EmailMessage
msg = EmailMessage()
msg['Subject'] = 'Global Characters: こんにちは, Привет, नमस्ते'
msg['From'] = 'global_sender@example.com'
msg['To'] = 'global_recipient@example.com'
# Japanese, Russian, and Hindi characters
content = "This message contains diverse global characters:\n"
content += "こんにちは (Japanese)\n"
content += "Привет (Russian)\n"
content += "नमस्ते (Hindi)\n\n"
content += "The 'email' package handles UTF-8 gracefully."
msg.set_content(content)
print(msg.as_string())
Forklaring:
- Når
set_content()
modtager en Python-streng, koder den den automatisk til UTF-8 og indstillerContent-Type: text/plain; charset="utf-8"
-headeren. - Hvis indholdet kræver det (f.eks. indeholder mange ikke-ASCII-tegn), kan det også anvende
Content-Transfer-Encoding: quoted-printable
ellerbase64
for at sikre sikker transmission over ældre e-mailsystemer. Pakken håndterer dette automatisk i henhold til den valgte politik.
Brugerdefinerede Headers og Politikker
Du kan tilføje enhver brugerdefineret header til en e-mail. Politikker (fra email.policy
) definerer, hvordan beskeder håndteres, og påvirker aspekter som header-kodning, linjeskift og fejlhåndtering. Standardpolitikken er generelt god, men du kan vælge `SMTP` for streng SMTP-overholdelse eller definere dine egne.
from email.message import EmailMessage
from email import policy
msg = EmailMessage(policy=policy.SMTP)
msg['Subject'] = 'Email with Custom Header'
msg['From'] = 'info@example.org'
msg['To'] = 'user@example.org'
msg['X-Custom-Header'] = 'This is a custom value for tracking'
msg['Reply-To'] = 'support@example.org'
msg.set_content('This email demonstrates custom headers and policies.')
print(msg.as_string())
Forklaring:
- Brug af
policy=policy.SMTP
sikrer streng overholdelse af SMTP-standarder, hvilket kan være afgørende for leveringsevne. - Brugerdefinerede headers tilføjes ligesom standardheaders. De starter ofte med
X-
for at angive ikke-standardheaders.
MIME-beskedparsing: Udtrækning af information fra indgående e-mails
Parsing indebærer at tage rå e-mail-data (typisk modtaget via IMAP eller fra en fil) og konvertere dem til et `EmailMessage`-objekt, som du derefter nemt kan inspicere og manipulere.
Indlæsning og indledende Parsing
Du vil typisk modtage e-mails som rå bytes. email.parser.BytesParser
(eller bekvemmelighedsfunktionerne email.message_from_bytes()
) bruges til dette.
from email.parser import BytesParser
from email.policy import default
raw_email_bytes = b"""
From: sender@example.com
To: recipient@example.com
Subject: Test Email with Basic Headers
Date: Mon, 1 Jan 2024 10:00:00 +0000
Content-Type: text/plain; charset="utf-8"
This is the body of the email.
It's a simple test.
"""
# Using BytesParser
parser = BytesParser(policy=default)
msg = parser.parsebytes(raw_email_bytes)
# Or using the convenience function
# from email import message_from_bytes
# msg = message_from_bytes(raw_email_bytes, policy=default)
print(f"Subject: {msg['subject']}")
print(f"From: {msg['from']}")
print(f"Content-Type: {msg['Content-Type']}")
Forklaring:
BytesParser
tager rå byte-data (som er, hvordan e-mails transmitteres) og returnerer etEmailMessage
-objekt.policy=default
specificerer parseringsreglerne.
Adgang til Headers
Headers er nemt tilgængelige via ordbogs-lignende nøgler. Pakken håndterer automatisk afkodning af kodede headers (f.eks. emner med internationale tegn).
# ... (using the 'msg' object from the previous parsing example)
print(f"Date: {msg['date']}")
print(f"Message ID: {msg['Message-ID'] if 'Message-ID' in msg else 'N/A'}")
# Handling multiple headers (e.g., 'Received' headers)
# from email.message import EmailMessage # If not imported yet
# from email import message_from_string # For a quick string example
multi_header_email = message_from_string(
"""
From: a@example.com
To: b@example.com
Subject: Multi-header Test
Received: from client.example.com (client.example.com [192.168.1.100])
by server.example.com (Postfix) with ESMTP id 123456789
for <b@example.com>; Mon, 1 Jan 2024 10:00:00 +0000 (GMT)
Received: from mx.another.com (mx.another.com [192.168.1.101])
by server.example.com (Postfix) with ESMTP id 987654321
for <b@example.com>; Mon, 1 Jan 2024 09:59:00 +0000 (GMT)
Body content here.
"""
)
received_headers = multi_header_email.get_all('received')
if received_headers:
print("\nReceived Headers:")
for header in received_headers:
print(f"- {header}")
Forklaring:
- Adgang til en header returnerer dens værdi som en streng.
get_all('header-name')
er nyttig for headers, der kan optræde flere gange (somReceived
).- Pakken håndterer header-afkodning, så værdier som
Subject: =?utf-8?Q?Global_Characters:_=E3=81=93=E3=82=93=E3=81=AB=E3=81=A1=E3=81=AF?=
automatisk konverteres til læsbare strenge.
Udtrækning af brødtekstindhold
Udtrækning af selve meddelelsesbrødteksten kræver kontrol af, om meddelelsen er multipart. For multipart-beskeder itererer du gennem dens dele.
from email.message import EmailMessage
from email import message_from_string
multipart_email_raw = """
From: multi@example.com
To: user@example.com
Subject: Test Multipart Email
Content-Type: multipart/alternative; boundary="_----------=_12345"
--_----------=_12345
Content-Type: text/plain; charset="utf-8"
Hello from the plain text part!
--_----------=_12345
Content-Type: text/html; charset="utf-8"
<html>
<body>
<h1>Hello from the HTML part!</h1>
<p>This is a <strong>rich text</strong> email.</p>
</body>
</html>
--_----------=_12345--
"""
msg = message_from_string(multipart_email_raw)
if msg.is_multipart():
print("\n--- Multipart Email Body ---")
for part in msg.iter_parts():
content_type = part.get_content_type()
charset = part.get_content_charset() or 'utf-8' # Default to utf-8 if not specified
payload = part.get_payload(decode=True) # Decode payload bytes
try:
decoded_content = payload.decode(charset)
print(f"Content-Type: {content_type}, Charset: {charset}\nContent:\n{decoded_content}\n")
except UnicodeDecodeError:
print(f"Content-Type: {content_type}, Charset: {charset}\nContent: (Binary or undecodable data)\n")
# Handle binary data, or attempt a fallback encoding
else:
print("\n--- Single Part Email Body ---")
charset = msg.get_content_charset() or 'utf-8'
payload = msg.get_payload(decode=True)
try:
decoded_content = payload.decode(charset)
print(f"Content-Type: {msg.get_content_type()}, Charset: {charset}\nContent:\n{decoded_content}\n")
except UnicodeDecodeError:
print(f"Content: (Binary or undecodable data)\n")
Forklaring:
is_multipart()
afgør, om e-mailen har flere dele.iter_parts()
itererer gennem alle underdele af en multipart-besked.get_content_type()
returnerer den fulde MIME-type (f.eks.text/plain
).get_content_charset()
udtrækker tegnsættet fraContent-Type
-headeren.get_payload(decode=True)
er afgørende: den returnerer det *afkodede* indhold som bytes. Du skal derefter.decode()
disse bytes ved hjælp af det korrekte tegnsæt for at få en Python-streng.
Håndtering af vedhæftninger under parsing
Vedhæftninger er også dele af en multipart-besked. Du kan identificere dem ved hjælp af deres Content-Disposition
-header og gemme deres afkodede payload.
from email.message import EmailMessage
from email import message_from_string
import os
# Example email with a simple attachment
email_with_attachment = """
From: attach@example.com
To: user@example.com
Subject: Document Attached
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="_----------=_XYZ"
--_----------=_XYZ
Content-Type: text/plain; charset="utf-8"
Here is your requested document.
--_----------=_XYZ
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="document.pdf"
JVBERi0xLjQKMSAwIG9iagpbL1BERi9UZXh0L0ltYWdlQy9JbWFnZUkvSW1hZ0VCXQplbmRvYmoK
--_----------=_XYZ--
"""
msg = message_from_string(email_with_attachment)
output_dir = 'parsed_attachments'
os.makedirs(output_dir, exist_ok=True)
print("\n--- Processing Attachments ---")
for part in msg.iter_attachments():
filename = part.get_filename()
if filename:
filepath = os.path.join(output_dir, filename)
try:
with open(filepath, 'wb') as f:
f.write(part.get_payload(decode=True))
print(f"Saved attachment: {filepath} (Type: {part.get_content_type()})")
except Exception as e:
print(f"Error saving {filename}: {e}")
else:
print(f"Found an attachment without a filename (Content-Type: {part.get_content_type()})")
# Clean up the output directory
# import shutil
# shutil.rmtree(output_dir)
Forklaring:
iter_attachments()
giver specifikt dele, der sandsynligvis er vedhæftninger (dvs. har enContent-Disposition: attachment
-header eller ikke er klassificeret på anden måde).get_filename()
udtrækker filnavnet fraContent-Disposition
-headeren.part.get_payload(decode=True)
henter vedhæftningens rå binære indhold, allerede afkodet frabase64
ellerquoted-printable
.
Afkodning af kodninger og tegnsæt
email
-pakken gør et fremragende arbejde med automatisk at afkode almindelige overførselskodninger (som base64
, quoted-printable
), når du kalder get_payload(decode=True)
. For selve tekstindholdet forsøger den at bruge det charset
, der er angivet i Content-Type
-headeren. Hvis ingen tegnsæt er specificeret, eller det er ugyldigt, skal du muligvis håndtere det elegant.
from email.message import EmailMessage
from email import message_from_string
# Example with a potentially problematic charset
email_latin1 = """
From: legacy@example.com
To: new_system@example.com
Subject: Special characters: àéíóú
Content-Type: text/plain; charset="iso-8859-1"
This message contains Latin-1 characters: àéíóú
"""
msg = message_from_string(email_latin1)
if msg.is_multipart():
for part in msg.iter_parts():
payload = part.get_payload(decode=True)
charset = part.get_content_charset() or 'utf-8'
try:
print(f"Decoded (Charset: {charset}): {payload.decode(charset})")
except UnicodeDecodeError:
print(f"Failed to decode with {charset}. Trying fallback...")
# Fallback to a common charset or 'latin-1' if expecting it
print(f"Decoded (Fallback Latin-1): {payload.decode('latin-1', errors='replace')}")
else:
payload = msg.get_payload(decode=True)
charset = msg.get_content_charset() or 'utf-8'
try:
print(f"Decoded (Charset: {charset}): {payload.decode(charset})")
except UnicodeDecodeError:
print(f"Failed to decode with {charset}. Trying fallback...")
print(f"Decoded (Fallback Latin-1): {payload.decode('latin-1', errors='replace')}")
Forklaring:
- Forsøg altid at bruge det tegnsæt, der er angivet i
Content-Type
-headeren. - Brug en
try-except UnicodeDecodeError
-blok for robusthed, især når du håndterer e-mails fra forskellige og potentielt ikke-standardiserede kilder. errors='replace'
ellererrors='ignore'
kan bruges med.decode()
til at håndtere tegn, der ikke kan kortlægges til den ønskede kodning, og forhindre nedbrud.
Avancerede Parsing-scenarier
Virkelige e-mails kan være yderst komplekse med indlejrede multipart-strukturer. email
-pakkens rekursive natur gør det ligetil at navigere i disse. Du kan kombinere is_multipart()
med iter_parts()
for at gennemgå dybt indlejrede beskeder.
from email.message import EmailMessage
from email import message_from_string
def parse_email_part(part, indent=0):
prefix = " " * indent
content_type = part.get_content_type()
charset = part.get_content_charset() or 'N/A'
print(f"{prefix}Part Type: {content_type}, Charset: {charset}")
if part.is_multipart():
for subpart in part.iter_parts():
parse_email_part(subpart, indent + 1)
elif part.get_filename(): # It's an attachment
print(f"{prefix} Attachment: {part.get_filename()} (Size: {len(part.get_payload(decode=True))} bytes)")
else: # It's a regular text/html body part
payload = part.get_payload(decode=True)
try:
decoded_content = payload.decode(charset)
# print(f"{prefix} Content (first 100 chars): {decoded_content[:100]}...") # For brevity
except UnicodeDecodeError:
print(f"{prefix} Content: (Binary or undecodable text)")
complex_email_raw = """
From: complex@example.com
To: receiver@example.com
Subject: Complex Email with HTML, Plain, and Attachment
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="outer_boundary"
--outer_boundary
Content-Type: multipart/alternative; boundary="inner_boundary"
--inner_boundary
Content-Type: text/plain; charset="utf-8"
Plain text content.
--inner_boundary
Content-Type: text/html; charset="utf-8"
<html><body><h2>HTML Content</h2></body></html>
--inner_boundary--
--outer_boundary
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="data.bin"
SGVsbG8gV29ybGQh
--outer_boundary--
"""
msg = message_from_string(complex_email_raw)
print("\n--- Traversing Complex Email Structure ---")
parse_email_part(msg)
Forklaring:
- Den rekursive funktion
parse_email_part
demonstrerer, hvordan man gennemgår hele meddelelsestræet, identificerer multipart-dele, vedhæftninger og brødtekstindhold på hvert niveau. - Dette mønster er yderst fleksibelt til udtrækning af specifikke typer indhold fra dybt indlejrede e-mails.
Konstruktion vs. Parsing: Et komparativt perspektiv
Selvom det er særskilte operationer, er konstruktion og parsing to sider af samme sag: håndtering af MIME-beskeder. At forstå det ene hjælper uundgåeligt det andet.
Konstruktion (Afsendelse):
- Fokus: Korrekt samling af headers, indhold og vedhæftninger til en standardkompatibel MIME-struktur.
- Primært værktøj:
email.message.EmailMessage
med metoder somset_content()
,add_attachment()
,add_alternative()
,add_related()
. - Nøgleudfordringer: Sikring af korrekte MIME-typer, tegnsæt (især UTF-8 for global understøttelse), `Content-Transfer-Encoding` og korrekt headerformatering. Fejltrin kan føre til, at e-mails ikke vises korrekt, vedhæftninger bliver beskadiget, eller beskeder bliver markeret som spam.
Parsing (Modtagelse):
- Fokus: Demontering af en rå e-mail byte-strøm til dens bestanddele, udtrækning af specifikke headers, brødtekstindhold og vedhæftninger.
- Primært værktøj:
email.parser.BytesParser
elleremail.message_from_bytes()
, derefter navigation i det resulterendeEmailMessage
-objekt med metoder somis_multipart()
,iter_parts()
,get_payload()
,get_filename()
og headeradgang. - Nøgleudfordringer: Håndtering af fejlformede e-mails, korrekt identifikation af tegnkodninger (især når de er tvetydige), håndtering af manglende headers og robust udtrækning af data fra varierede MIME-strukturer.
En besked, du konstruerer ved hjælp af `EmailMessage`, bør være perfekt parsbar af `BytesParser`. På samme måde giver forståelsen af MIME-strukturen, der produceres under parsing, dig indsigt i, hvordan du selv bygger komplekse beskeder.
Bedste praksis for global e-mailhåndtering med Python
For applikationer, der interagerer med et globalt publikum eller håndterer forskellige e-mailkilder, overvej disse bedste praksisser:
- Standardiser på UTF-8: Brug altid UTF-8 for alt tekstindhold, både ved konstruktion og når det forventes under parsing. Dette er den globale standard for tegnkodning og undgår mojibake (forvrænget tekst).
- Valider e-mailadresser: Før afsendelse skal du validere modtagerens e-mailadresser for at sikre leveringsevne. Under parsing skal du være forberedt på potentielt ugyldige eller fejlformede adresser i `From`, `To` eller `Cc` headers.
- Test grundigt: Test din e-mailkonstruktion med forskellige e-mailklienter (Gmail, Outlook, Apple Mail, Thunderbird) og platforme for at sikre ensartet gengivelse af HTML og vedhæftninger. Ved parsing skal du teste med et bredt udvalg af eksempler på e-mails, herunder dem med usædvanlige kodninger, manglende headers eller komplekse indlejrede strukturer.
- Sanitiser parset input: Behandl altid indhold udtrækket fra indgående e-mails som upålideligt. Sanitiser HTML-indhold for at forhindre XSS-angreb, hvis du viser det i en webapplikation. Valider vedhæftede filnavne og -typer for at forhindre path traversal eller andre sikkerhedsrisici ved lagring af filer.
- Robust fejlhåndtering: Implementer omfattende
try-except
-blokke ved afkodning af payloads eller adgang til potentielt manglende headers. HåndterUnicodeDecodeError
ellerKeyError
elegant. - Håndter store vedhæftninger: Vær opmærksom på vedhæftningsstørrelser, både ved konstruktion (for at undgå at overskride mailservergrænser) og parsing (for at forhindre overdreven hukommelsesforbrug eller diskpladsforbrug). Overvej at streame store vedhæftninger, hvis det understøttes af dit system.
- Udnyt
email.policy
: For kritiske applikationer skal du eksplicit vælge en `email.policy` (f.eks. `policy.SMTP`) for at sikre streng overholdelse af e-mailstandarder, hvilket kan påvirke leveringsevne og interoperabilitet. - Bevaring af metadata: Når du parser, skal du beslutte, hvilke metadata (headers, originale grænsestrenge) der er vigtige at bevare, især hvis du bygger et e-mailarkiverings- eller videresendelsessystem.
Konklusion
Pythons email
-pakke er et utroligt kraftfuldt og fleksibelt bibliotek for enhver, der har brug for programmatisk at interagere med e-mail. Ved at mestre både konstruktion af MIME-beskeder og robust parsing af indgående e-mails låser du op for evnen til at skabe sofistikerede e-mailautomationssystemer, bygge e-mailklienter, analysere e-mail-data og integrere e-mailfunktionaliteter i stort set enhver applikation.
Pakken håndterer omhyggeligt de underliggende kompleksiteter ved MIME, hvilket giver udviklere mulighed for at fokusere på indholdet og logikken i deres e-mailinteraktioner. Uanset om du sender personaliserede nyhedsbreve til et globalt publikum eller udtrækker kritiske data fra automatiserede systemrapporter, vil en dyb forståelse af email
-pakken vise sig at være uvurderlig i opbygningen af pålidelige, interoperable og globalt bevidste e-mail-løsninger.