perf: IMAP 两步策略 — 先 INTERNALDATE 过滤再下载 RFC822

避免对游标之前的邮件下载完整正文,大幅减少 QQ 等大收件箱的获取时间
第一步:批量 FETCH INTERNALDATE 快速过滤
第二步:仅对候选邮件下载 RFC822 解析正文

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
胡绍康andCopilot committed 2026-03-05 19:18:06 +08:00
1 parent 9c838da1f3
commit 5ade471cef
1 file changed
+42 -2
+42 -2
View File
@@ -101,7 +101,10 @@ def _send_telegram_message(bot_token: str, chat_id: str, text: str) -> bool:
def _fetch_new_emails_imap(account: dict, since: str) -> List[dict]: def _fetch_new_emails_imap(account: dict, since: str) -> List[dict]:
"""通过 IMAP 获取 received_at > since 的邮件,最多返回 50 封。""" """通过 IMAP 获取 received_at > since 的邮件,最多返回 50 封。
两步策略:先用 INTERNALDATE 快速过滤,再对命中的邮件下载正文。
"""
import email as email_lib import email as email_lib
import email.header import email.header
import imaplib import imaplib
@@ -128,7 +131,44 @@ def _fetch_new_emails_imap(account: dict, since: str) -> List[dict]:
_, data = conn.search(None, f'(SINCE "{since_date_str}")') _, data = conn.search(None, f'(SINCE "{since_date_str}")')
msg_ids = data[0].split() if data[0] else [] msg_ids = data[0].split() if data[0] else []
for mid in msg_ids[-MAX_EMAILS_PER_FETCH:]: if not msg_ids:
return results
# 第一步:批量获取 INTERNALDATE 快速过滤
candidate_ids = msg_ids[-MAX_EMAILS_PER_FETCH:]
id_range = b",".join(candidate_ids)
_, date_data = conn.fetch(id_range, "(INTERNALDATE)")
import re
date_pattern = re.compile(rb'INTERNALDATE "([^"]+)"')
new_msg_ids = []
for item in date_data:
if not isinstance(item, tuple):
continue
header_line = item[0] if isinstance(item[0], bytes) else b""
mid_match = re.match(rb"(\d+)", header_line)
date_match = date_pattern.search(header_line)
if not mid_match or not date_match:
continue
mid = mid_match.group(1)
idate_str = date_match.group(1).decode("ascii", errors="replace")
try:
from imaplib import Internaldate2tuple
import calendar
tt = Internaldate2tuple(b'"' + date_match.group(1) + b'"')
if tt:
ts = calendar.timegm(tt)
idate_iso = dt.utcfromtimestamp(ts).strftime("%Y-%m-%dT%H:%M:%S")
if idate_iso <= since:
continue
except Exception:
pass # 解析失败时保留,由后续 RFC822 过滤
new_msg_ids.append(mid)
# 第二步:仅对候选邮件下载 RFC822
for mid in new_msg_ids[-MAX_EMAILS_PER_FETCH:]:
try: try:
_, msg_data = conn.fetch(mid, "(RFC822)") _, msg_data = conn.fetch(mid, "(RFC822)")
raw = msg_data[0][1] raw = msg_data[0][1]