14.5. imaplib — IMAP4 客户端类库

未匹配的标注

目的:用于IMAP4通信的客户端库。

imaplib实现用于与Internet消息访问协议( IMAP )版本 4 服务器进行通信的客户端。 IMAP 协议定义了一组发送到服务器的命令以及将响应传递回客户端的命令。大多数命令可用作用于与服务器通信的IMAP4对象的方法。

这些示例讨论了 IMAP 协议的一部分,但绝不是完整的。有关完整的详细信息,请参阅 RFC 3501。

变化

存在三种用于使用各种机制与服务器进行通信的客户端类。第一个IMAP4使用明文套接字; IMAP4_SSL使用SSL套接字上的加密通信;和IMAP4_stream使用外部命令的标准输入和标准输出。此处所有示例将使用IMAP4_SSL,但其他类的 API 相似。

连接到服务器

与IMAP服务器建立连接有两个步骤。首先,设置套接字连接本身。其次,使用服务器上的帐户以用户身份进行身份验证。以下示例代码将从配置文件中读取服务器和用户信息。

imaplib_connect.py

import imaplib
import configparser
import os

def open_connection(verbose=False):
    # Read the config file
    config = configparser.ConfigParser()
    config.read([os.path.expanduser('~/.pymotw')])

    # Connect to the server
    hostname = config.get('server', 'hostname')
    if verbose:
        print('Connecting to', hostname)
    connection = imaplib.IMAP4_SSL(hostname)

    # Login to our account
    username = config.get('account', 'username')
    password = config.get('account', 'password')
    if verbose:
        print('Logging in as', username)
    connection.login(username, password)
    return connection

if __name__ == '__main__':
    with open_connection(verbose=True) as c:
        print(c)

运行时,open_connection()从用户主目录中的文件中读取配置信息,然后打开IMAP4_SSL连接并进行身份验证。

$ python3 imaplib_connect.py

Connecting to pymotw.hellfly.net
Logging in as example
<imaplib.IMAP4_SSL object at 0x10421e320>

本节中的其他示例重用了此模块,以避免重复代码。

验证失败

如果建立连接但身份验证失败,则会引发异常。

imaplib_connect_fail.py

import imaplib
import configparser
import os

# Read the config file
config = configparser.ConfigParser()
config.read([os.path.expanduser('~/.pymotw')])

# Connect to the server
hostname = config.get('server', 'hostname')
print('Connecting to', hostname)
connection = imaplib.IMAP4_SSL(hostname)

# Login to our account
username = config.get('account', 'username')
password = 'this_is_the_wrong_password'
print('Logging in as', username)
try:
    connection.login(username, password)
except Exception as err:
    print('ERROR:', err)

本示例故意使用错误的密码来触发异常。

$ python3 imaplib_connect_fail.py

Connecting to pymotw.hellfly.net
Logging in as example
ERROR: b'[AUTHENTICATIONFAILED] Authentication failed.'

示例配置

该示例帐户在层次结构中具有多个邮箱:

  • INBOX 收件箱
  • Deleted Messages 删除的邮件
  • Archive 存档
  • Example 例子
    • 2016

INBOX文件夹中有一则未读消息,而Example / 2016中有一则未读消息。

列出邮箱

要检索帐户可用的邮箱,请使用list()方法。

imaplib_list.py

import imaplib
from pprint import pprint
from imaplib_connect import open_connection

with open_connection() as c:
    typ, data = c.list()
    print('Response code:', typ)
    print('Response:')
    pprint(data)

返回值是一个tuple,包含响应代码和服务器返回的数据。除非出现错误,否则响应代码为OKlist()的数据是一个字符串序列,包含每个邮箱的 标志,层次结构分隔符邮箱名称*。

$ python3 imaplib_list.py

Response code: OK
Response:
[b'(.HasChildren) "." Example',
 b'(.HasNoChildren) "." Example.2016',
 b'(.HasNoChildren) "." Archive',
 b'(.HasNoChildren) "." "Deleted Messages"',
 b'(.HasNoChildren) "." INBOX']

