Odemkněte Python balíček 'email'. Naučte se efektivně a globálně vytvářet složité MIME zprávy a parsovat příchozí e-maily pro extrakci dat.
Zvládnutí Python balíčku 'email': Umění konstrukce MIME zpráv a robustního parsování
E-mail zůstává základním kamenem globální komunikace, nepostradatelným pro osobní korespondenci, obchodní operace a automatizovaná systémová oznámení. Za každým e-mailem s formátovaným textem, každou přílohou a každým pečlivě naformátovaným podpisem se skrývá složitost Multipurpose Internet Mail Extensions (MIME). Pro vývojáře, zejména ty pracující s Pythonem, je zvládnutí programového vytváření a parsování těchto MIME zpráv klíčovou dovedností.
Vestavěný Python balíček email
poskytuje robustní a komplexní framework pro práci s e-mailovými zprávami. Není určen jen pro posílání jednoduchého textu; je navržen tak, aby abstrahoval složité detaily MIME, což vám umožní vytvářet sofistikované e-maily a s pozoruhodnou přesností extrahovat specifická data z příchozích zpráv. Tento průvodce vás provede dvěma hlavními aspekty tohoto balíčku: konstrukcí MIME zpráv pro odesílání a jejich parsováním pro extrakci dat, přičemž poskytne globální pohled na osvědčené postupy.
Pochopení jak konstrukce, tak parsování je klíčové. Když vytváříte zprávu, v podstatě definujete její strukturu a obsah pro interpretaci jiným systémem. Když parsujete, interpretujete strukturu a obsah definovaný jiným systémem. Hluboké porozumění jednomu výrazně pomáhá při zvládnutí druhého, což vede k odolnějším a interoperabilnějším e-mailovým aplikacím.
Pochopení MIME: Páteř moderního e-mailu
Předtím, než se ponoříme do specifik Pythonu, je nezbytné pochopit, co je MIME a proč je tak důležitý. Původně byly e-mailové zprávy omezeny na prostý text (7bitové ASCII znaky). MIME, představený na začátku 90. let, rozšířil možnosti e-mailu o podporu:
- Ne-ASCII znaků: Umožňuje text v jazycích jako je arabština, čínština, ruština nebo jakýkoli jiný jazyk, který používá znaky mimo sadu ASCII.
- Příloh: Odesílání souborů, jako jsou dokumenty, obrázky, audio a video.
- Formátování rich text: HTML e-maily s tučným písmem, kurzívou, barvami a rozložením.
- Více částí: Kombinace prostého textu, HTML a příloh v rámci jednoho e-mailu.
MIME toho dosahuje přidáním specifických hlaviček do e-mailové zprávy a strukturováním jejího těla do různých „částí“. Klíčové MIME hlavičky, se kterými se setkáte, zahrnují:
Content-Type:
Specifikuje typ dat v části (např.text/plain
,text/html
,image/jpeg
,application/pdf
,multipart/alternative
). Často také obsahuje parametrcharset
(např.charset=utf-8
).Content-Transfer-Encoding:
Udává, jak by měl e-mailový klient dekódovat obsah (např.base64
pro binární data,quoted-printable
pro převážně text s některými ne-ASCII znaky).Content-Disposition:
Navrhuje, jak by měl e-mailový klient příjemce zobrazit danou část (např.inline
pro zobrazení v těle zprávy,attachment
pro soubor k uložení).
Python balíček email
: Hluboký ponor
Python balíček email
je komplexní knihovna určená k programovému vytváření, parsování a úpravě e-mailových zpráv. Je postavena na konceptu objektů Message
, které reprezentují strukturu e-mailu.
Klíčové moduly v rámci balíčku zahrnují:
email.message:
Obsahuje základní tříduEmailMessage
, která je primárním rozhraním pro vytváření a manipulaci s e-mailovými zprávami. Je to vysoce flexibilní třída, která automaticky zpracovává detaily MIME.email.mime:
Poskytuje starší třídy (jakoMIMEText
,MIMEMultipart
), které nabízejí explicitnější kontrolu nad strukturou MIME. Ačkoliv je pro nový kód obecně preferována třídaEmailMessage
kvůli své jednoduchosti, porozumění těmto třídám může být přínosné.email.parser:
Nabízí třídy jakoBytesParser
aParser
pro převod surových e-mailových dat (bytů nebo řetězců) na objektyEmailMessage
.email.policy:
Definuje politiky, které řídí, jak jsou e-mailové zprávy vytvářeny a parsovány, což ovlivňuje kódování hlaviček, konce řádků a zpracování chyb.
Pro většinu moderních případů použití budete primárně interagovat s třídou email.message.EmailMessage
jak pro konstrukci, tak jako s objektem parsované zprávy. Její metody výrazně zjednodušují proces, který byl dříve s staršími třídami email.mime
zdlouhavější.
Konstrukce MIME zpráv: Tvorba e-mailů s precizností
Vytváření e-mailů zahrnuje sestavování různých komponent (text, HTML, přílohy) do platné struktury MIME. Třída EmailMessage
tento proces výrazně zjednodušuje.
Základní textové e-maily
Nejjednodušší e-mail je prostý text. Můžete ho snadno vytvořit a nastavit základní hlavičky:
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())
Vysvětlení:
EmailMessage()
vytvoří prázdný objekt zprávy.- Přístup podobný slovníku (
msg['Subject'] = ...
) nastavuje běžné hlavičky. set_content()
přidá primární obsah e-mailu. Ve výchozím nastavení odvodíContent-Type: text/plain; charset="utf-8"
.as_string()
serializuje zprávu do formátu řetězce vhodného pro odeslání přes SMTP nebo uložení do souboru.
Přidání HTML obsahu
Chcete-li odeslat HTML e-mail, jednoduše specifikujte typ obsahu při volání set_content()
. Je dobrým zvykem poskytnout alternativu v prostém textu pro příjemce, jejichž e-mailoví klienti nezobrazují HTML, nebo z důvodů přístupnosti.
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())
Vysvětlení:
add_alternative()
se používá k přidání různých reprezentací *stejného* obsahu. E-mailový klient zobrazí tu „nejlepší“, kterou dokáže zpracovat (obvykle HTML).- Tím se automaticky vytvoří struktura MIME
multipart/alternative
.
Zpracování příloh
Připojování souborů je jednoduché pomocí add_attachment()
. Můžete připojit jakýkoli typ souboru a balíček se postará o příslušné typy MIME a kódování (obvykle 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()
Vysvětlení:
add_attachment()
přijímá surové byty obsahu souboru.maintype
asubtype
specifikují typ MIME (např.application/pdf
,image/png
). Tyto údaje jsou klíčové, aby e-mailový klient příjemce správně identifikoval a zpracoval přílohu.filename
poskytuje název, pod kterým bude příloha uložena příjemcem.- Tím se automaticky nastaví struktura
multipart/mixed
.
Vytváření vícedílných zpráv
Pokud máte zprávu s HTML tělem, záložním prostým textem a vloženými obrázky nebo souvisejícími soubory, potřebujete složitější vícedílnou strukturu. Třída EmailMessage
to inteligentně zvládá pomocí add_related()
a add_alternative()
.
Běžným scénářem je HTML e-mail s obrázkem vloženým přímo do HTML (tzv. „inline“ obrázek). K tomu se používá 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()
Vysvětlení:
set_content()
nastaví počáteční obsah (zde prostý text).add_alternative()
přidá HTML verzi a vytvoří strukturumultipart/alternative
, která obsahuje části s prostým textem a HTML.add_related()
se používá pro obsah, který je „související“ s jednou z částí zprávy, typicky pro vložené obrázky v HTML. Přijímá parametrcid
(Content-ID), který je poté odkazován v HTML tagu<img src="cid:my-banner-image">
.- Konečná struktura bude
multipart/mixed
(pokud by existovaly externí přílohy) obsahující částmultipart/alternative
, která zase obsahuje částmultipart/related
. Částmultipart/related
obsahuje HTML a vložený obrázek. TřídaEmailMessage
se o tuto složitost vnoření postará za vás.
Kódování a znakové sady pro globální dosah
Pro mezinárodní komunikaci je správné kódování znaků prvořadé. Balíček email
je ve výchozím nastavení silně orientován na používání UTF-8, což je univerzální standard pro zpracování různých znakových sad z celého světa.
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())
Vysvětlení:
- Když
set_content()
obdrží Python řetězec, automaticky ho zakóduje do UTF-8 a nastaví hlavičkuContent-Type: text/plain; charset="utf-8"
. - Pokud to obsah vyžaduje (např. obsahuje mnoho ne-ASCII znaků), může také použít
Content-Transfer-Encoding: quoted-printable
nebobase64
, aby zajistil bezpečný přenos přes starší e-mailové systémy. Balíček to zpracovává automaticky podle zvolené politiky.
Vlastní hlavičky a politiky
Do e-mailu můžete přidat jakoukoli vlastní hlavičku. Politiky (z email.policy
) definují, jak jsou zprávy zpracovávány, a ovlivňují aspekty jako kódování hlaviček, konce řádků a zpracování chyb. Výchozí politika je obecně dobrá, ale můžete zvolit SMTP
pro striktní shodu s SMTP nebo definovat vlastní.
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())
Vysvětlení:
- Použití
policy=policy.SMTP
zajišťuje striktní shodu se standardy SMTP, což může být klíčové pro doručitelnost. - Vlastní hlavičky se přidávají stejně jako standardní. Často začínají na
X-
, aby označovaly nestandardní hlavičky.
Parsování MIME zpráv: Extrakce informací z příchozích e-mailů
Parsování zahrnuje převzetí surových e-mailových dat (obvykle přijatých přes IMAP nebo ze souboru) a jejich převedení na objekt `EmailMessage`, který můžete následně snadno prozkoumat a manipulovat s ním.
Načtení a počáteční parsování
Obvykle obdržíte e-maily jako surové byty. K tomu se používá email.parser.BytesParser
(nebo pomocné funkce email.message_from_bytes()
).
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']}")
Vysvětlení:
BytesParser
přijímá surová bytová data (což je způsob, jakým jsou e-maily přenášeny) a vrací objektEmailMessage
.policy=default
specifikuje pravidla parsování.
Přístup k hlavičkám
Hlavičky jsou snadno přístupné pomocí klíčů podobných slovníku. Balíček automaticky zpracovává dekódování zakódovaných hlaviček (např. předměty s mezinárodními znaky).
# ... (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}")
Vysvětlení:
- Přístup k hlavičce vrací její hodnotu jako řetězec.
get_all('header-name')
je užitečné pro hlavičky, které se mohou objevit vícekrát (jakoReceived
).- Balíček zpracovává dekódování hlaviček, takže hodnoty jako
Subject: =?utf-8?Q?Global_Characters:_=E3=81=93=E3=82=93=E3=81=AB=E3=81=A1=E3=81=AF?=
jsou automaticky převedeny na čitelné řetězce.
Extrakce obsahu těla
Extrakce skutečného těla zprávy vyžaduje kontrolu, zda je zpráva vícedílná. U vícedílných zpráv iterujete přes její části.
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")
Vysvětlení:
is_multipart()
určuje, zda má e-mail více částí.iter_parts()
iteruje přes všechny podčásti vícedílné zprávy.get_content_type()
vrací celý typ MIME (např.text/plain
).get_content_charset()
extrahuje znakovou sadu z hlavičkyContent-Type
.get_payload(decode=True)
je klíčové: vrací *dekódovaný* obsah jako byty. Poté je třeba tyto byty.decode()
pomocí správné znakové sady, abyste získali Python řetězec.
Zpracování příloh při parsování
Přílohy jsou také části vícedílné zprávy. Můžete je identifikovat pomocí jejich hlavičky Content-Disposition
a uložit jejich dekódovaný obsah.
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)
Vysvětlení:
iter_attachments()
specificky vrací části, které jsou pravděpodobně přílohami (tj. mají hlavičkuContent-Disposition: attachment
nebo nejsou jinak klasifikovány).get_filename()
extrahuje název souboru z hlavičkyContent-Disposition
.part.get_payload(decode=True)
načte surový binární obsah přílohy, již dekódovaný zbase64
neboquoted-printable
.
Dekódování kódování a znakových sad
Balíček email
odvádí vynikající práci při automatickém dekódování běžných přenosových kódování (jako base64
, quoted-printable
), když zavoláte get_payload(decode=True)
. Pro samotný textový obsah se snaží použít charset
specifikovaný v hlavičce Content-Type
. Pokud není znaková sada specifikována nebo je neplatná, možná budete muset situaci řešit elegantně.
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')}")
Vysvětlení:
- Vždy se snažte použít znakovou sadu specifikovanou v hlavičce
Content-Type
. - Používejte blok
try-except UnicodeDecodeError
pro robustnost, zejména při práci s e-maily z různých a potenciálně nestandardních zdrojů. errors='replace'
neboerrors='ignore'
lze použít s.decode()
pro zpracování znaků, které nelze mapovat na cílové kódování, což zabrání pádům aplikace.
Pokročilé scénáře parsování
E-maily v reálném světě mohou být velmi složité, s vnořenými vícedílnými strukturami. Rekurzivní povaha balíčku email
usnadňuje jejich procházení. Můžete kombinovat is_multipart()
s iter_parts()
pro procházení hluboce vnořených zpráv.
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)
Vysvětlení:
- Rekurzivní funkce
parse_email_part
ukazuje, jak procházet celým stromem zprávy a identifikovat vícedílné části, přílohy a obsah těla na každé úrovni. - Tento vzor je vysoce flexibilní pro extrakci specifických typů obsahu z hluboce vnořených e-mailů.
Konstrukce vs. parsování: Srovnávací perspektiva
Ačkoliv jde o odlišné operace, konstrukce a parsování jsou dvě strany téže mince: zpracování MIME zpráv. Porozumění jednomu nevyhnutelně pomáhá druhému.
Konstrukce (odesílání):
- Zaměření: Správné sestavení hlaviček, obsahu a příloh do standardní MIME struktury.
- Hlavní nástroj:
email.message.EmailMessage
s metodami jakoset_content()
,add_attachment()
,add_alternative()
,add_related()
. - Klíčové výzvy: Zajištění správných typů MIME, znakových sad (zejména UTF-8 pro globální podporu),
Content-Transfer-Encoding
a správného formátování hlaviček. Chyby mohou vést k tomu, že se e-maily nezobrazí správně, přílohy budou poškozené nebo zprávy budou označeny jako spam.
Parsování (příjem):
- Zaměření: Rozložení surového bytového proudu e-mailu na jeho jednotlivé části, extrakce specifických hlaviček, obsahu těla a příloh.
- Hlavní nástroj:
email.parser.BytesParser
neboemail.message_from_bytes()
, poté navigace výsledným objektemEmailMessage
pomocí metod jakois_multipart()
,iter_parts()
,get_payload()
,get_filename()
a přístup k hlavičkám. - Klíčové výzvy: Zpracování špatně formátovaných e-mailů, správná identifikace kódování znaků (zejména pokud jsou nejednoznačné), řešení chybějících hlaviček a robustní extrakce dat z různých struktur MIME.
Zpráva, kterou vytvoříte pomocí `EmailMessage`, by měla být dokonale parsovatelná pomocí `BytesParser`. Podobně, pochopení struktury MIME vytvořené během parsování vám dává vhled do toho, jak si sami vytvářet složité zprávy.
Osvědčené postupy pro globální zpracování e-mailů s Pythonem
Pro aplikace, které interagují s globálním publikem nebo zpracovávají různé zdroje e-mailů, zvažte tyto osvědčené postupy:
- Standardizujte na UTF-8: Vždy používejte UTF-8 pro veškerý textový obsah, jak při vytváření, tak při očekávání během parsování. Je to globální standard pro kódování znaků a zabraňuje tzv. mojibake (zkomolenému textu).
- Validujte e-mailové adresy: Před odesláním ověřte e-mailové adresy příjemců, abyste zajistili doručitelnost. Při parsování buďte připraveni na potenciálně neplatné nebo špatně formátované adresy v hlavičkách `From`, `To` nebo `Cc`.
- Důkladně testujte: Testujte vaši konstrukci e-mailů s různými e-mailovými klienty (Gmail, Outlook, Apple Mail, Thunderbird) a platformami, abyste zajistili konzistentní vykreslování HTML a příloh. Pro parsování testujte s širokou škálou vzorových e-mailů, včetně těch s neobvyklými kódováními, chybějícími hlavičkami nebo složitými vnořenými strukturami.
- Sanitizujte parsovaný vstup: Vždy považujte obsah extrahovaný z příchozích e-mailů za nedůvěryhodný. Sanitizujte HTML obsah, abyste předešli XSS útokům, pokud ho zobrazujete ve webové aplikaci. Ověřujte názvy a typy příloh, abyste předešli zranitelnostem typu path traversal nebo jiným bezpečnostním rizikům při ukládání souborů.
- Robustní zpracování chyb: Implementujte komplexní bloky
try-except
při dekódování obsahu nebo přístupu k potenciálně chybějícím hlavičkám. Elegantně zpracujte chybyUnicodeDecodeError
neboKeyError
. - Zpracování velkých příloh: Mějte na paměti velikost příloh, jak při vytváření (abyste nepřekročili limity poštovního serveru), tak při parsování (abyste předešli nadměrnému využití paměti nebo místa na disku). Zvažte streamování velkých příloh, pokud to váš systém podporuje.
- Využijte
email.policy
: Pro kritické aplikace explicitně zvolteemail.policy
(např.policy.SMTP
), abyste zajistili striktní shodu s e-mailovými standardy, což může ovlivnit doručitelnost a interoperabilitu. - Zachování metadat: Při parsování se rozhodněte, která metadata (hlavičky, původní řetězce hranic) je důležité zachovat, zejména pokud budujete systém pro archivaci nebo přeposílání pošty.
Závěr
Python balíček email
je neuvěřitelně výkonná a flexibilní knihovna pro každého, kdo potřebuje programově interagovat s e-maily. Zvládnutím jak konstrukce MIME zpráv, tak robustního parsování příchozích e-mailů, odemykáte schopnost vytvářet sofistikované systémy pro automatizaci e-mailů, budovat e-mailové klienty, analyzovat e-mailová data a integrovat e-mailové funkce do téměř jakékoli aplikace.
Balíček promyšleně zpracovává základní složitosti MIME, což umožňuje vývojářům soustředit se na obsah a logiku jejich e-mailových interakcí. Ať už posíláte personalizované newslettery globálnímu publiku nebo extrahujete kritická data z automatizovaných systémových reportů, hluboké porozumění balíčku email
se ukáže jako neocenitelné při budování spolehlivých, interoperabilních a globálně orientovaných e-mailových řešení.