Python imaplib 教程:使用 IMAP 协议管理邮件
imaplib 是 Python 标准库中的一个模块,它实现了 IMAP4 (Internet Message Access Protocol v4) 客户端,IMAP 是一种用于从邮件服务器获取邮件的协议,与只能下载邮件的 POP3 协议不同,IMAP 允许你操作服务器上的邮件,

- 在服务器上创建、删除、重命名邮箱(如 "INBOX", "Sent")。
- 搜索邮件(按发件人、主题、日期等)。
- 标记邮件状态(已读、未读、已加星标等)。
- 获取邮件的头部、正文或附件,而无需下载整个邮件。
这份教程将带你一步步掌握 imaplib 的使用。
目录
- 准备工作
- 获取 IMAP 服务器信息
- 启用“应用专用密码”(重要!)
- 基础连接与认证
- 建立安全连接 (IMAP4_SSL)
- 登录账户
- 优雅地登出
- 探索邮箱
- 获取邮箱列表
- 选择一个邮箱
- 获取邮箱状态信息
- 搜索邮件
- 使用
SEARCH命令 - 常用搜索条件详解
- 使用
- 获取邮件内容
- 使用
FETCH命令 - 解析邮件结构
- 实战:获取邮件主题和正文
- 使用
- 处理邮件状态
- 标记邮件为已读/未读
- 删除邮件
- 完整示例:下载所有未读邮件
- 最佳实践与注意事项
- 使用
with语句管理连接 - 错误处理
- 性能考虑
- 使用
准备工作
在使用 imaplib 之前,你需要准备两样东西。
a. 获取 IMAP 服务器信息
你需要知道邮件服务商提供的 IMAP 服务器地址和端口,常见服务商的信息如下:
| 邮件服务商 | IMAP 服务器地址 | 端口 | 加密方式 |
|---|---|---|---|
| Gmail | imap.gmail.com |
993 |
SSL |
| Outlook / Hotmail | outlook.office365.com |
993 |
SSL |
| QQ Mail | imap.qq.com |
993 |
SSL |
| 163 Mail | imap.163.com |
993 |
SSL |
| Apple iCloud | imap.mail.me.com |
993 |
SSL |
b. 启用“应用专用密码”(重要!)
出于安全考虑,许多现代邮件服务商(如 Gmail)不再允许第三方应用直接使用你的主账户密码登录,你需要为你的 Python 脚本生成一个“应用专用密码”(App Password)。

以 Gmail 为例:
- 确保你的两步验证(2-Step Verification)已经开启。
- 访问你的 Google 账户:https://myaccount.google.com/
- 转到 安全性。
- 在“登录 Google”部分,点击 应用密码。
- 在“选择应用”下拉菜单中,选择“其他(自定义名称)”,然后为你的脚本命名("Python Mail Fetcher")。
- 点击 生成。
- 系统会显示一个 16 位密码。请立即复制并保存,因为你只会看到这一次,这个密码就是你的 Python 脚本要使用的密码。
基础连接与认证
这是所有操作的第一步。
import imaplib
import email
# --- 配置 ---
# 使用你自己的邮箱信息
EMAIL = "your_email@gmail.com"
PASSWORD = "your_16_digit_app_password" # 从上面步骤获取的专用密码
IMAP_SERVER = "imap.gmail.com"
IMAP_PORT = 993
try:
# 1. 建立安全连接
# imaplib.IMAP4_SSL 会自动处理 SSL/TLS 加密
print(f"正在连接到 {IMAP_SERVER}...")
mail = imaplib.IMAP4_SSL(IMAP_SERVER, IMAP_PORT)
# 2. 登录
print("正在登录...")
mail.login(EMAIL, PASSWORD)
print("登录成功!")
# ... 在这里执行其他操作 ...
# 3. 登出
print("正在登出...")
mail.logout()
print("已安全登出。")
except imaplib.IMAP4.error as e:
print(f"IMAP 错误: {e}")
except Exception as e:
print(f"发生错误: {e}")
代码解释:
imaplib.IMAP4_SSL(server, port): 创建一个使用 SSL 加密的 IMAP4 连接对象。mail.login(user, password): 使用邮箱和密码进行身份验证。mail.logout(): 关闭连接并登出。
探索邮箱
连接成功后,我们需要知道服务器上有哪些邮箱。

