Python 用 IMAP 读取邮箱验证码:和租用邮箱 API 怎么选

用 Python 读取邮件验证码,如果读的是你自己的邮箱,标准库 imaplib 最直接:用授权码或应用专用密码登录 IMAP,按发件人和时间找到验证邮件,解析正文,再用正则取出验证码。如果每次都需要一个新的真实地址,又不想维护邮箱和授权码,就改用租用邮箱的 API。

下面的代码只用于你自己有权访问的邮箱,例如团队的测试邮箱。先讲登录凭据,再给 IMAP 示例和 Java 写法,最后用一张表对比两种做法。

准备:IMAP 不能再用登录密码

很多旧教程直接用邮箱密码登录 IMAP,现在大多行不通了:

无论哪种,凭据都放在环境变量或密钥管理里,不要写进代码。

imaplib 示例:Python 读取邮箱邮件并找出验证码

import email, imaplib, os, re, time
from email.header import decode_header, make_header
from email.utils import parsedate_to_datetime

HOST = os.environ.get("IMAP_HOST", "imap.gmail.com")
USER = os.environ["IMAP_USER"]
SECRET = os.environ["IMAP_SECRET"]  # 授权码或应用专用密码,不是登录密码

MONTHS = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split()
NEAR_WORD = re.compile(r"(?:code|验证码|verification)\D{0,40}?(\d{4,8})(?!\d)", re.I)
SIX_DIGITS = re.compile(r"(?<!\d)\d{6}(?!\d)")

def body_text(msg):
    """优先取 text/plain;只有 HTML 时去掉标签。"""
    html = ""
    for part in msg.walk():
        if part.get_content_maintype() == "multipart" or part.get_filename():
            continue
        raw = part.get_payload(decode=True) or b""
        text = raw.decode(part.get_content_charset() or "utf-8", errors="replace")
        if part.get_content_type() == "text/plain":
            return text
        if part.get_content_type() == "text/html":
            html = text
    return re.sub(r"<[^>]+>", " ", html)

def find_code(sender, since_ts, timeout=180, interval=5):
    t = time.gmtime(since_ts)
    since = f"{t.tm_mday:02d}-{MONTHS[t.tm_mon - 1]}-{t.tm_year}"  # IMAP 日期格式
    end = time.time() + timeout
    while time.time() < end:
        with imaplib.IMAP4_SSL(HOST, 993) as box:
            box.login(USER, SECRET)
            box.select("INBOX", readonly=True)  # 只读,不改变已读状态
            _, data = box.search(None, "FROM", sender, "SINCE", since)
            for num in reversed(data[0].split()):  # 从最新的开始
                _, parts = box.fetch(num, "(RFC822)")
                msg = email.message_from_bytes(parts[0][1])
                if msg["Date"] and parsedate_to_datetime(msg["Date"]).timestamp() < since_ts - 60:
                    continue  # 触发发送之前的旧邮件
                subject = str(make_header(decode_header(msg["Subject"] or "")))
                text = subject + "\n" + body_text(msg)
                m = NEAR_WORD.search(text) or SIX_DIGITS.search(text)
                if m:
                    return m.group(m.lastindex or 0)
        time.sleep(interval)
    return None

# 用法:先记下时间,再触发网站发送验证码
started = time.time()
# ……在这里提交注册表单……
print(find_code("[email protected]", started))

几个细节,老教程里常常没有:

Gmail 还支持在 IMAP 里直接使用网页版的搜索语法(X-GM-RAW),条件写起来更灵活。例如把上面的搜索换成:

_, data = box.search(None, "X-GM-RAW", '"from:[email protected] newer_than:1d"')

newer_than 只接受天、月、年(d、m、y),所以仍然要靠 Date 再筛一次。

Java:javax.mail 读取邮件

Java 的思路完全一样,用 javax.mail(新版本叫 Jakarta Mail,包名改成了 jakarta.mail):

Session session = Session.getInstance(new Properties());
Store store = session.getStore("imaps");
store.connect("imap.gmail.com", user, appPassword); // 授权码或应用专用密码
Folder inbox = store.getFolder("INBOX");
inbox.open(Folder.READ_ONLY);
Message[] found = inbox.search(new FromStringTerm("[email protected]"));
// 从 found 的最后一封往前看:比较 getSentDate(),取正文,再用正则找验证码

