BemÀstra e-postautomatisering med Pythons imaplib. Denna djupgÄende guide tÀcker anslutning till IMAP-servrar, sökning, hÀmtning, parsning av e-post, hantering av bilagor och hantering av brevlÄdor som ett proffs.
Python IMAP-klient: En omfattande guide till e-posthÀmtning och brevlÄdehantering
E-post förblir en hörnsten i digital kommunikation för företag och individer över hela vÀrlden. Att hantera en stor volym e-post kan dock vara en tidskrÀvande och repetitiv uppgift. FrÄn att behandla fakturor och filtrera aviseringar till att arkivera viktiga konversationer kan den manuella anstrÀngningen snabbt bli övervÀldigande. Det Àr hÀr programmatisk automatisering lyser, och Python, med sitt rika standardbibliotek, tillhandahÄller kraftfulla verktyg för att ta kontroll över din inkorg.
Denna omfattande guide leder dig genom processen att bygga en Python IMAP-klient frÄn grunden med hjÀlp av det inbyggda imaplib
-biblioteket. Du kommer inte bara att lÀra dig hur du hÀmtar e-post, utan ocksÄ hur du parsar deras innehÄll, laddar ner bilagor och hanterar din brevlÄda genom att markera meddelanden som lÀsta, flytta dem eller ta bort dem. I slutet av den hÀr artikeln kommer du att vara utrustad för att automatisera dina mest trÄkiga e-postuppgifter, vilket sparar tid och ökar din produktivitet.
FörstÄ protokollen: IMAP vs. POP3 vs. SMTP
Innan du dyker ner i koden Àr det viktigt att förstÄ de grundlÀggande protokollen som styr e-post. Du kommer ofta att höra tre akronymer: SMTP, POP3 och IMAP. De tjÀnar alla ett distinkt syfte.
- SMTP (Simple Mail Transfer Protocol): Detta Àr protokollet för att skicka e-post. TÀnk pÄ SMTP som posttjÀnsten som hÀmtar ditt brev och levererar det till mottagarens brevlÄdeserver. NÀr ditt Python-skript skickar ett e-postmeddelande anvÀnder det SMTP.
- POP3 (Post Office Protocol 3): Detta Àr ett protokoll för att hÀmta e-post. POP3 Àr utformat för att ansluta till en server, ladda ner alla nya meddelanden till din lokala klient och sedan, som standard, ta bort dem frÄn servern. Det Àr som att gÄ till postkontoret, samla all din post och ta med den hem; nÀr det vÀl Àr hemma hos dig finns det inte lÀngre pÄ postkontoret. Denna modell Àr mindre vanlig idag pÄ grund av dess begrÀnsningar i en vÀrld med flera enheter.
- IMAP (Internet Message Access Protocol): Detta Àr det moderna protokollet för att fÄ Ätkomst till och hantera e-post. Till skillnad frÄn POP3 lÀmnar IMAP meddelandena pÄ servern och synkroniserar tillstÄndet (lÀst, olÀst, flaggad, raderad) över alla anslutna klienter. NÀr du lÀser ett e-postmeddelande pÄ din telefon visas det som lÀst pÄ din bÀrbara dator. Denna servercentrerade modell Àr perfekt för automatisering eftersom ditt skript kan interagera med brevlÄdan som en annan klient, och de Àndringar det gör kommer att Äterspeglas överallt. I den hÀr guiden kommer vi att fokusera uteslutande pÄ IMAP.
Kom igÄng med Pythons imaplib
Pythons standardbibliotek innehÄller imaplib
, en modul som tillhandahÄller alla nödvÀndiga verktyg för att kommunicera med en IMAP-server. Inga externa paket krÀvs för att komma igÄng.
FörutsÀttningar
- Python installerat: Se till att du har en ny version av Python (3.6 eller nyare) installerad pÄ ditt system.
- Ett e-postkonto med IMAP aktiverat: De flesta moderna e-postleverantörer (Gmail, Outlook, Yahoo, etc.) stöder IMAP. Du kan behöva aktivera det i ditt kontos instÀllningar.
SÀkerhet först: AnvÀnd app-lösenord, inte ditt huvudlösenord
Detta Àr det viktigaste steget för sÀkerheten. HÄrdkoda inte ditt huvudlösenord för e-postkontot i ditt skript. Om din kod nÄgonsin komprometteras Àr hela ditt konto i riskzonen. De flesta stora e-postleverantörer som anvÀnder tvÄfaktorautentisering (2FA) krÀver att du genererar ett "app-lösenord".
Ett app-lösenord Àr en unik, 16-siffrig kod som ger ett specifikt program tillstÄnd att komma Ät ditt konto utan att behöva ditt primÀra lösenord eller 2FA-koder. Du kan generera ett och Äterkalla det nÀr som helst utan att pÄverka ditt huvudlösenord.
- För Gmail: GÄ till dina Google-kontoinstÀllningar -> SÀkerhet -> TvÄstegsverifiering -> App-lösenord.
- För Outlook/Microsoft: GÄ till din Microsoft-kontos sÀkerhetsinstrumentpanel -> Avancerade sÀkerhetsalternativ -> App-lösenord.
- För andra leverantörer: Sök i deras dokumentation efter "app password" eller "application-specific password".
NÀr du vÀl har genererat det, behandla detta app-lösenord som alla andra legitimationer. En bÀsta praxis Àr att lagra det i en miljövariabel eller ett sÀkert system för hemlighetshantering istÀllet för direkt i din kÀllkod.
Den grundlÀggande anslutningen
LÄt oss skriva vÄr första kod för att upprÀtta en sÀker anslutning till en IMAP-server, logga in och sedan logga ut pÄ ett smidigt sÀtt. Vi kommer att anvÀnda imaplib.IMAP4_SSL
för att sÀkerstÀlla att vÄr anslutning Àr krypterad.
import imaplib
import os
# --- Credentials ---
# It's best to load these from environment variables or a config file
# For this example, we'll define them here. Replace with your details.
EMAIL_ACCOUNT = "your_email@example.com"
APP_PASSWORD = "your_16_digit_app_password"
IMAP_SERVER = "imap.example.com" # e.g., "imap.gmail.com"
# --- Connect to the IMAP server ---
# We use a try...finally block to ensure we logout gracefully
conn = None
try:
# Connect using SSL for a secure connection
conn = imaplib.IMAP4_SSL(IMAP_SERVER)
# Login to the account
status, messages = conn.login(EMAIL_ACCOUNT, APP_PASSWORD)
if status == 'OK':
print("Successfully logged in!")
# We will add more logic here later
else:
print(f"Login failed: {messages}")
finally:
if conn:
# Always logout and close the connection
conn.logout()
print("Logged out and connection closed.")
Det hÀr skriptet upprÀttar en grund. Blocket try...finally
Àr avgörande eftersom det garanterar att conn.logout()
anropas, vilket stÀnger sessionen med servern, Àven om ett fel intrÀffar under vÄra operationer.
Navigera i din brevlÄda
NÀr du vÀl Àr inloggad kan du börja interagera med brevlÄdorna (kallas ofta mappar) i ditt konto.
Lista alla brevlÄdor
För att se vilka brevlÄdor som Àr tillgÀngliga kan du anvÀnda metoden conn.list()
. Utdata kan vara lite rörig, sÄ lite parsning krÀvs för att fÄ en ren lista med namn.
# Inside the 'try' block after a successful login:
status, mailbox_list = conn.list()
if status == 'OK':
print("Available Mailboxes:")
for mailbox in mailbox_list:
# The raw mailbox entry is a byte string that needs decoding
# It's often formatted like: (\HasNoChildren) "/" "INBOX"
# We can do some basic parsing to clean it up
parts = mailbox.decode().split(' "/" ')
if len(parts) == 2:
mailbox_name = parts[1].strip('"')
print(f"- {mailbox_name}")
Detta kommer att skriva ut en lista som 'INBOX', 'Sent', '[Gmail]/Spam', etc., beroende pÄ din e-postleverantör.
VÀlja en brevlÄda
Innan du kan söka eller hÀmta e-postmeddelanden mÄste du vÀlja en brevlÄda att arbeta med. Det vanligaste valet Àr 'INBOX'. Metoden conn.select()
gör en brevlÄda aktiv. Du kan ocksÄ öppna den i skrivskyddat lÀge om du inte har för avsikt att göra Àndringar (som att markera e-postmeddelanden som lÀsta).
# Select the 'INBOX' to work with.
# Use readonly=True if you don't want to change email flags (e.g., from UNSEEN to SEEN)
status, messages = conn.select('INBOX', readonly=False)
if status == 'OK':
total_messages = int(messages[0])
print(f"INBOX selected. Total messages: {total_messages}")
else:
print(f"Failed to select INBOX: {messages}")
NÀr du vÀljer en brevlÄda returnerar servern det totala antalet meddelanden den innehÄller. Alla efterföljande kommandon för sökning och hÀmtning gÀller för den valda brevlÄdan.
Söka och hÀmta e-postmeddelanden
Detta Àr kÀrnan i e-posthÀmtning. Processen bestÄr av tvÄ steg: först söka efter meddelanden som matchar specifika kriterier för att fÄ deras unika ID:n, och för det andra hÀmta innehÄllet i dessa meddelanden med hjÀlp av deras ID:n.
Kraften i search()
Metoden search()
Àr otroligt mÄngsidig. Den returnerar inte sjÀlva e-postmeddelandena, utan snarare en lista med meddelandesekvensnummer (ID:n) som matchar din frÄga. Dessa ID:n Àr specifika för den aktuella sessionen och den valda brevlÄdan.
HÀr Àr nÄgra av de vanligaste sökkriterierna:
'ALL'
: Alla meddelanden i brevlÄdan.'UNSEEN'
: Meddelanden som inte har lÀsts Ànnu.'SEEN'
: Meddelanden som har lÀsts.'FROM "sender@example.com"'
: Meddelanden frÄn en specifik avsÀndare.'TO "recipient@example.com"'
: Meddelanden som skickats till en specifik mottagare.'SUBJECT "Your Subject Line"'
: Meddelanden med ett specifikt Àmne.'BODY "a keyword in the body"'
: Meddelanden som innehÄller en viss strÀng i brödtexten.'SINCE "01-Jan-2024"'
: Meddelanden som tagits emot pÄ eller efter ett visst datum.'BEFORE "31-Jan-2024"'
: Meddelanden som tagits emot före ett visst datum.
Du kan ocksÄ kombinera kriterier. Till exempel, för att hitta alla olÀsta e-postmeddelanden frÄn en specifik avsÀndare med ett visst Àmne, skulle du söka efter '(UNSEEN FROM "alerts@example.com" SUBJECT "System Alert")'
.
LÄt oss se det i praktiken:
# Search for all unread emails in the INBOX
status, message_ids = conn.search(None, 'UNSEEN')
if status == 'OK':
# message_ids is a list of byte strings, e.g., [b'1 2 3']
# We need to split it into individual IDs
email_id_list = message_ids[0].split()
if email_id_list:
print(f"Found {len(email_id_list)} unread emails.")
else:
print("No unread emails found.")
else:
print("Search failed.")
HÀmta e-postinnehÄll med fetch()
Nu nÀr du har meddelande-ID:na kan du anvÀnda metoden fetch()
för att hÀmta den faktiska e-postdatan. Du mÄste ange vilka delar av e-postmeddelandet du vill ha.
'RFC822'
: Detta hÀmtar hela det rÄa e-postinnehÄllet, inklusive alla huvuden och brödtextdelar. Det Àr det vanligaste och mest omfattande alternativet.'BODY[]'
: En synonym förRFC822
.'ENVELOPE'
: HÀmtar viktig huvudinformation som datum, Àmne, frÄn, till och svar-till. Detta Àr snabbare om du bara behöver metadata.'BODY[HEADER]'
: HĂ€mtar bara huvudena.
LÄt oss hÀmta hela innehÄllet i det första olÀsta e-postmeddelandet vi hittade:
if email_id_list:
first_email_id = email_id_list[0]
# Fetch the email data for the given ID
# 'RFC822' is a standard that specifies the format of text messages
status, msg_data = conn.fetch(first_email_id, '(RFC822)')
if status == 'OK':
for response_part in msg_data:
# The fetch command returns a tuple, where the second part is the email content
if isinstance(response_part, tuple):
raw_email = response_part[1]
# Now we have the raw email data as bytes
# The next step is to parse it
print("Successfully fetched an email.")
# We will process `raw_email` in the next section
else:
print("Fetch failed.")
Parsa e-postinnehÄll med modulen email
Den rÄa datan som returneras av fetch()
Àr en bytestrÀng formaterad enligt RFC 822-standarden. Det Àr inte lÀttlÀst. Pythons inbyggda email
-modul Àr speciellt utformad för att parsa dessa rÄa meddelanden till en anvÀndarvÀnlig objektstruktur.
Skapa ett Message
-objekt
Det första steget Àr att konvertera den rÄa bytestrÀngen till ett Message
-objekt med hjÀlp av email.message_from_bytes()
.
import email
from email.header import decode_header
# Assuming `raw_email` contains the byte data from the fetch command
email_message = email.message_from_bytes(raw_email)
Extrahera viktig information (Huvuden)
NÀr du vÀl har Message
-objektet kan du komma Ät dess huvuden som en ordlista.
# Get subject, from, to, and date
subject = email_message["Subject"]
from_ = email_message["From"]
to_ = email_message["To"]
date_ = email_message["Date"]
# Email headers can contain non-ASCII characters, so we need to decode them
def decode_email_header(header):
decoded_parts = decode_header(header)
header_str = ""
for part, encoding in decoded_parts:
if isinstance(part, bytes):
# If there's an encoding, use it. Otherwise, default to utf-8.
header_str += part.decode(encoding or 'utf-8')
else:
header_str += part
return header_str
subject = decode_email_header(subject)
from_ = decode_email_header(from_)
print(f"Subject: {subject}")
print(f"From: {from_}")
HjÀlpfunktionen decode_email_header
Àr viktig eftersom huvuden ofta Àr kodade för att hantera internationella teckenuppsÀttningar. Att helt enkelt komma Ät email_message["Subject"]
kan ge dig en strÀng med förvirrande teckensekvenser om du inte avkodar den korrekt.
Hantera e-posttexter och bilagor
Moderna e-postmeddelanden Àr ofta "multipart", vilket innebÀr att de innehÄller olika versioner av innehÄllet (som ren text och HTML) och kan ocksÄ innehÄlla bilagor. Vi mÄste gÄ igenom dessa delar för att hitta det vi letar efter.
Metoden msg.is_multipart()
talar om för oss om ett e-postmeddelande har flera delar, och msg.walk()
ger ett enkelt sÀtt att iterera genom dem.
def process_email_body(msg):
body = ""
attachments = []
if msg.is_multipart():
# Iterate through email parts
for part in msg.walk():
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
try:
# Get the email body
if content_type == "text/plain" and "attachment" not in content_disposition:
payload = part.get_payload(decode=True)
charset = part.get_content_charset() or 'utf-8'
body = payload.decode(charset)
# Get attachments
elif "attachment" in content_disposition:
filename = part.get_filename()
if filename:
# Decode filename if needed
decoded_filename = decode_email_header(filename)
attachments.append({
'filename': decoded_filename,
'data': part.get_payload(decode=True)
})
except Exception as e:
print(f"Error processing part: {e}")
else:
# Not a multipart message, just get the payload
payload = msg.get_payload(decode=True)
charset = msg.get_content_charset() or 'utf-8'
body = payload.decode(charset)
return body, attachments
# Using the function with our fetched message
email_body, email_attachments = process_email_body(email_message)
print("\n--- Email Body ---")
print(email_body)
if email_attachments:
print("\n--- Attachments ---")
for att in email_attachments:
print(f"Filename: {att['filename']}")
# Example of saving an attachment
with open(att['filename'], 'wb') as f:
f.write(att['data'])
print(f"Saved attachment: {att['filename']}")
Den hÀr funktionen skiljer intelligent mellan ren textbrödtext och filbilagor genom att inspektera Content-Type
och Content-Disposition
-huvudena för varje del.
Avancerad brevlÄdehantering
Att hÀmta e-post Àr bara halva striden. Sann automatisering innebÀr att Àndra statusen för meddelanden pÄ servern. Kommandot store()
Àr ditt primÀra verktyg för detta.
Markera e-postmeddelanden (lÀsta, olÀsta, flaggade)
Du kan lÀgga till, ta bort eller ersÀtta flaggor pÄ ett meddelande. Den vanligaste flaggan Àr \Seen
, som styr lÀs-/olÀststatusen.
- Markera som lÀst:
conn.store(msg_id, '+FLAGS', '\Seen')
- Markera som olÀst:
conn.store(msg_id, '-FLAGS', '\Seen')
- Flagga/stjÀrnmÀrk ett e-postmeddelande:
conn.store(msg_id, '+FLAGS', '\Flagged')
- Ta bort flaggan frÄn ett e-postmeddelande:
conn.store(msg_id, '-FLAGS', '\Flagged')
Kopiera och flytta e-postmeddelanden
Det finns inget direkt kommando för att "flytta" i IMAP. Att flytta ett e-postmeddelande Àr en process i tvÄ steg:
- Kopiera meddelandet till mÄlbrevlÄdan med hjÀlp av
conn.copy()
. - Markera originalmeddelandet för radering med hjÀlp av flaggan
\Deleted
.
# Assuming `msg_id` is the ID of the email to move
# 1. Copy to the 'Archive' mailbox
status, _ = conn.copy(msg_id, 'Archive')
if status == 'OK':
print(f"Message {msg_id.decode()} copied to Archive.")
# 2. Mark the original for deletion
conn.store(msg_id, '+FLAGS', '\Deleted')
print(f"Message {msg_id.decode()} marked for deletion.")
Ta bort e-postmeddelanden permanent
Att markera ett meddelande med \Deleted
tar inte omedelbart bort det. Det döljer det helt enkelt frÄn vyn i de flesta e-postklienter. För att permanent ta bort alla meddelanden i den valda brevlÄdan som Àr markerade för radering mÄste du anropa metoden expunge()
.
Varning: expunge()
Àr oÄterkallelig. NÀr den vÀl har anropats Àr datan borta för gott.
# This will permanently delete all messages with the \Deleted flag
status, response = conn.expunge()
if status == 'OK':
print(f"{len(response)} messages expunged (permanently deleted).")
En avgörande bieffekt av expunge()
Àr att det kan numrera om meddelande-ID:na för alla efterföljande meddelanden i brevlÄdan. Av denna anledning Àr det bÀst att identifiera alla meddelanden du vill bearbeta, utföra dina ÄtgÀrder (som att kopiera och markera för radering) och sedan anropa expunge()
en gÄng i slutet av din session.
SĂ€tta ihop allt: Ett praktiskt exempel
LÄt oss skapa ett komplett skript som utför en verklig uppgift: Skanna inkorgen efter olÀsta e-postmeddelanden frÄn "invoices@mycorp.com", ladda ner eventuella PDF-bilagor och flytta det bearbetade e-postmeddelandet till en brevlÄda som heter "Processed-Invoices".
import imaplib
import email
from email.header import decode_header
import os
# --- Configuration ---
EMAIL_ACCOUNT = "your_email@example.com"
APP_PASSWORD = "your_16_digit_app_password"
IMAP_SERVER = "imap.gmail.com"
TARGET_SENDER = "invoices@mycorp.com"
DESTINATION_MAILBOX = "Processed-Invoices"
DOWNLOAD_DIR = "invoices"
# Create download directory if it doesn't exist
if not os.path.isdir(DOWNLOAD_DIR):
os.mkdir(DOWNLOAD_DIR)
def decode_email_header(header):
# (Same function as defined earlier)
decoded_parts = decode_header(header)
header_str = ""
for part, encoding in decoded_parts:
if isinstance(part, bytes):
header_str += part.decode(encoding or 'utf-8')
else:
header_str += part
return header_str
conn = None
try:
# --- Connect and Login ---
conn = imaplib.IMAP4_SSL(IMAP_SERVER)
conn.login(EMAIL_ACCOUNT, APP_PASSWORD)
print("Login successful.")
# --- Select INBOX ---
conn.select('INBOX')
print("INBOX selected.")
# --- Search for emails ---
search_criteria = f'(UNSEEN FROM "{TARGET_SENDER}")'
status, message_ids = conn.search(None, search_criteria)
if status != 'OK':
raise Exception("Search failed")
email_id_list = message_ids[0].split()
if not email_id_list:
print("No new invoices found.")
else:
print(f"Found {len(email_id_list)} new invoices to process.")
# --- Process Each Email ---
for email_id in email_id_list:
print(f"\nProcessing email ID: {email_id.decode()}")
# Fetch the email
status, msg_data = conn.fetch(email_id, '(RFC822)')
if status != 'OK':
print(f"Failed to fetch email ID {email_id.decode()}")
continue
raw_email = msg_data[0][1]
email_message = email.message_from_bytes(raw_email)
subject = decode_email_header(email_message["Subject"])
print(f" Subject: {subject}")
# Look for attachments
for part in email_message.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get('Content-Disposition') is None:
continue
filename = part.get_filename()
if filename and filename.lower().endswith('.pdf'):
decoded_filename = decode_email_header(filename)
filepath = os.path.join(DOWNLOAD_DIR, decoded_filename)
# Save the attachment
with open(filepath, 'wb') as f:
f.write(part.get_payload(decode=True))
print(f" -> Downloaded attachment: {decoded_filename}")
# --- Move the processed email ---
# 1. Copy to destination mailbox
status, _ = conn.copy(email_id, DESTINATION_MAILBOX)
if status == 'OK':
# 2. Mark original for deletion
conn.store(email_id, '+FLAGS', '\Deleted')
print(f" Email moved to '{DESTINATION_MAILBOX}'.")
# --- Expunge and Clean Up ---
if email_id_list:
conn.expunge()
print("\nExpunged deleted emails.")
except Exception as e:
print(f"An error occurred: {e}")
finally:
if conn:
conn.logout()
print("Logged out.")
BĂ€sta metoder och felhantering
NÀr du bygger robusta automatiseringsskript, övervÀg följande bÀsta metoder:
- Robust felhantering: Kapsla in din kod i
try...except
-block för att fÄnga potentiella problem som inloggningsfel (imaplib.IMAP4.error
), nÀtverksproblem eller parsningsfel. - Konfigurationshantering: HÄrdkoda aldrig legitimationer. AnvÀnd miljövariabler (
os.getenv()
), en konfigurationsfil (t.ex. INI eller YAML) eller en dedikerad hemlighetshanterare. - Loggning: IstÀllet för
print()
-satser, anvÀnd Pythonslogging
-modul. Det lÄter dig kontrollera utförligheten i din utdata, skriva till filer och lÀgga till tidsstÀmplar, vilket Àr ovÀrderligt för att felsöka skript som körs utan övervakning. - HastighetsbegrÀnsning: Var en bra internetmedborgare. FrÄga inte e-postservern för mycket. Om du behöver kontrollera om det finns ny e-post ofta, övervÀg intervall pÄ flera minuter istÀllet för sekunder.
- Teckenkodningar: E-post Àr en global standard, och du kommer att stöta pÄ olika teckenkodningar. Försök alltid att faststÀlla teckenuppsÀttningen frÄn e-postdelen (
part.get_content_charset()
) och ha en reserv (som 'utf-8') för att undvikaUnicodeDecodeError
.
Slutsats
Du har nu rest genom hela livscykeln för att interagera med en e-postserver med hjÀlp av Pythons imaplib
. Vi har tÀckt att upprÀtta en sÀker anslutning, lista brevlÄdor, utföra kraftfulla sökningar, hÀmta och parsa komplexa multipart-e-postmeddelanden, ladda ner bilagor och hantera meddelandestatusar pÄ servern.
Kraften i denna kunskap Àr enorm. Du kan bygga system för att automatiskt kategorisera supportÀrenden, parsa data frÄn dagliga rapporter, arkivera nyhetsbrev, utlösa ÄtgÀrder baserat pÄ varningsmeddelanden och mycket mer. Inkorgen, en gÄng en kÀlla till manuellt arbete, kan bli en kraftfull, automatiserad datakÀlla för dina applikationer och arbetsflöden.
Vilka e-postuppgifter kommer du att automatisera först? Möjligheterna begrÀnsas bara av din fantasi. Börja i liten skala, bygg vidare pÄ exemplen i den hÀr guiden och Äterta din tid frÄn djupet av din inkorg.