Avtomatizirajte e-pošto s Pythonovo knjižnico imaplib. Ta vodnik obravnava IMAP strežnike, iskanje, pridobivanje in razčlenjevanje e-pošte, priponke ter upravljanje poštnih predalov.
Python IMAP odjemalec: Celovit vodnik za pridobivanje e-pošte in upravljanje poštnih predalov
E-pošta ostaja temelj digitalne komunikacije za podjetja in posameznike po vsem svetu. Vendar pa je upravljanje velikega števila e-poštnih sporočil lahko dolgotrajno in ponavljajoče se opravilo. Od obdelave računov in filtriranja obvestil do arhiviranja pomembnih pogovorov – ročno delo lahko hitro postane preobsežno. Tu se pokaže moč programatične avtomatizacije, Python pa s svojo bogato standardno knjižnico ponuja zmogljiva orodja za prevzem nadzora nad vašim poštnim predalom.
Ta celovit vodnik vas bo vodil skozi proces gradnje Python IMAP odjemalca od začetka z uporabo vgrajene knjižnice imaplib
. Naučili se boste ne le, kako pridobiti e-pošto, ampak tudi, kako razčleniti njeno vsebino, prenesti priponke in upravljati svoj poštni predal z označevanjem sporočil kot prebranih, premikanjem ali brisanjem. Ob koncu tega članka boste opremljeni za avtomatizacijo vaših najbolj zamudnih e-poštnih opravil, kar vam bo prihranilo čas in povečalo vašo produktivnost.
Razumevanje protokolov: IMAP proti POP3 proti SMTP
Preden se poglobite v kodo, je bistveno razumeti temeljne protokole, ki urejajo e-pošto. Pogosto boste slišali tri kratice: SMTP, POP3 in IMAP. Vsak od njih služi svojemu namenu.
- SMTP (Simple Mail Transfer Protocol): To je protokol za pošiljanje e-pošte. Predstavljajte si SMTP kot pošto, ki pobere vaše pismo in ga dostavi na strežnik prejemnikovega poštnega predala. Ko vaš Python skript pošlje e-pošto, uporablja SMTP.
- POP3 (Post Office Protocol 3): To je protokol za pridobivanje e-pošte. POP3 je zasnovan za povezavo s strežnikom, prenos vseh novih sporočil na vaš lokalni odjemalec in nato, privzeto, njihovo brisanje s strežnika. To je kot, da greste na pošto, poberete vso svojo pošto in jo odnesete domov; ko je pri vas doma, je ni več na pošti. Ta model je danes manj pogost zaradi svojih omejitev v svetu z več napravami.
- IMAP (Internet Message Access Protocol): To je sodoben protokol za dostopanje in upravljanje e-pošte. Za razliko od POP3, IMAP pusti sporočila na strežniku in sinhronizira stanje (prebrano, neprebrano, označeno, izbrisano) med vsemi povezanimi odjemalci. Ko preberete e-pošto na telefonu, se ta prikaže kot prebrana tudi na vašem prenosniku. Ta strežniško usmerjen model je popoln za avtomatizacijo, saj lahko vaš skript komunicira s poštnim predalom kot drug odjemalec, in spremembe, ki jih naredi, se bodo odražale povsod. Za ta vodnik se bomo osredotočili izključno na IMAP.
Začetek z Pythonovo knjižnico imaplib
Standardna Pythonova knjižnica vključuje imaplib
, modul, ki ponuja vsa potrebna orodja za komunikacijo z IMAP strežnikom. Za začetek niso potrebni zunanji paketi.
Predpogoji
- Nameščen Python: Zagotovite, da imate na svojem sistemu nameščeno novejšo različico Pythona (3.6 ali novejšo).
- E-poštni račun z omogočenim IMAP-om: Večina sodobnih ponudnikov e-pošte (Gmail, Outlook, Yahoo, itd.) podpira IMAP. Morda ga boste morali omogočiti v nastavitvah svojega računa.
Najprej varnost: Uporabite gesla za aplikacije, ne svojega glavnega gesla
To je najpomembnejši korak za varnost. Ne vpisujte svojega glavnega gesla e-poštnega računa neposredno v svoj skript. Če je vaša koda kdaj ogrožena, je ogrožen celoten vaš račun. Večina večjih ponudnikov e-pošte, ki uporabljajo dvofaktorsko avtentikacijo (2FA), zahteva, da ustvarite "geslo za aplikacije" (App Password).
Geslo za aplikacije je edinstvena 16-mestna koda, ki določeni aplikaciji omogoča dostop do vašega računa, ne da bi potrebovali vaše glavno geslo ali kode za 2FA. Lahko ga ustvarite in prekličete kadar koli, ne da bi to vplivalo na vaše glavno geslo.
- Za Gmail: Pojdite v nastavitve Google računa -> Varnost -> Preverjanje v dveh korakih -> Gesla za aplikacije.
- Za Outlook/Microsoft: Pojdite na varnostno nadzorno ploščo Microsoft računa -> Napredne varnostne možnosti -> Gesla za aplikacije.
- Za druge ponudnike: Poiščite v njihovi dokumentaciji "geslo za aplikacijo" ali "geslo, specifično za aplikacijo".
Ko je geslo za aplikacije ustvarjeno, ga obravnavajte kot katero koli drugo poverilnico. Najboljša praksa je, da ga shranite v okoljsko spremenljivko ali v varen sistem za upravljanje skrivnosti, namesto neposredno v svojo izvorno kodo.
Osnovna povezava
Napišimo naš prvi del kode za vzpostavitev varne povezave z IMAP strežnikom, prijavo in nato urejeno odjavo. Uporabili bomo imaplib.IMAP4_SSL
, da zagotovimo šifrirano povezavo.
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.")
Ta skript vzpostavlja temelj. Blok try...finally
je ključen, saj zagotavlja, da se conn.logout()
pokliče in zapre sejo s strežnikom, tudi če med našimi operacijami pride do napake.
Krmarjenje po vašem poštnem predalu
Ko ste prijavljeni, lahko začnete komunicirati s poštnimi predali (pogosto imenovanimi mape) v vašem računu.
Seznam vseh poštnih predalov
Za pregled razpoložljivih poštnih predalov lahko uporabite metodo conn.list()
. Izhod je lahko nekoliko neurejen, zato je potrebno nekaj razčlenjevanja, da dobite čist seznam imen.
# 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}")
To bo izpisalo seznam kot 'INBOX', 'Sent', '[Gmail]/Spam' itd., odvisno od vašega ponudnika e-pošte.
Izbira poštnega predala
Preden lahko iščete ali pridobivate e-pošto, morate izbrati poštni predal, s katerim boste delali. Najpogostejša izbira je 'INBOX'. Metoda conn.select()
aktivira poštni predal. Odprete ga lahko tudi v načinu samo za branje, če ne nameravate delati sprememb (kot je označevanje e-pošte kot prebrane).
# 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}")
Ko izberete poštni predal, strežnik vrne skupno število sporočil, ki jih vsebuje. Vsi naslednji ukazi za iskanje in pridobivanje se bodo nanašali na ta izbrani poštni predal.
Iskanje in pridobivanje e-poštnih sporočil
To je jedro pridobivanja e-pošte. Postopek vključuje dva koraka: najprej iskanje sporočil, ki ustrezajo določenim kriterijem, da dobimo njihove edinstvene ID-je, in drugič, pridobivanje vsebine teh sporočil z uporabo njihovih ID-jev.
Moč metode search()
Metoda search()
je neverjetno vsestranska. Ne vrne samih e-poštnih sporočil, ampak seznam zaporednih številk sporočil (ID-jev), ki ustrezajo vaši poizvedbi. Ti ID-ji so specifični za trenutno sejo in izbrani poštni predal.
Tukaj so nekateri najpogostejši kriteriji iskanja:
'ALL'
: Vsa sporočila v poštnem predalu.'UNSEEN'
: Sporočila, ki še niso bila prebrana.'SEEN'
: Sporočila, ki so bila prebrana.'FROM "sender@example.com"'
: Sporočila od določenega pošiljatelja.'TO "recipient@example.com"'
: Sporočila, poslana določenemu prejemniku.'SUBJECT "Your Subject Line"'
: Sporočila z določeno zadevo.'BODY "a keyword in the body"'
: Sporočila, ki vsebujejo določeno besedno zvezo v telesu.'SINCE "01-Jan-2024"'
: Sporočila, prejeta na določen datum ali po njem.'BEFORE "31-Jan-2024"'
: Sporočila, prejeta pred določenim datumom.
Kriterije lahko tudi kombinirate. Na primer, za iskanje vseh neprebranih e-poštnih sporočil od določenega pošiljatelja z določeno zadevo bi iskali '(UNSEEN FROM "alerts@example.com" SUBJECT "System Alert")'
.
Poglejmo si to v praksi:
# 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.")
Pridobivanje vsebine e-pošte z metodo fetch()
Sedaj, ko imate ID-je sporočil, lahko uporabite metodo fetch()
za pridobivanje dejanskih podatkov e-pošte. Določiti morate, katere dele e-pošte želite.
'RFC822'
: To pridobi celotno surovo vsebino e-pošte, vključno z vsemi glavami in deli telesa. Je najpogostejša in najobsežnejša možnost.'BODY[]'
: Sinonim zaRFC822
.'ENVELOPE'
: Pridobi ključne informacije iz glave, kot so Datum, Zadeva, Od, Za in Odgovori-komu. To je hitreje, če potrebujete le metapodatke.'BODY[HEADER]'
: Pridobi samo glave.
Pridobimo celotno vsebino prvega neprebranega e-poštnega sporočila, ki smo ga našli:
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.")
Razčlenjevanje vsebine e-pošte z modulom email
Surovi podatki, ki jih vrne fetch()
, so bajtni niz, formatiran v skladu s standardom RFC 822. Ni enostaven za branje. Pythonov vgrajeni modul email
je zasnovan posebej za razčlenjevanje teh surovih sporočil v uporabniku prijazno objektno strukturo.
Ustvarjanje objekta Message
Prvi korak je pretvorba surovega bajtnega niza v objekt Message
z uporabo 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)
Pridobivanje ključnih informacij (glav)
Ko imate objekt Message
, lahko dostopate do njegovih glav kot do slovarja.
# 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_}")
Pomožna funkcija decode_email_header
je pomembna, ker so glave pogosto kodirane za obravnavo mednarodnih naborov znakov. Preprost dostop do email_message["Subject"]
vam lahko vrne niz z zmedenimi zaporedji znakov, če ga ne dekodirate pravilno.
Obravnava teles e-pošte in priponk
Sodobna e-poštna sporočila so pogosto "večdelna", kar pomeni, da vsebujejo različne različice vsebine (kot so golo besedilo in HTML) in lahko vključujejo tudi priponke. Te dele moramo pregledati, da najdemo tisto, kar iščemo.
Metoda msg.is_multipart()
nam pove, ali ima e-poštno sporočilo več delov, msg.walk()
pa omogoča enostaven način za iteracijo skozi njih.
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']}")
Ta funkcija inteligentno razlikuje med golim besedilom telesa in datotečnimi priponkami s pregledovanjem glav Content-Type
in Content-Disposition
vsakega dela.
Napredno upravljanje poštnih predalov
Pridobivanje e-pošte je le polovica bitke. Prava avtomatizacija vključuje spreminjanje stanja sporočil na strežniku. Ukaz store()
je vaše primarno orodje za to.
Označevanje e-pošte (prebrano, neprebrano, označeno)
Na sporočilo lahko dodate, odstranite ali zamenjate zastavice. Najpogostejša zastavica je \Seen
, ki nadzoruje stanje prebrano/neprebrano.
- Označi kot prebrano:
conn.store(msg_id, '+FLAGS', '\Seen')
- Označi kot neprebrano:
conn.store(msg_id, '-FLAGS', '\Seen')
- Označi/zvezda e-pošte:
conn.store(msg_id, '+FLAGS', '\Flagged')
- Odstrani oznako e-pošte:
conn.store(msg_id, '-FLAGS', '\Flagged')
Kopiranje in premikanje e-pošte
V IMAP-u ni neposrednega ukaza "premikanje". Premikanje e-pošte je dvostopenjski postopek:
- Kopirajte sporočilo v ciljni poštni predal z uporabo
conn.copy()
. - Označite izvirno sporočilo za brisanje z uporabo zastavice
\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.")
Trajno brisanje e-pošte
Označitev sporočila z \Deleted
ga ne odstrani takoj. V večini e-poštnih odjemalcev ga preprosto skrije pred pogledom. Za trajno odstranitev vseh sporočil v trenutno izbranem poštnem predalu, ki so označena za brisanje, morate poklicati metodo expunge()
.
Opozorilo: expunge()
je nepovratno. Ko je enkrat poklicano, so podatki za vedno izginili.
# 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).")
Pomemben stranski učinek expunge()
je, da lahko ponovno oštevilči ID-je sporočil za vsa naslednja sporočila v poštnem predalu. Zato je najbolje, da prepoznate vsa sporočila, ki jih želite obdelati, izvedete svoja dejanja (kot sta kopiranje in označevanje za brisanje) in nato pokličete expunge()
enkrat na samem koncu vaše seje.
Vse skupaj: Praktičen primer
Ustvarimo popoln skript, ki opravlja nalogo iz resničnega sveta: Pregledovanje mape »Prejeto« za neprebrana e-poštna sporočila od "invoices@mycorp.com", prenos vseh PDF priponk in premik obdelane e-pošte v poštni predal z imenom "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.")
Najboljše prakse in obravnava napak
Pri gradnji robustnih avtomatizacijskih skript upoštevajte naslednje najboljše prakse:
- Zanesljivo obravnavanje napak: Kodo ovijte v bloke
try...except
, da ujamete morebitne težave, kot so napake pri prijavi (imaplib.IMAP4.error
), omrežne težave ali napake pri razčlenjevanju. - Upravljanje konfiguracije: Nikoli ne vpisujte poverilnic neposredno v kodo. Uporabite okoljske spremenljivke (
os.getenv()
), konfiguracijsko datoteko (npr. INI ali YAML) ali namenski upravitelj skrivnosti. - Beleženje: Namesto izjav
print()
uporabite Pythonov modullogging
. Omogoča vam nadzor nad podrobnostjo izpisa, pisanje v datoteke in dodajanje časovnih žigov, kar je neprecenljivo za odpravljanje napak v skriptah, ki se izvajajo brez nadzora. - Omejitev števila zahtev (Rate Limiting): Bodite dober državljan interneta. Ne poizvedujte e-poštnega strežnika pretirano. Če morate pogosto preverjati novo pošto, razmislite o intervalih nekaj minut namesto sekund.
- Kodiranja znakov: E-pošta je globalni standard in naleteli boste na različna kodiranja znakov. Vedno poskusite določiti nabor znakov iz e-poštnega dela (
part.get_content_charset()
) in imejte nadomestno možnost (kot je 'utf-8'), da se izognete napakiUnicodeDecodeError
.
Zaključek
Sedaj ste potovali skozi celoten življenjski cikel interakcije z e-poštnim strežnikom z uporabo Pythonove knjižnice imaplib
. Pokrili smo vzpostavitev varne povezave, seznam poštnih predalov, izvajanje zmogljivih iskanj, pridobivanje in razčlenjevanje kompleksnih večdelnih e-poštnih sporočil, prenos priponk in upravljanje stanj sporočil na strežniku.
Moč tega znanja je ogromna. Zgradite lahko sisteme za avtomatsko kategorizacijo podpornih zahtevkov, razčlenjevanje podatkov iz dnevnih poročil, arhiviranje novic, sprožanje dejanj na podlagi opozorilnih e-poštnih sporočil in še veliko več. Prejeto, nekoč vir ročnega dela, lahko postane zmogljiv, avtomatiziran vir podatkov za vaše aplikacije in delovne tokove.
Katere e-poštne naloge boste avtomatizirali najprej? Možnosti so omejene le z vašo domišljijo. Začnite z majhnimi koraki, gradite na primerih v tem vodniku in si povrnite čas iz globin vašega poštnjega predala.