每个响应字符串可以使用re 或[csv (请参阅 IMAP 本节末尾参考中的备份脚本,以使用csv的示例为例).

imaplib_list_parse.py

import imaplib
import re

from imaplib_connect import open_connection

list_response_pattern = re.compile(
    r'.(?P<flags>.*?). "(?P<delimiter>.*)" (?P<name>.*)'
)

def parse_list_response(line):
    match = list_response_pattern.match(line.decode('utf-8'))
    flags, delimiter, mailbox_name = match.groups()
    mailbox_name = mailbox_name.strip('"')
    return (flags, delimiter, mailbox_name)

with open_connection() as c:
    typ, data = c.list()
print('Response code:', typ)

for line in data:
    print('Server response:', line)
    flags, delimiter, mailbox_name = parse_list_response(line)
    print('Parsed response:', (flags, delimiter, mailbox_name))

如果服务器名称中包含空格,则服务器会引用该邮箱名称,但是稍后,需要删除那些引号,以便在其他回拨到服务器的调用中使用该邮箱名称。

$ python3 imaplib_list_parse.py

Response code: OK
Server response: b'(.HasChildren) "." Example'
Parsed response: ('.HasChildren', '.', 'Example')
Server response: b'(.HasNoChildren) "." Example.2016'
Parsed response: ('.HasNoChildren', '.', 'Example.2016')
Server response: b'(.HasNoChildren) "." Archive'
Parsed response: ('.HasNoChildren', '.', 'Archive')
Server response: b'(.HasNoChildren) "." "Deleted Messages"'
Parsed response: ('.HasNoChildren', '.', 'Deleted Messages')
Server response: b'(.HasNoChildren) "." INBOX'
Parsed response: ('.HasNoChildren', '.', 'INBOX')

list()采用参数来指定部分层次结构中的邮箱。例如,要列出Example的子文件夹,请传递“ Example”作为目录参数。

imaplib_list_subfolders.py

import imaplib

from imaplib_connect import open_connection

with open_connection() as c:
    typ, data = c.list(directory='Example')

print('Response code:', typ)

for line in data:
    print('Server response:', line)

返回父级和子文件夹。

$ python3 imaplib_list_subfolders.py

Response code: OK
Server response: b'(.HasChildren) "." Example'
Server response: b'(.HasNoChildren) "." Example.2016'

或者,要列出与模式匹配的文件夹,请传递pattern参数。

imaplib_list_pattern.py

import imaplib

from imaplib_connect import open_connection

with open_connection() as c:
    typ, data = c.list(pattern='*Example*')

print('Response code:', typ)

for line in data:
    print('Server response:', line)

在这种情况下,响应中同时包含ExampleExample.2016

$ python3 imaplib_list_pattern.py

Response code: OK
Server response: b'(.HasChildren) "." Example'
Server response: b'(.HasNoChildren) "." Example.2016'

邮箱状态

使用status()询问有关内容的汇总信息。下表列出了标准定义的状态条件。

IMAP 4邮箱状态条件

条件 含义
MESSAGES 邮箱中的邮件数。
RECENT 设置了.ecent标志的消息数。
UIDNEXT 邮箱的下一个唯一标识符值。
UIDVALIDITY 邮箱的唯一标识符有效性值。
UNSEEN 没有设置.een标志的消息数。

状态条件的格式必须为括号内用空格分隔的字符串,这是IMAP4规范中“列表”的编码。邮箱名称被包裹在中,以防任何名称中包含空格或其他可能引发解析器的字符。

imaplib_status.py

import imaplib
import re

from imaplib_connect import open_connection
from imaplib_list_parse import parse_list_response

with open_connection() as c:
    typ, data = c.list()
    for line in data:
        flags, delimiter, mailbox = parse_list_response(line)
        print('Mailbox:', mailbox)
        status = c.status(
            '"{}"'.format(mailbox),
            '(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)',
        )
        print(status)

返回值是通常的 tuple,其中包含响应代码和来自服务器的信息列表。在这种情况下,列表包含单个字符串,该字符串的格式为用引号引起来的邮箱名称,然后是括号中的状态条件和值。

$ python3 imaplib_status.py

Response code: OK
Server response: b'(.HasChildren) "." Example'
Parsed response: ('.HasChildren', '.', 'Example')
Server response: b'(.HasNoChildren) "." Example.2016'
Parsed response: ('.HasNoChildren', '.', 'Example.2016')
Server response: b'(.HasNoChildren) "." Archive'
Parsed response: ('.HasNoChildren', '.', 'Archive')
Server response: b'(.HasNoChildren) "." "Deleted Messages"'
Parsed response: ('.HasNoChildren', '.', 'Deleted Messages')
Server response: b'(.HasNoChildren) "." INBOX'
Parsed response: ('.HasNoChildren', '.', 'INBOX')
Mailbox: Example
('OK', [b'Example (MESSAGES 0 RECENT 0 UIDNEXT 2 UIDVALIDITY 145
7297771 UNSEEN 0)'])
Mailbox: Example.2016
('OK', [b'Example.2016 (MESSAGES 1 RECENT 0 UIDNEXT 3 UIDVALIDIT
Y 1457297772 UNSEEN 0)'])
Mailbox: Archive
('OK', [b'Archive (MESSAGES 0 RECENT 0 UIDNEXT 1 UIDVALIDITY 145
7297770 UNSEEN 0)'])
Mailbox: Deleted Messages
('OK', [b'"Deleted Messages" (MESSAGES 3 RECENT 0 UIDNEXT 4 UIDV
ALIDITY 1457297773 UNSEEN 0)'])
Mailbox: INBOX
('OK', [b'INBOX (MESSAGES 2 RECENT 0 UIDNEXT 6 UIDVALIDITY 14572
97769 UNSEEN 1)'])

选择一个邮箱

客户端通过身份验证后,基本的操作模式是选择一个邮箱,然后向服务器询问有关邮箱中消息的信息。连接是有状态的,因此在选择邮箱后,所有命令都会对该邮箱中的消息进行操作,直到选择了新邮箱。

imaplib_select.py

import imaplib
import imaplib_connect

with imaplib_connect.open_connection() as c:
    typ, data = c.select('INBOX')
    print(typ, data)
    num_msgs = int(data[0])
    print('There are {} messages in INBOX'.format(num_msgs))

响应数据包含邮箱中的邮件总数。

$ python3 imaplib_select.py

OK [b'1']
There are 1 messages in INBOX

如果指定了无效的邮箱,则响应代码为NO

imaplib_select_invalid.py

import imaplib
import imaplib_connect

with imaplib_connect.open_connection() as c:
    typ, data = c.select('Does-Not-Exist')
    print(typ, data)

数据包含描述该问题的错误消息。

$ python3 imaplib_select_invalid.py

NO [b"Mailbox doesn't exist: Does-Not-Exist"]

搜索消息

选择邮箱后,使用search()检索邮箱中的邮件ID。

imaplib_search_all.py

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

with imaplib_connect.open_connection() as c:
    typ, mbox_data = c.list()
    for line in mbox_data:
        flags, delimiter, mbox_name = parse_list_response(line)
        c.select('"{}"'.format(mbox_name), readonly=True)
        typ, msg_ids = c.search(None, 'ALL')
        print(mbox_name, typ, msg_ids)

消息 ID 由服务器分配,并且取决于实现。 IMAP4 协议区分了交易期间给定时间点的消息顺序 ID 和消息的 UID 标识符,但并非所有服务器都实现这两者。

$ python3 imaplib_search_all.py

Response code: OK
Server response: b'(.HasChildren) "." Example'
Parsed response: ('.HasChildren', '.', 'Example')
Server response: b'(.HasNoChildren) "." Example.2016'
Parsed response: ('.HasNoChildren', '.', 'Example.2016')
Server response: b'(.HasNoChildren) "." Archive'
Parsed response: ('.HasNoChildren', '.', 'Archive')
Server response: b'(.HasNoChildren) "." "Deleted Messages"'
Parsed response: ('.HasNoChildren', '.', 'Deleted Messages')
Server response: b'(.HasNoChildren) "." INBOX'
Parsed response: ('.HasNoChildren', '.', 'INBOX')
Example OK [b'']
Example.2016 OK [b'1']
Archive OK [b'']
Deleted Messages OK [b'']
INBOX OK [b'1']

在这种情况下,INBOXExample.2016各自具有 ID 为1的不同消息。其他邮箱为空。

搜索条件

可以使用各种其他搜索条件,包括查看消息的日期,标志和其他标题。请参阅 RFC 3501 第6.4.4 节 的完整细节。

要在主题中查找带有 'Example message 2' 的消息,搜索条件应构造为:

(SUBJECT "Example message 2")

本示例在所有邮箱中查找所有标题为 'Example message 2' 的消息:

imaplib_search_subject.py

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

with imaplib_connect.open_connection() as c:
    typ, mbox_data = c.list()
    for line in mbox_data:
        flags, delimiter, mbox_name = parse_list_response(line)
        c.select('"{}"'.format(mbox_name), readonly=True)
        typ, msg_ids = c.search(
            None,
            '(SUBJECT "Example message 2")',
        )
        print(mbox_name, typ, msg_ids)

帐户中只有一条这样的消息,它在 INBOX 中。

$ python3 imaplib_search_subject.py

Response code: OK
Server response: b'(.HasChildren) "." Example'
Parsed response: ('.HasChildren', '.', 'Example')
Server response: b'(.HasNoChildren) "." Example.2016'
Parsed response: ('.HasNoChildren', '.', 'Example.2016')
Server response: b'(.HasNoChildren) "." Archive'
Parsed response: ('.HasNoChildren', '.', 'Archive')
Server response: b'(.HasNoChildren) "." "Deleted Messages"'
Parsed response: ('.HasNoChildren', '.', 'Deleted Messages')
Server response: b'(.HasNoChildren) "." INBOX'
Parsed response: ('.HasNoChildren', '.', 'INBOX')
Example OK [b'']
Example.2016 OK [b'']
Archive OK [b'']
Deleted Messages OK [b'']
INBOX OK [b'1']

搜索条件也可以组合。

imaplib_search_from.py

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

with imaplib_connect.open_connection() as c:
    typ, mbox_data = c.list()
    for line in mbox_data:
        flags, delimiter, mbox_name = parse_list_response(line)
        c.select('"{}"'.format(mbox_name), readonly=True)
        typ, msg_ids = c.search(
            None,
            '(FROM "Doug" SUBJECT "Example message 2")',
        )
        print(mbox_name, typ, msg_ids)

这些标准与逻辑  and  运算结合在一起。

$ python3 imaplib_search_from.py

Response code: OK
Server response: b'(.HasChildren) "." Example'
Parsed response: ('.HasChildren', '.', 'Example')
Server response: b'(.HasNoChildren) "." Example.2016'
Parsed response: ('.HasNoChildren', '.', 'Example.2016')
Server response: b'(.HasNoChildren) "." Archive'
Parsed response: ('.HasNoChildren', '.', 'Archive')
Server response: b'(.HasNoChildren) "." "Deleted Messages"'
Parsed response: ('.HasNoChildren', '.', 'Deleted Messages')
Server response: b'(.HasNoChildren) "." INBOX'
Parsed response: ('.HasNoChildren', '.', 'INBOX')
Example OK [b'']
Example.2016 OK [b'']
Archive OK [b'']
Deleted Messages OK [b'']
INBOX OK [b'1']

获取消息

search() 返回的标识符用于检索消息的内容或部分内容,以便使用 fetch() 方法进行进一步处理。它有两个参数,要获取的消息I D 和要检索的消息部分。

“ message_ids” 参数是逗号分隔的 ID(例如“ 1”,“ 1,2””或 ID 范围(例如“ 1:2”)列表。 message_parts 参数是消息段名称的IMAP列表。如同search()的搜索条件一样,IMAP 协议指定了命名的消息段,因此客户端可以有效地仅检索他们实际需要的消息部分。例如,要检索邮箱中邮件的标题,请使用带有参数BODY.PEEK [HEADER] 的 fetch()

注意

另一种获取标头的方法是 BODY [HEADERS] ,但是这种形式具有将消息隐式标记为已读的副作用,这在许多情况下是不希望的。

imaplib_fetch_raw.py

import imaplib
import pprint
import imaplib_connect

imaplib.Debug = 4
with imaplib_connect.open_connection() as c:
    c.select('INBOX', readonly=True)
    typ, msg_data = c.fetch('1', '(BODY.PEEK[HEADER] FLAGS)')
    pprint.pprint(msg_data)

fetch() 的返回值已经部分解析,因此使用起来比  list() 的返回值难一些。打开调试将显示客户端和服务器之间的完整交互,以了解为什么会这样。

$ python3 imaplib_fetch_raw.py

  19:40.68 imaplib version 2.58
  19:40.68 new IMAP4 connection, tag=b'IIEN'
  19:40.70 < b'* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN
-REFERRALS ID ENABLE IDLE AUTH=PLAIN] Dovecot (Ubuntu) ready.'
  19:40.70 > b'IIEN0 CAPABILITY'
  19:40.73 < b'* CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REF
ERRALS ID ENABLE IDLE AUTH=PLAIN'
  19:40.73 < b'IIEN0 OK Pre-login capabilities listed, post-logi
n capabilities have more.'
  19:40.73 CAPABILITIES: ('IMAP4REV1', 'LITERAL+', 'SASL-IR', 'L
OGIN-REFERRALS', 'ID', 'ENABLE', 'IDLE', 'AUTH=PLAIN')
  19:40.73 > b'IIEN1 LOGIN example "TMFw00fpymotw"'
  19:40.79 < b'* CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REF
ERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD
=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNS
ELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDS
TORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-
STATUS SPECIAL-USE BINARY MOVE'
  19:40.79 < b'IIEN1 OK Logged in'
  19:40.79 > b'IIEN2 EXAMINE INBOX'
  19:40.82 < b'* FLAGS (.Answered .Flagged .Deleted .Seen .
Draft)'
  19:40.82 < b'* OK [PERMANENTFLAGS ()] Read-only mailbox.'
  19:40.82 < b'* 2 EXISTS'
  19:40.82 < b'* 0 RECENT'
  19:40.82 < b'* OK [UNSEEN 1] First unseen.'
  19:40.82 < b'* OK [UIDVALIDITY 1457297769] UIDs valid'
  19:40.82 < b'* OK [UIDNEXT 6] Predicted next UID'
  19:40.82 < b'* OK [HIGHESTMODSEQ 20] Highest'
  19:40.82 < b'IIEN2 OK [READ-ONLY] Examine completed (0.000 sec
s).'
  19:40.82 > b'IIEN3 FETCH 1 (BODY.PEEK[HEADER] FLAGS)'
  19:40.86 < b'* 1 FETCH (FLAGS () BODY[HEADER] {3108}'
  19:40.86 read literal size 3108
  19:40.86 < b')'
  19:40.89 < b'IIEN3 OK Fetch completed.'
  19:40.89 > b'IIEN4 LOGOUT'
  19:40.93 < b'* BYE Logging out'
  19:40.93 BYE response: b'Logging out'
[(b'1 (FLAGS () BODY[HEADER] {3108}',
  b'Return-Path: <doug@doughellmann.com>..Received: from compu
te4.internal ('
  b'compute4.nyi.internal [10.202.2.44])... by sloti26t01 (Cy
rus 3.0.0-beta1'
  b'-git-fastmail-12410) with LMTPA;... Sun, 06 Mar 2016 16:1
6:03 -0500.'
  b'.X-Sieve: CMU Sieve 2.4..X-Spam-known-sender: yes, fadd1c
f2-dc3a-4984-a0'
  b'8b-02cef3cf1221="doug",..  ea349ad0-9299-47b5-b632-6ff1e39
4cc7d="both he'
  b'llfly"..X-Spam-score: 0.0..X-Spam-hits: ALL_TRUSTED -1,
BAYES_00 -1.'
  b'9, LANGUAGES unknown, BAYES_USED global,..  SA_VERSION 3.3
.2..X-Spam'
  b"-source: IP='127.0.0.1', Host='unk', Country='unk', FromHead
er='com',.. "
  b" MailFrom='com'..X-Spam-charsets: plain='us-ascii'..X-Re
solved-to: d"
  b'oughellmann@fastmail.fm..X-Delivered-to: doug@doughellmann
.com..X-Ma'
  b'il-from: doug@doughellmann.com..Received: from mx5 ([10.20
2.2.204]).'
  b'.  by compute4.internal (LMTPProxy); Sun, 06 Mar 2016 16:16
:03 -0500..Re'
  b'ceived: from mx5.nyi.internal (localhost [127.0.0.1])...b
y mx5.nyi.inter'
  b'nal (Postfix) with ESMTP id 47CBA280DB3...for <doug@dough
ellmann.com>; S'
  b'un,  6 Mar 2016 16:16:03 -0500 (EST)..Received: from mx5.n
yi.internal (l'
  b'ocalhost [127.0.0.1])..    by mx5.nyi.internal (Authentica
tion Milter) w'
  b'ith ESMTP..    id A717886846E.30BA4280D81;..    Sun, 6 M
ar 2016 16:1'
  b'6:03 -0500..Authentication-Results: mx5.nyi.internal;..
   dkim=pass'
  b' (1024-bit rsa key) header.d=messagingengine.com header.i=@m
essagingengi'
  b'ne.com header.b=Jrsm+pCo;..    x-local-ip=pass..Received
: from mailo'
  b'ut.nyi.internal (gateway1.nyi.internal [10.202.2.221])...
(using TLSv1.2 '
  b'with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))..
t(No client cer'
  b'tificate requested)...by mx5.nyi.internal (Postfix) with
ESMTPS id 30BA4'
  b'280D81...for <doug@doughellmann.com>; Sun,  6 Mar 2016 16
:16:03 -0500 (E'
  b'ST)..Received: from compute2.internal (compute2.nyi.intern
al [10.202.2.4'
  b'2])...by mailout.nyi.internal (Postfix) with ESMTP id 174
0420D0A...f'
  b'or <doug@doughellmann.com>; Sun,  6 Mar 2016 16:16:03 -0500
(EST)..Recei'
  b'ved: from frontend2 ([10.202.2.161])..  by compute2.intern
al (MEProxy); '
  b'Sun, 06 Mar 2016 16:16:03 -0500..DKIM-Signature: v=1; a=rs
a-sha1; c=rela'
  b'xed/relaxed; d=...messagingengine.com; h=content-transfer
-encoding:conte'
  b'nt-type...:date:from:message-id:mime-version:subject:to:x
-sasl-enc..'
  b'.:x-sasl-enc; s=smtpout; bh=P98NTsEo015suwJ4gk71knAWLa4=; b
=Jrsm+...'
  b'pCovRIoQIRyp8Fl0L6JHOI8sbZy2obx7O28JF2iTlTWmX33Rhlq9403XRklw
N3JA...7KSPq'
  b'MTp30Qdx6yIUaADwQqlO+QMuQq/QxBHdjeebmdhgVfjhqxrzTbSMww/ZNhL
r..Ywv/QM/oDH'
  b'bXiLSUlB3Qrg+9wsE/0jU/EOisiU=..X-Sasl-enc: 8ZJ+4ZRE8AGPzdL
RWQFivGymJb8pa'
  b'4G9JGcb7k4xKn+I 1457298962..Received: from [192.168.1.14]
(75-137-1-34.d'
  b'hcp.nwnn.ga.charter.com [75.137.1.34])...by mail.messagin
gengine.com (Po'
  b'stfix) with ESMTPA id C0B366801CD...for <doug@doughellman
n.com>; Sun,  6'
  b' Mar 2016 16:16:02 -0500 (EST)..From: Doug Hellmann <doug@
doughellmann.c'
  b'om>..Content-Type: text/plain; charset=us-ascii..Content
-Transfer-En'
  b'coding: 7bit..Subject: PyMOTW Example message 2..Message
-Id: <00ABCD'
  b'46-DADA-4912-A451-D27165BC3A2F@doughellmann.com>..Date: Su
n, 6 Mar 2016 '
  b'16:16:02 -0500..To: Doug Hellmann <doug@doughellmann.com>
r.Mime-Vers'
  b'ion: 1.0 (Mac OS X Mail 9.2 .(3112.))..X-Mailer: Apple M
ail (2.3112)'
  b'....'),
 b')']

 FETCH 命令的响应从标志开始,然后指示头数据有595字节。 客户端使用消息的响应构造一个元组,然后用包含右括号 (“)”) 的单个字符串关闭该序列,服务器在获取响应的末尾发送该字符串。 由于采用这种格式,可能更容易分别获取不同的信息,或者重新组合响应并在客户端中对其进行解析。

imaplib_fetch_separately.py

import imaplib
import pprint
import imaplib_connect

with imaplib_connect.open_connection() as c:
    c.select('INBOX', readonly=True)

    print('HEADER:')
    typ, msg_data = c.fetch('1', '(BODY.PEEK[HEADER])')
    for response_part in msg_data:
        if isinstance(response_part, tuple):
            print(response_part[1])

    print('.BODY TEXT:')
    typ, msg_data = c.fetch('1', '(BODY.PEEK[TEXT])')
    for response_part in msg_data:
        if isinstance(response_part, tuple):
            print(response_part[1])

    print('.FLAGS:')
    typ, msg_data = c.fetch('1', '(FLAGS)')
    for response_part in msg_data:
        print(response_part)
        print(imaplib.ParseFlags(response_part))

分别获取值还有一个额外的好处,就是易于使用  ParseFlags() 从响应中解析标志。

$ python3 imaplib_fetch_separately.py

HEADER:
b'Return-Path: <doug@doughellmann.com>..Received: from compute
4.internal (compute4.nyi.internal [10.202.2.44])... by sloti2
6t01 (Cyrus 3.0.0-beta1-git-fastmail-12410) with LMTPA;... Su
n, 06 Mar 2016 16:16:03 -0500..X-Sieve: CMU Sieve 2.4..X-Spa
m-known-sender: yes, fadd1cf2-dc3a-4984-a08b-02cef3cf1221="doug"
,..  ea349ad0-9299-47b5-b632-6ff1e394cc7d="both hellfly"..X-
Spam-score: 0.0..X-Spam-hits: ALL_TRUSTED -1, BAYES_00 -1.9, L
ANGUAGES unknown, BAYES_USED global,..  SA_VERSION 3.3.2..X-
Spam-source: IP=.127.0.0.1., Host=.unk., Country=.unk., Fr
omHeader=.com.,..  MailFrom=.com...X-Spam-charsets: plai
n=.us-ascii...X-Resolved-to: doughellmann@fastmail.fm..X-D
elivered-to: doug@doughellmann.com..X-Mail-from: doug@doughell
mann.com..Received: from mx5 ([10.202.2.204])..  by compute4
.internal (LMTPProxy); Sun, 06 Mar 2016 16:16:03 -0500..Receiv
ed: from mx5.nyi.internal (localhost [127.0.0.1])...by mx5.ny
i.internal (Postfix) with ESMTP id 47CBA280DB3...for <doug@do
ughellmann.com>; Sun,  6 Mar 2016 16:16:03 -0500 (EST)..Receiv
ed: from mx5.nyi.internal (localhost [127.0.0.1])..    by mx5.
nyi.internal (Authentication Milter) with ESMTP..    id A71788
6846E.30BA4280D81;..    Sun, 6 Mar 2016 16:16:03 -0500..Auth
entication-Results: mx5.nyi.internal;..    dkim=pass (1024-bit
 rsa key) header.d=messagingengine.com header.i=@messagingengine
.com header.b=Jrsm+pCo;..    x-local-ip=pass..Received: from
 mailout.nyi.internal (gateway1.nyi.internal [10.202.2.221])..
.(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/25
6 bits))...(No client certificate requested)...by mx5.nyi.
internal (Postfix) with ESMTPS id 30BA4280D81...for <doug@dou
ghellmann.com>; Sun,  6 Mar 2016 16:16:03 -0500 (EST)..Receive
d: from compute2.internal (compute2.nyi.internal [10.202.2.42])
r..by mailout.nyi.internal (Postfix) with ESMTP id 1740420D0A
r..for <doug@doughellmann.com>; Sun,  6 Mar 2016 16:16:03 -050
0 (EST)..Received: from frontend2 ([10.202.2.161])..  by com
pute2.internal (MEProxy); Sun, 06 Mar 2016 16:16:03 -0500..DKI
M-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=...messagi
ngengine.com; h=content-transfer-encoding:content-type...:dat
e:from:message-id:mime-version:subject:to:x-sasl-enc...:x-sas
l-enc; s=smtpout; bh=P98NTsEo015suwJ4gk71knAWLa4=; b=Jrsm+...
pCovRIoQIRyp8Fl0L6JHOI8sbZy2obx7O28JF2iTlTWmX33Rhlq9403XRklwN3JA
...7KSPqMTp30Qdx6yIUaADwQqlO+QMuQq/QxBHdjeebmdhgVfjhqxrzTbSMw
w/ZNhL...Ywv/QM/oDHbXiLSUlB3Qrg+9wsE/0jU/EOisiU=..X-Sasl-en
c: 8ZJ+4ZRE8AGPzdLRWQFivGymJb8pa4G9JGcb7k4xKn+I 1457298962..Re
ceived: from [192.168.1.14] (75-137-1-34.dhcp.nwnn.ga.charter.co
m [75.137.1.34])...by mail.messagingengine.com (Postfix) with
 ESMTPA id C0B366801CD...for <doug@doughellmann.com>; Sun,  6
 Mar 2016 16:16:02 -0500 (EST)..From: Doug Hellmann <doug@doug
hellmann.com>..Content-Type: text/plain; charset=us-ascii..C
ontent-Transfer-Encoding: 7bit..Subject: PyMOTW Example messag
e 2..Message-Id: <00ABCD46-DADA-4912-A451-D27165BC3A2F@doughel
lmann.com>..Date: Sun, 6 Mar 2016 16:16:02 -0500..To: Doug H
ellmann <doug@doughellmann.com>..Mime-Version: 1.0 (Mac OS X M
ail 9.2 .(3112.))..X-Mailer: Apple Mail (2.3112)....'

BODY TEXT:
b'This is the second example message...'

FLAGS:
b'1 (FLAGS ())'
()

全部消息

如前所述,客户端可以分别向服务器询问消息的各个部分。还可以将整个消息作为 RFC 822 格式的邮件消息进行检索,并使用email模块中的类对其进行解析。

imaplib_fetch_rfc822.py

import imaplib
import email
import email.parser

import imaplib_connect

with imaplib_connect.open_connection() as c:
    c.select('INBOX', readonly=True)

    typ, msg_data = c.fetch('1', '(RFC822)')
    for response_part in msg_data:
        if isinstance(response_part, tuple):
            email_parser = email.parser.BytesFeedParser()
            email_parser.feed(response_part[1])
            msg = email_parser.close()
            for header in ['subject', 'to', 'from']:
                print('{:^8}: {}'.format(
                    header.upper(), msg[header]))

email 模块中的解析器使访问和处理消息变得非常容易。本示例仅为每个消息打印一些标题。

$ python3 imaplib_fetch_rfc822.py

SUBJECT : PyMOTW Example message 2
   TO   : Doug Hellmann <doug@doughellmann.com>
  FROM  : Doug Hellmann <doug@doughellmann.com>

上传消息

要将新邮件添加到邮箱,请构造一个Message实例,并将其以及该邮件的时间戳传递给append() 方法。

imaplib_append.py

import imaplib
import time
import email.message
import imaplib_connect

new_message = email.message.Message()
new_message.set_unixfrom('pymotw')
new_message['Subject'] = 'subject goes here'
new_message['From'] = 'pymotw@example.com'
new_message['To'] = 'example@example.com'
new_message.set_payload('This is the body of the message..')

print(new_message)

with imaplib_connect.open_connection() as c:
    c.append('INBOX', '',
             imaplib.Time2Internaldate(time.time()),
             str(new_message).encode('utf-8'))

    # Show the headers for all messages in the mailbox
    c.select('INBOX')
    typ, [msg_ids] = c.search(None, 'ALL')
    for num in msg_ids.split():
        typ, msg_data = c.fetch(num, '(BODY.PEEK[HEADER])')
        for response_part in msg_data:
            if isinstance(response_part, tuple):
                print('.{}:'.format(num))
                print(response_part[1])

本示例中使用的  payload  是一个简单的纯文本电子邮件正文。 Message还支持MIME编码的多部分消息。

$ python3 imaplib_append.py

Subject: subject goes here
From: pymotw@example.com
To: example@example.com

This is the body of the message.

b'1':
b'Return-Path: <doug@doughellmann.com>..Received: from compute
4.internal (compute4.nyi.internal [10.202.2.44])... by sloti2
6t01 (Cyrus 3.0.0-beta1-git-fastmail-12410) with LMTPA;... Su
n, 06 Mar 2016 16:16:03 -0500..X-Sieve: CMU Sieve 2.4..X-Spa
m-known-sender: yes, fadd1cf2-dc3a-4984-a08b-02cef3cf1221="doug"
,..  ea349ad0-9299-47b5-b632-6ff1e394cc7d="both hellfly"..X-
Spam-score: 0.0..X-Spam-hits: ALL_TRUSTED -1, BAYES_00 -1.9, L
ANGUAGES unknown, BAYES_USED global,..  SA_VERSION 3.3.2..X-
Spam-source: IP=.127.0.0.1., Host=.unk., Country=.unk., Fr
omHeader=.com.,..  MailFrom=.com...X-Spam-charsets: plai
n=.us-ascii...X-Resolved-to: doughellmann@fastmail.fm..X-D
elivered-to: doug@doughellmann.com..X-Mail-from: doug@doughell
mann.com..Received: from mx5 ([10.202.2.204])..  by compute4
.internal (LMTPProxy); Sun, 06 Mar 2016 16:16:03 -0500..Receiv
ed: from mx5.nyi.internal (localhost [127.0.0.1])...by mx5.ny
i.internal (Postfix) with ESMTP id 47CBA280DB3...for <doug@do
ughellmann.com>; Sun,  6 Mar 2016 16:16:03 -0500 (EST)..Receiv
ed: from mx5.nyi.internal (localhost [127.0.0.1])..    by mx5.
nyi.internal (Authentication Milter) with ESMTP..    id A71788
6846E.30BA4280D81;..    Sun, 6 Mar 2016 16:16:03 -0500..Auth
entication-Results: mx5.nyi.internal;..    dkim=pass (1024-bit
 rsa key) header.d=messagingengine.com header.i=@messagingengine
.com header.b=Jrsm+pCo;..    x-local-ip=pass..Received: from
 mailout.nyi.internal (gateway1.nyi.internal [10.202.2.221])..
.(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/25
6 bits))...(No client certificate requested)...by mx5.nyi.
internal (Postfix) with ESMTPS id 30BA4280D81...for <doug@dou
ghellmann.com>; Sun,  6 Mar 2016 16:16:03 -0500 (EST)..Receive
d: from compute2.internal (compute2.nyi.internal [10.202.2.42])
r..by mailout.nyi.internal (Postfix) with ESMTP id 1740420D0A
r..for <doug@doughellmann.com>; Sun,  6 Mar 2016 16:16:03 -050
0 (EST)..Received: from frontend2 ([10.202.2.161])..  by com
pute2.internal (MEProxy); Sun, 06 Mar 2016 16:16:03 -0500..DKI
M-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=...messagi
ngengine.com; h=content-transfer-encoding:content-type...:dat
e:from:message-id:mime-version:subject:to:x-sasl-enc...:x-sas
l-enc; s=smtpout; bh=P98NTsEo015suwJ4gk71knAWLa4=; b=Jrsm+...
pCovRIoQIRyp8Fl0L6JHOI8sbZy2obx7O28JF2iTlTWmX33Rhlq9403XRklwN3JA
...7KSPqMTp30Qdx6yIUaADwQqlO+QMuQq/QxBHdjeebmdhgVfjhqxrzTbSMw
w/ZNhL...Ywv/QM/oDHbXiLSUlB3Qrg+9wsE/0jU/EOisiU=..X-Sasl-en
c: 8ZJ+4ZRE8AGPzdLRWQFivGymJb8pa4G9JGcb7k4xKn+I 1457298962..Re
ceived: from [192.168.1.14] (75-137-1-34.dhcp.nwnn.ga.charter.co
m [75.137.1.34])...by mail.messagingengine.com (Postfix) with
 ESMTPA id C0B366801CD...for <doug@doughellmann.com>; Sun,  6
 Mar 2016 16:16:02 -0500 (EST)..From: Doug Hellmann <doug@doug
hellmann.com>..Content-Type: text/plain; charset=us-ascii..C
ontent-Transfer-Encoding: 7bit..Subject: PyMOTW Example messag
e 2..Message-Id: <00ABCD46-DADA-4912-A451-D27165BC3A2F@doughel
lmann.com>..Date: Sun, 6 Mar 2016 16:16:02 -0500..To: Doug H
ellmann <doug@doughellmann.com>..Mime-Version: 1.0 (Mac OS X M
ail 9.2 .(3112.))..X-Mailer: Apple Mail (2.3112)....'

b'2':
b'Subject: subject goes here..From: pymotw@example.com..To:
example@example.com....'

移动和复制消息

消息在服务器上后,可以使用move()copy()将其移动或复制而无需下载。这些方法对消息ID范围进行操作,就像fetch()一样。

imaplib_archive_read.py

import imaplib
import imaplib_connect

with imaplib_connect.open_connection() as c:
    # Find the "SEEN" messages in INBOX
    c.select('INBOX')
    typ, [response] = c.search(None, 'SEEN')
    if typ != 'OK':
        raise RuntimeError(response)
    msg_ids = ','.join(response.decode('utf-8').split(' '))

    # Create a new mailbox, "Example.Today"
    typ, create_response = c.create('Example.Today')
    print('CREATED Example.Today:', create_response)

    # Copy the messages
    print('COPYING:', msg_ids)
    c.copy(msg_ids, 'Example.Today')

    # Look at the results
    c.select('Example.Today')
    typ, [response] = c.search(None, 'ALL')
    print('COPIED:', response)

该示例脚本在Example下创建一个新邮箱,并将读取的邮件从INBOX复制到其中。

$ python3 imaplib_archive_read.py

CREATED Example.Today: [b'Completed']
COPYING: 2
COPIED: b'1'

再次运行同一脚本显示了检查返回码的重要性。除了引发异常外,对create() 的调用使新邮箱报告该邮箱已存在。

$ python3 imaplib_archive_read.py

CREATED Example.Today: [b'[ALREADYEXISTS] Mailbox already exists
']
COPYING: 2
COPIED: b'1 2'

删除讯息

尽管许多现代邮件客户端使用“垃圾箱”模型来处理已删除的邮件,但通常不会将邮件移到实际的文件夹中。而是更新其标志以添加.eleted。通过EXPUNGE命令实现“清空”垃圾的操作。此示例脚本查找主题中带有“ Lorem ipsum ”的已归档邮件,设置已删除标志,然后通过再次查询服务器来显示邮件仍存在于文件夹中。

imaplib_delete_messages.py

import imaplib
import imaplib_connect
from imaplib_list_parse import parse_list_response

with imaplib_connect.open_connection() as c:
    c.select('Example.Today')

    # What ids are in the mailbox?
    typ, [msg_ids] = c.search(None, 'ALL')
    print('Starting messages:', msg_ids)

    # Find the message(s)
    typ, [msg_ids] = c.search(
        None,
        '(SUBJECT "subject goes here")',
    )
    msg_ids = ','.join(msg_ids.decode('utf-8').split(' '))
    print('Matching messages:', msg_ids)

    # What are the current flags?
    typ, response = c.fetch(msg_ids, '(FLAGS)')
    print('Flags before:', response)

    # Change the Deleted flag
    typ, response = c.store(msg_ids, '+FLAGS', r'(.eleted)')

    # What are the flags now?
    typ, response = c.fetch(msg_ids, '(FLAGS)')
    print('Flags after:', response)

    # Really delete the message.
    typ, response = c.expunge()
    print('Expunged:', response)

    # What ids are left in the mailbox?
    typ, [msg_ids] = c.search(None, 'ALL')
    print('Remaining messages:', msg_ids)

显式调用expunge()会删除消息,但是调用close()具有相同的效果。区别在于调用close()时不会通知客户端有关删除的操作。

$ python3 imaplib_delete_messages.py

Response code: OK
Server response: b'(.HasChildren) "." Example'
Parsed response: ('.HasChildren', '.', 'Example')
Server response: b'(.HasNoChildren) "." Example.Today'
Parsed response: ('.HasNoChildren', '.', 'Example.Today')
Server response: b'(.HasNoChildren) "." Example.2016'
Parsed response: ('.HasNoChildren', '.', 'Example.2016')
Server response: b'(.HasNoChildren) "." Archive'
Parsed response: ('.HasNoChildren', '.', 'Archive')
Server response: b'(.HasNoChildren) "." "Deleted Messages"'
Parsed response: ('.HasNoChildren', '.', 'Deleted Messages')
Server response: b'(.HasNoChildren) "." INBOX'
Parsed response: ('.HasNoChildren', '.', 'INBOX')
Starting messages: b'1 2'
Matching messages: 1,2
Flags before: [b'1 (FLAGS (.Seen))', b'2 (FLAGS (.Seen))']
Flags after: [b'1 (FLAGS (.Deleted .Seen))', b'2 (FLAGS (.Del
eted .Seen))']
Expunged: [b'2', b'1']
Remaining messages: b''

另请参见

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/pymotw/imaplib-...

译文地址:https://learnku.com/docs/pymotw/imaplib-...

上一篇 下一篇
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~