IMAP 和租用邮箱 API:怎么选

自己的邮箱 + IMAP 租用邮箱 API
要准备什么 开通 IMAP,管理授权码、应用专用密码或 OAuth 一个 API 密钥
地址 一个或少数几个固定地址 每个订单一个新的真实 @gmail.com 或 @icloud.com 地址
并行测试 多个用例共用一个收件箱,要靠发件人和时间区分 每个用例各自一个订单,互不干扰
解析邮件 自己处理 MIME、编码和正则 系统已把验证码或链接提取到 otp[].code;提取不出时给出邮件原文
收不到时 只能自己排查 到 codeDeadlineAt 没有验证码,订单自动取消并全额退款
成本 不花钱,花维护时间 按次付费
适合 读你自己的邮件,少量、串行的测试 每次都要新地址、并行测试、注册流程的冒烟测试

如果你只是想读自己邮箱里的某封邮件,IMAP 就够了,不需要任何第三方服务。如果瓶颈在“地址”上,比如每次注册都提示邮箱已被使用,或者多个用例的邮件混在一起,再考虑租用邮箱。

同一个任务,换成租用邮箱 API

下面是和上面 IMAP 示例做同一件事的写法:租一个地址,把它填进注册表单,然后轮询验证码。测试你自己的网站时,服务代码用 ot,也就是任意服务;列表里有的网站用它自己的代码。

import time, uuid, requests

HOST = "otpgmail.net"
H = {"Authorization": "Bearer og_YOUR_API_KEY"}

order = requests.post(f"https://{HOST}/v1/orders", json={"service": "ot"},
                      headers={**H, "Idempotency-Key": str(uuid.uuid4())}, timeout=15).json()["data"][0]
# 把 order["email"] 填进注册表单,然后每 4 秒查一次
while True:
    o = requests.get(f"https://{HOST}/v1/orders/{order['orderId']}", headers=H, timeout=15).json()["data"]
    if o["otp"] or o["status"] == "cancelled":
        break
    time.sleep(4)
print(o["otp"][-1]["code"] if o["otp"] else "没有收到,订单已自动取消并退款")

这段为了简短省略了错误处理;错误码怎么处理、怎么提前取消,见用 Python 接收邮箱验证码。最长 25 分钟没有验证码,订单自动取消,费用全额退回余额,规则见退款与质保政策。放进 Playwright、Selenium 或 Cypress 的写法,见自动化测试怎么处理邮箱验证码。

请只读取你有权访问的邮箱,只在你自己的项目或目标平台条款允许的范围内使用。使用前请确认符合该平台的服务条款和你所在地的法律法规;OTPGmail 只提供接收验证邮件的邮箱,不提供绕过平台或地区限制的方法。

常见问题

Python 读取 QQ 邮箱要注意什么?

先在 QQ 邮箱网页版的设置里开启 IMAP 服务,拿到授权码,程序里用授权码登录;中文标题要先用 decode_header 解码再匹配。服务器地址和端口以 QQ 邮箱帮助页为准。

Gmail 用账号密码登录 IMAP 报错怎么办?

谷歌已经不支持只用用户名和密码登录的第三方应用。改用 OAuth 2.0,或者开启两步验证后创建应用专用密码,把它当作密码使用。

为什么脚本搜不到刚收到的验证邮件?

常见原因有三个:邮件进了垃圾箱,而脚本只搜 INBOX;SINCE 的日期按服务器时区计算,跨零点时差了一天;邮件还没到,轮询的总时长太短。

租用邮箱的 API 能读我自己的邮箱吗?

不能。租用邮箱的 API 只返回租到的那个地址收到的验证码;读你自己的邮箱,请用 IMAP。

相关教程

现在就获取你的验证码

注册账户不收费。分配到邮箱才扣费;等待时间内(最长 25 分钟)没有收到验证码,自动 100% 退款。

找不到你需要的服务?在租用页面点添加服务,如果我们的基础设施支持,就会把它加上。

需要可以登录的 Gmail 账号,而不只是验证码?请看购买短效 Gmail 账号 · 退款与质保