杰瑞科技汇

python imaplib 教程

Python imaplib 教程:使用 IMAP 协议管理邮件

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

python imaplib 教程-图1
(图片来源网络,侵删)
  • 在服务器上创建、删除、重命名邮箱(如 "INBOX", "Sent")。
  • 搜索邮件(按发件人、主题、日期等)。
  • 标记邮件状态(已读、未读、已加星标等)。
  • 获取邮件的头部、正文或附件,而无需下载整个邮件。

这份教程将带你一步步掌握 imaplib 的使用。


目录

  1. 准备工作
    • 获取 IMAP 服务器信息
    • 启用“应用专用密码”(重要!)
  2. 基础连接与认证
    • 建立安全连接 (IMAP4_SSL)
    • 登录账户
    • 优雅地登出
  3. 探索邮箱
    • 获取邮箱列表
    • 选择一个邮箱
    • 获取邮箱状态信息
  4. 搜索邮件
    • 使用 SEARCH 命令
    • 常用搜索条件详解
  5. 获取邮件内容
    • 使用 FETCH 命令
    • 解析邮件结构
    • 实战:获取邮件主题和正文
  6. 处理邮件状态
    • 标记邮件为已读/未读
    • 删除邮件
  7. 完整示例:下载所有未读邮件
  8. 最佳实践与注意事项
    • 使用 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)。

python imaplib 教程-图2
(图片来源网络,侵删)

以 Gmail 为例:

  1. 确保你的两步验证(2-Step Verification)已经开启。
  2. 访问你的 Google 账户:https://myaccount.google.com/
  3. 转到 安全性
  4. 在“登录 Google”部分,点击 应用密码
  5. 在“选择应用”下拉菜单中,选择“其他(自定义名称)”,然后为你的脚本命名("Python Mail Fetcher")。
  6. 点击 生成
  7. 系统会显示一个 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(): 关闭连接并登出。

探索邮箱

连接成功后,我们需要知道服务器上有哪些邮箱。

python imaplib 教程-图3
(图片来源网络,侵删)
# ... (接上面的连接代码) ...
# 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') 会返回一个巨大的列表,对于分页显示或处理,最好结合 SEARCHFETCH 的范围查询(SEARCH UID 1:100)。
  • 只获取你需要的数据:使用 FETCH 时,不要总是使用 (RFC822),如果你只需要主题,可以使用 (BODY[HEADER.FIELDS (SUBJECT)]) 来获取更少的数据,从而提高速度并节省带宽。

希望这份详细的教程能帮助你掌握 Python 的 imaplib

分享:
扫描分享到社交APP
上一篇
下一篇