20-搜索电子邮件
18.5.3 搜索电子邮件
登录后,实际获取你感兴趣的电子邮件分为两步。首先,必须选择要搜索的文件夹。然后,必须调用 IMAPClient
对象的 search()
方法,向其传入IMAP搜索关键词字符串。
选择文件夹
几乎每个账户默认有一个INBOX文件夹, 但也可以调用 IMAPClient
对象的 list_folders()
方法来获取文件夹列表。这将返回一个元组的列表。每个元组包含一个文件夹的信息。输入以下代码,继续演示交互式环境的例子:
>>> import pprint
>>> pprint.pprint(imapObj.list_folders())
[(('\\HasNoChildren',), '/', 'Drafts'),
(('\\HasNoChildren',), '/', 'Filler'),
(('\\HasNoChildren',), '/', 'INBOX'),
(('\\HasNoChildren',), '/', 'Sent'),
--snip--
(('\\HasNoChildren', '\\Flagged'), '/', 'Starred'),
(('\\HasNoChildren', '\\Trash'), '/', 'Trash')]
每个元组的3个值,例如 (('\\HasNoChildren',), '/', 'INBOX')
,其解释如下。
- 该文件夹的标志的元组(这些标志代表到底是什么超出了本书的讨论范围,你可以放心地忽略该字段)。
- 名称字符串中用于分隔父文件夹和子文件夹的分隔符。
- 该文件夹的全名。
要选择一个文件夹进行搜索,就调用 IMAPClient
对象的 select_folder()
方法,传入该文件夹的名称字符串:
>>> imapObj.select_folder('INBOX', readonly=True)
可以忽略 select_folder()
的返回值。如果所选文件夹不存在,Python会抛出 imaplib.error
异常。
readonly=True
关键字参数可以防止你在随后的方法调用中,不小心更改或删除该文件夹中的任何电子邮件。除非你想删除电子邮件,否则将 readonly
设置为 True
总是个好主意。
执行搜索
文件夹选中后,就可以用 IMAPClient
对象的 search()
方法搜索电子邮件。 search()
的参数是一个字符串列表,每一个被格式化为IMAP搜索键。表18-3介绍了IMAP的各种搜索键。
请注意,在处理标志和搜索键方面,某些IMAP服务器的实现可能稍有不同。可能需要在交互式环境中试验一下,看看它们实际的行为如何。
在传入 search()
方法的列表参数中,可以有多个IMAP搜索键字符串。返回的消息将匹配所有的搜索键。如果想匹配任何一个搜索键,使用OR搜索键。对于NOT和OR搜索键,它们后边分别跟着一个和两个完整的搜索键。
| 搜索键 | 含义 |
| :----- | :----- | :----- | :----- |
| 'ALL'
| 返回该文件夹中的所有邮件。如果你请求一个大文件夹中的所有消息,可能会遇到 imaplib
的大小限制。参见本小节“大小限制”部分 |
| 'BEFORE date',
'ON date',
'SINCE date'
| 这3个搜索键分别返回给定date之前、当天和之后IMAP服务器接收的消息。日期的格式必须为05-Jul-2015。此外,虽然 'SINCE 05-Jul-2015'
将匹配7月5日当天和之后的消息,但 'BEFORE 05-Jul-2015'
仅匹配7月5日之前的消息,不包括7月5日当天 |
| 'SUBJECT string',
'BODY string',
'TEXT string'
| 分别返回 string
出现在主题、正文、主题或正文中的消息。如果 string
中有空格,就使用双引号: 'TEXT "search with spaces"'
|
| 'FROM string',
'TO string',
'CC string',
'BCC string'
| 返回所有消息,其中 string
分别出现在“ from
”邮件地址、“ to
”邮件地址、“ cc
”(抄送)地址或“ bcc
”(密件抄送)地址中。如果 string
中有多个电子邮件地址,就用空格将它们分开,并使用双引号: 'CC "[email protected] [email protected]"'
|
| 'SEEN',
'UNSEEN'
| 分别返回包含和不包含 \Seen
标记的所有信息。如果电子邮件已经被 fetch()
方法调用访问(稍后描述),或者你曾在电子邮件程序或网络浏览器中单击过它,就会有 \Seen
标记。比较常用的说法是电子邮件“已读”,而不是“已看”,但它们的意思一样 |
| 'ANSWERED',
'UNANSWERED'
| 分别返回包含和不包含 \Answered
标记的所有消息。如果消息已答复,就会有\ Answered
标记 |
| 'DELETED',
'UNDELETED'
| 分别返回包含和不包含 \Deleted
标记的所有信息。用 delete_messages()
方法删除的邮件就会有 \Deleted
标记,直到调用 expunge()
方法才会永久删除(请参阅18.5.7小节“删除电子邮件”)。请注意,一些电子邮件提供商,例如Gmail,会自动清除邮件 |
| 'DRAFT',
'UNDRAFT'
| 分别返回包含和不包含 \Draft
标记的所有消息。草稿邮件通常保存在单独的草稿文件夹中,而不是在收件箱中 |
| 'FLAGGED',
'UNFLAGGED'
| 分别返回包含和不包含 \Flagged
标记的所有消息。这个标记通常用来标记电子邮件为“重要”或“紧急” |
| 'LARGER N',
'SMALLER N'
| 分别返回大于或小于N个字节的所有消息 |
| 'NOT search-key '
| 返回搜索键不会返回的那些消息 |
| 'OR search-key1
search-key2'
| 返回符合第一个或第二个搜索键的消息 |
下面是 search()
方法调用的一些例子,以及它们的含义。
imapObj.search(['ALL'])
返回当前选定的文件夹中的每一个消息。
imapObj.search(['ON 05-Jul-2019'])
返回在2019年7月5日发送的每个消息。
imapObj.search(['SINCE 01-Jan-2019', 'BEFORE 01-Feb-2019', 'UNSEEN'])
返回2019年1月发送的所有未读消息(注意,这意味着从1月1日直到2月1日,但不包括2月1日)。
imapObj.search(['SINCE 01-Jan-2019', 'FROM alice@example. com'])
返回自2019年开始以来,发自[email protected]的消息。
imapObj.search(['SINCE 01-Jan-2019', 'NOT FROM alice@example. com'])
返回自2019年开始以来,除[email protected]外,其他所有人发来的消息。
imapObj.search(['OR FROM [email protected] FROM bob@example. com'])
返回发自[email protected]或[email protected]的所有信息。
imapObj.search(['FROM [email protected]', 'FROM bob@example. com'])
为恶作剧例子。该搜索不会返回任何消息,因为消息必须匹配所有搜索关键词。因为只能有一个“ from
”地址,所以一条消息不可能既来自[email protected],又来自[email protected]。
search()
方法不返回电子邮件本身,而是返回邮件的唯一ID(UID)。然后,可以将这些UID传入 fetch()
方法,获得邮件内容。
输入以下代码,继续演示交互式环境的例子:
>>> UIDs = imapObj.search(['SINCE 05-Jul-2019'])
>>> UIDs
[40032, 40033, 40034, 40035, 40036, 40037, 40038, 40039, 40040, 40041]
这里, search()
返回的消息ID列表(针对7月5日以来接收的消息)保存在UIDs中。计算机上返回的UIDs列表与这里显示的不同,它们对于特定的电子邮件账户是唯一的。如果你稍后将UID传递给其他函数调用,请用你收到的UID值,而不是本书例子中输出的。
大小限制
如果你的搜索匹配大量的电子邮件,Python可能抛出异常 imaplib.error: got more than 10000 bytes
。如果发生这种情况,必须断开并重连IMAP服务器,然后再试。
这个限制是防止Python程序消耗太多内存。遗憾的是,默认大小限制往往太小。可以执行下面的代码,将限制从10 000字节改为10 000 000字节:
>>> import imaplib
>>> imaplib._MAXLINE = 10000000
这能避免该异常再次出现。也许要在你写的每一个IMAP程序中加上这两行。