# ... (接上面的连接代码) ...
# 1. 获取邮箱列表
# list() 返回一个元组 (status, [list_of_mailboxes])
# '""' 表示在根目录下查找所有邮箱
status, mailboxes_data = mail.list('""')
print("\n可用的邮箱:")
if status == 'OK':
# mailboxes_data 是一个字节串列表,需要解码
for mailbox in mailboxes_data:
print(mailbox.decode('utf-8'))
# 2. 选择一个邮箱 (例如收件箱)
# 'INBOX' 是标准的收件箱名称
print("\n正在选择收件箱...")
status, messages_data = mail.select('INBOX')
if status == 'OK':
print(f"成功选择收件箱,邮件总数: {messages_data[0].decode('utf-8')}")
else:
print("选择收件箱失败。")
# ... (后续操作) ...
代码解释:
mail.list(directory): 获取指定目录下的邮箱列表。 通常表示获取所有邮箱。mail.select(mailbox): 选择一个邮箱进行操作,返回值中的第二个元素是当前邮箱中的邮件总数(以字节串形式返回)。
搜索邮件
搜索是 IMAP 的核心功能之一。SEARCH 命令非常强大。
# ... (接上面的选择收件箱代码) ...
# 1. 搜索所有邮件
# 'ALL' 搜索所有邮件
status, search_data = mail.search(None, 'ALL')
if status == 'OK':
# search_data[0] 是一个包含所有邮件 ID 的字节串,用空格分隔
email_ids = search_data[0].split()
print(f"\n找到所有邮件数量: {len(email_ids)}")
# 2. 搜索未读邮件
# 'UNSEEN' 搜索未读邮件
status, search_data = mail.search(None, 'UNSEEN')
if status == 'OK':
unread_ids = search_data[0].split()
print(f"找到未读邮件数量: {len(unread_ids)}")
# 3. 搜索来自特定发件人的邮件
# 'FROM "sender@example.com"'
status, search_data = mail.search(None, 'FROM "google@gmail.com"')
if status == 'OK':
from_google_ids = search_data[0].split()
print(f"来自 google@gmail.com 的邮件数量: {len(from_google_ids)}")
# 4. 搜索包含特定主题的邮件
# 'SUBJECT "Your Subject"'
status, search_data = mail.search(None, 'SUBJECT "重要通知"')
if status == 'OK':
subject_ids = search_data[0].split()
print(f"主题包含 '重要通知' 的邮件数量: {len(subject_ids)}")
# 5. 组合搜索条件 (AND)
# 使用多个参数,IMAP 会将它们视为 AND 关系
status, search_data = mail.search(None, 'UNSEEN', 'FROM "boss@company.com"')
if status == 'OK':
unread_from_boss_ids = search_data[0].split()
print(f"来自老板的未读邮件数量: {len(unread_from_boss_ids)}")
常用搜索条件:
| 条件 | 描述 | 示例 |
|---|---|---|
ALL |
所有邮件 | mail.search(None, 'ALL') |
ANSWERED |
已回复的邮件 | mail.search(None, 'ANSWERED') |
DELETED |
已标记删除的邮件 | mail.search(None, 'DELETED') |
DRAFT |
草稿箱中的邮件 | mail.search(None, 'DRAFT') |
NEW |
新邮件 | mail.search(None, 'NEW') |
OLD |
旧邮件 | mail.search(None, 'OLD') |
RECENT |
最近接收的邮件 | mail.search(None, 'RECENT') |
SEEN |
已读邮件 | mail.search(None, 'SEEN') |
UNSEEN |
未读邮件 | mail.search(None, 'UNSEEN') |
FROM "string" |
发件人包含 "string" | mail.search(None, 'FROM "john"') |
SUBJECT "string" |
主题包含 "string" | mail.search(None, 'SUBJECT "invoice"') |
BODY "string" |
正文包含 "string" | mail.search(None, 'BODY "hello"') |
BEFORE "date" |
在指定日期之前 | mail.search(None, 'BEFORE "1-Jan-2025"') |
SINCE "date" |
在指定日期之后 | mail.search(None, 'SINCE "1-Jan-2025"') |
UID "unique_id" |
通过唯一的邮件 ID 搜索 | mail.search(None, 'UID 12345') |
获取邮件内容
找到邮件 ID 后,我们可以使用 FETCH 命令来获取邮件内容。
# 假设我们已经有一些邮件 ID
# email_ids = mail.search(None, 'ALL')[0].split()
# 获取一封邮件的内容,例如最新的一封
if email_ids:
latest_email_id = email_ids[-1]
print(f"\n正在获取 ID 为 {latest_email_id} 的邮件...")
# FETCH 命令格式: mail.fetch(email_id, data_part)
# 'RFC822' 表示获取完整的邮件内容
status, email_data = mail.fetch(latest_email_id, '(RFC822)')
if status == 'OK':
# email_data 是一个列表,其中包含一个元组 (b'1 (RFC822 ...)', b'邮件原始内容...')
raw_email = email_data[0][1]
# 使用 email 标准库解析原始邮件
msg = email.message_from_bytes(raw_email)
# --- 解析邮件的各个部分 ---
print("\n--- 邮件信息 ---")
print(f"发件人: {msg['from']}")
print(f"收件人: {msg['to']}")
print(f"主题: {msg['subject']}")
print(f"日期: {msg['date']}")
# 处理邮件正文
print("\n--- 邮件正文 ---")
if msg.is_multipart():
# 如果邮件是多部分的(包含附件或文本/HTML)
for part in msg.walk():
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
# 跳过附件
if "attachment" not in content_disposition:
if content_type == "text/plain":
body = part.get_payload(decode=True).decode('utf-8', errors='ignore')
print("纯文本正文:")
print(body)
elif content_type == "text/html":
html_body = part.get_payload(decode=True).decode('utf-8', errors='ignore')
# 如果你想显示 HTML,可以使用一个库如 beautifulsoup
# print("HTML 正文:")
# print(html_body)
else:
# 如果邮件是单部分的(纯文本)
body = msg.get_payload(decode=True).decode('utf-8', errors='ignore')
print("纯文本正文:")
print(body)
代码解释:
mail.fetch(email_id, '(RFC822)'): 获取指定 ID 邮件的完整原始内容。email.message_from_bytes(raw_email): Python 的email标准库可以将原始字节流解析成一个易于操作的对象。msg.is_multipart(): 判断邮件是否包含多个部分(既有文本正文,又有 HTML 正文,还有附件)。msg.walk(): 遍历邮件的所有部分。part.get_content_type(): 获取该部分的类型(如text/plain,text/html,application/pdf)。part.get_payload(decode=True): 获取该部分的解码后的载荷(即实际内容)。
处理邮件状态
你可以通过 STORE 命令来修改邮件的状态。
# ... (假设我们有 email_ids) ...
if email_ids:
# 标记一封邮件为已读
email_id_to_mark = email_ids[0]
print(f"\n将邮件 {email_id_to_mark} 标记为已读...")
# '+FLAGS' 表示添加标志, '\\Seen' 是已读的标志
mail.store(email_id_to_mark, '+FLAGS', '\\Seen')
print("标记成功。")
# 标记一封邮件为未读
email_id_to_unmark = email_ids[1]
print(f"\n将邮件 {email_id_to_unmark} 标记为未读...")
# '-FLAGS' 表示移除标志
mail.store(email_id_to_unmark, '-FLAGS', '\\Seen')
print("标记成功。")
# 删除一封邮件
email_id_to_delete = email_ids[2]
print(f"\n将邮件 {email_id_to_delete} 标记为删除...")
# '\\Deleted' 是删除的标志
mail.store(email_id_to_delete, '+FLAGS', '\\Deleted')
print("邮件已标记为删除。")
# 执行删除操作
# expunge 会永久删除所有标记为 '\\Deleted' 的邮件
print("正在永久删除邮件...")
mail.expunge()
print("删除完成。")
代码解释:
mail.store(email_id, command, flags): 修改邮件的标志。command:+FLAGS(添加),-FLAGS(移除),FLAGS(替换)。flags: 标志名称,如\\Seen(已读),\\Deleted(删除),\\Flagged(加星标)。
mail.expunge(): 永久删除所有被标记为\\Deleted的邮件,如果你只是想从当前视图中移除,而不真正删除,可以select一个邮箱时使用readonly选项。
完整示例:下载所有未读邮件
这是一个综合了以上所有知识的实用脚本。
import imaplib
import email
from email.header import decode_header
import os
def decode_str(s):
"""解码邮件主题或发件人"""
value, charset = decode_header(s)[0]
if isinstance(value, bytes):
try:
return value.decode(charset or 'utf-8')
except (UnicodeDecodeError, LookupError):
return value.decode('gb18030', errors='replace')
return value
def get_email_body(msg):
"""获取邮件正文"""
if msg.is_multipart():
for part in msg.walk():
content_type = part.get_content_type()
if content_type == "text/plain":
try:
return part.get_payload(decode=True).decode('utf-8')
except:
return part.get_payload(decode=True).decode('gb18030', errors='replace')
else:
try:
return msg.get_payload(decode=True).decode('utf-8')
except:
return msg.get_payload(decode=True).decode('gb18030', errors='replace')
return ""
def fetch_unread_emails():
# --- 配置 ---
EMAIL = "your_email@gmail.com"
PASSWORD = "your_16_digit_app_password"
IMAP_SERVER = "imap.gmail.com"
IMAP_PORT = 993
try:
# 连接并登录
mail = imaplib.IMAP4_SSL(IMAP_SERVER, IMAP_PORT)
mail.login(EMAIL, PASSWORD)
print("登录成功!")
# 选择收件箱
mail.select('INBOX')
# 搜索未读邮件
status, email_ids_data = mail.search(None, 'UNSEEN')
if status != 'OK':
print("搜索未读邮件失败。")
return
email_ids = email_ids_data[0].split()
print(f"找到 {len(email_ids)} 封未读邮件。")
# 创建一个目录来保存邮件
save_dir = "downloaded_emails"
if not os.path.exists(save_dir):
os.makedirs(save_dir)
for email_id in email_ids:
print(f"\n正在处理邮件 ID: {email_id}...")
# 获取邮件
status, email_data = mail.fetch(email_id, '(RFC822)')
if status != 'OK':
print(f"获取邮件 {email_id} 失败。")
continue
raw_email = email_data[0][1]
msg = email.message_from_bytes(raw_email)
# 解析邮件
subject = decode_str(msg['subject'])
from_ = decode_str(msg['from'])
body = get_email_body(msg)
# 保存邮件内容到文件
filename = os.path.join(save_dir, f"{subject.replace('/', '_')}.txt")
with open(filename, 'w', encoding='utf-8') as f:
f.write(f"Subject: {subject}\n")
f.write(f"From: {from_}\n")
f.write(f"Date: {msg['date']}\n")
f.write("-" * 20 + "\n")
f.write(body)
print(f"邮件 '{subject}' 已保存到 {filename}")
# 标记为已读
mail.store(email_id, '+FLAGS', '\\Seen')
print("已将该邮件标记为已读。")
# 登出
mail.logout()
print("\n所有操作完成,已登出。")
except Exception as e:
print(f"发生错误: {e}")
if __name__ == "__main__":
fetch_unread_emails()
最佳实践与注意事项
a. 使用 with 语句管理连接
imaplib 对象没有实现上下文管理器协议(__enter__ 和 __exit__),所以不能直接使用 with,但你可以自己封装一个。
class ImapClient:
def __init__(self, server, port, user, password):
self.server = server
self.port = port
self.user = user
self.password = password
self.mail = None
def __enter__(self):
self.mail = imaplib.IMAP4_SSL(self.server, self.port)
self.mail.login(self.user, self.password)
return self.mail
def __exit__(self, exc_type, exc_val, exc_tb):
if self.mail:
self.mail.logout()
# 使用方式
with ImapClient("imap.gmail.com", 993, "user", "pass") as mail:
mail.select('INBOX')
# ... 在这里执行操作 ...
# 离开 with 块后,会自动调用 logout()
b. 错误处理
网络问题、认证失败、服务器错误等都可能发生,始终使用 try...except 块来捕获 imaplib.IMAP4.error 和其他可能的异常,使你的脚本更健壮。
c. 性能考虑
- 避免一次性获取大量邮件:如果你有上万封邮件,
mail.search(None, 'ALL')会返回一个巨大的列表,对于分页显示或处理,最好结合SEARCH和FETCH的范围查询(SEARCH UID 1:100)。 - 只获取你需要的数据:使用
FETCH时,不要总是使用(RFC822),如果你只需要主题,可以使用(BODY[HEADER.FIELDS (SUBJECT)])来获取更少的数据,从而提高速度并节省带宽。
希望这份详细的教程能帮助你掌握 Python 的 imaplib!
