14.4. mailbox — 管理 Email 规定文件

未匹配的标注

目的:处理各种本地文件格式的电子邮件。

mailbox模块定义了用于访问以本地磁盘格式存储的电子邮件的通用API,包括:

  • Maildir
  • mbox
  • MH
  • Babyl
  • MMDF

MailboxMessage的基类,每种邮箱格式都包括一对相应的子类,以实现该格式的详细信息。

mbox

mbox格式是纯文本格式,因此最容易在文档中显示。每个邮箱都存储为单个文件,所有邮件都串联在一起。每次遇到以“From”(“发件人”,后跟一个空格)开头的行,它将被视为新消息的开头。每当这些字符出现在消息正文中行的开头时,都可以通过在行前面添加“>” 来对其进行转义。

创建一个mbox邮箱

通过将文件名传递给构造函数来实例化mbox类。如果文件不存在,则在使用add()附加消息时创建该文件。

mailbox_mbox_create.py

import mailbox
import email.utils

from_addr = email.utils.formataddr(('Author',
                                    'author@example.com'))
to_addr = email.utils.formataddr(('Recipient',
                                  'recipient@example.com'))

payload = '''This is the body.
From (will not be escaped).
There are 3 lines.
'''

mbox = mailbox.mbox('example.mbox')
mbox.lock()
try:
    msg = mailbox.mboxMessage()
    msg.set_unixfrom('author Sat Feb  7 01:05:34 2009')
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Subject'] = 'Sample message 1'
    msg.set_payload(payload)
    mbox.add(msg)
    mbox.flush()

    msg = mailbox.mboxMessage()
    msg.set_unixfrom('author')
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Subject'] = 'Sample message 2'
    msg.set_payload('This is the second body..')
    mbox.add(msg)
    mbox.flush()
finally:
    mbox.unlock()

print(open('example.mbox', 'r').read())

该脚本的结果是一个包含两个电子邮件的新邮箱文件。

$ python3 mailbox_mbox_create.py

From MAILER-DAEMON Sun Mar 18 20:20:59 2018
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 1

This is the body.
>From (will not be escaped).
There are 3 lines.

From MAILER-DAEMON Sun Mar 18 20:20:59 2018
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 2

This is the second body.

Reading an mbox Mailbox

要读取现有邮箱,请打开它并将mbox对象视为字典。密钥是邮箱实例定义的任意值,除了作为邮件对象的内部标识符之外,没有其他意义。

mailbox_mbox_read.py

import mailbox

mbox = mailbox.mbox('example.mbox')
for message in mbox:
    print(message['subject'])

打开的邮箱支持迭代器协议,但是与真正的字典对象不同,邮箱的默认迭代器在 values 而不是 keys 上运行。

$ python3 mailbox_mbox_read.py

Sample message 1
Sample message 2

从mbox邮箱中删除邮件

要从 mbox 文件中删除现有消息,请使用其键和remove()或使用del

mailbox_mbox_remove.py

import mailbox

mbox = mailbox.mbox('example.mbox')
mbox.lock()
try:
    to_remove = []
    for key, msg in mbox.iteritems():
        if '2' in msg['subject']:
            print('Removing:', key)
            to_remove.append(key)
    for key in to_remove:
        mbox.remove(key)
finally:
    mbox.flush()
    mbox.close()

print(open('example.mbox', 'r').read())

lock()unlock()方法用于防止问题同时访问文件,并且flush()强制将更改写入磁盘。

$ python3 mailbox_mbox_remove.py

Removing: 1
From MAILER-DAEMON Sun Mar 18 20:20:59 2018
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 1

This is the body.
>From (will not be escaped).
There are 3 lines.

Maildir

创建 Maildir 格式是为了消除同时修改 mbox 文件的问题。邮箱不使用单个文件,而是组织为目录,其中每个邮件都包含在其自己的文件中。这还允许嵌套邮箱,因此 Maildir 邮箱的 API 扩展了与子文件夹一起使用的方法。

创建Maildir邮箱

创建Maildirmbox的唯一真正区别是构造函数的参数是目录名而不是文件名。和以前一样,如果邮箱不存在,则会在添加邮件时创建它。

mailbox_maildir_create.py

import mailbox
import email.utils
import os

from_addr = email.utils.formataddr(('Author',
                                    'author@example.com'))
to_addr = email.utils.formataddr(('Recipient',
                                  'recipient@example.com'))

payload = '''This is the body.
From (will not be escaped).
There are 3 lines.
'''

mbox = mailbox.Maildir('Example')
mbox.lock()
try:
    msg = mailbox.mboxMessage()
    msg.set_unixfrom('author Sat Feb  7 01:05:34 2009')
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Subject'] = 'Sample message 1'
    msg.set_payload(payload)
    mbox.add(msg)
    mbox.flush()

    msg = mailbox.mboxMessage()
    msg.set_unixfrom('author Sat Feb  7 01:05:34 2009')
    msg['From'] = from_addr
    msg['To'] = to_addr
    msg['Subject'] = 'Sample message 2'
    msg.set_payload('This is the second body..')
    mbox.add(msg)
    mbox.flush()
finally:
    mbox.unlock()

for dirname, subdirs, files in os.walk('Example'):
    print(dirname)
    print('  Directories:', subdirs)
    for name in files:
        fullname = os.path.join(dirname, name)
        print('.***', fullname)
        print(open(fullname).read())
        print('*' * 20)

将邮件添加到邮箱后,它们将转到new子目录。

警告

尽管从多个进程中写入同一maildir是安全的,但是add()并不是线程安全的。使用信号量或其他锁定设备,以防止同一进程的多个线程同时修改邮箱。

$ python3 mailbox_maildir_create.py

Example
  Directories: ['new', 'cur', 'tmp']
Example/new
  Directories: []

*** Example/new/1521404460.M306174P41689Q2.hubert.local
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 2

This is the second body.

********************

*** Example/new/1521404460.M303200P41689Q1.hubert.local
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 1

This is the body.
From (will not be escaped).
There are 3 lines.

********************
Example/cur
  Directories: []
Example/tmp
  Directories: []

读取它们之后,客户端可以使用MaildirMessageset_subdir()方法将它们移动到cur子目录中。

mailbox_maildir_set_subdir.py

import mailbox
import os

print('Before:')
mbox = mailbox.Maildir('Example')
mbox.lock()
try:
    for message_id, message in mbox.iteritems():
        print('{:6} "{}"'.format(message.get_subdir(),
                                 message['subject']))
        message.set_subdir('cur')
        # Tell the mailbox to update the message.
        mbox[message_id] = message
finally:
    mbox.flush()
    mbox.close()

print('.After:')
mbox = mailbox.Maildir('Example')
for message in mbox:
    print('{:6} "{}"'.format(message.get_subdir(),
                             message['subject']))

print()
for dirname, subdirs, files in os.walk('Example'):
    print(dirname)
    print('  Directories:', subdirs)
    for name in files:
        fullname = os.path.join(dirname, name)
        print(fullname)

尽管maildir包含“ tmp”目录,但set_subdir()的唯一有效参数是“ cur”和“ new” 。

$ python3 mailbox_maildir_set_subdir.py

Before:
new    "Sample message 2"
new    "Sample message 1"

After:
cur    "Sample message 2"
cur    "Sample message 1"

Example
  Directories: ['new', 'cur', 'tmp']
Example/new
  Directories: []
Example/cur
  Directories: []
Example/cur/1521404460.M306174P41689Q2.hubert.local
Example/cur/1521404460.M303200P41689Q1.hubert.local
Example/tmp
  Directories: []

Reading a Maildir Mailbox

从现有 Maildir 邮箱中进行读取就像 mbox 邮箱一样。

mailbox_maildir_read.py

import mailbox

mbox = mailbox.Maildir('Example')
for message in mbox:
    print(message['subject'])

不能保证以任何特定顺序读取消息。

$ python3 mailbox_maildir_read.py

Sample message 2
Sample message 1

从Maildir邮箱中删除邮件

要从Maildir邮箱中删除现有邮件,请将其密钥传递到remove()或使用del

mailbox_maildir_remove.py

import mailbox
import os

mbox = mailbox.Maildir('Example')
mbox.lock()
try:
    to_remove = []
    for key, msg in mbox.iteritems():
        if '2' in msg['subject']:
            print('Removing:', key)
            to_remove.append(key)
    for key in to_remove:
        mbox.remove(key)
finally:
    mbox.flush()
    mbox.close()

for dirname, subdirs, files in os.walk('Example'):
    print(dirname)
    print('  Directories:', subdirs)
    for name in files:
        fullname = os.path.join(dirname, name)
        print('.***', fullname)
        print(open(fullname).read())
        print('*' * 20)

无法计算邮件的密钥,因此请同时使用items()iteritems()从邮箱同时检索密钥和消息对象。

$ python3 mailbox_maildir_remove.py

Removing: 1521404460.M306174P41689Q2.hubert.local
Example
  Directories: ['new', 'cur', 'tmp']
Example/new
  Directories: []
Example/cur
  Directories: []

*** Example/cur/1521404460.M303200P41689Q1.hubert.local
From: Author <author@example.com>
To: Recipient <recipient@example.com>
Subject: Sample message 1

This is the body.
From (will not be escaped).
There are 3 lines.

********************
Example/tmp
  Directories: []

Maildir文件夹

Maildir 邮箱的子目录或 文件夹 可以直接通过Maildir类的方法进行管理。呼叫者可以列出,检索,创建和删除给定邮箱的子文件夹。

mailbox_maildir_folders.py

import mailbox
import os

def show_maildir(name):
    os.system('find {} -print'.format(name))

mbox = mailbox.Maildir('Example')
print('Before:', mbox.list_folders())
show_maildir('Example')

print('.{:#^30}.'.format(''))

mbox.add_folder('subfolder')
print('subfolder created:', mbox.list_folders())
show_maildir('Example')

subfolder = mbox.get_folder('subfolder')
print('subfolder contents:', subfolder.list_folders())

print('.{:#^30}.'.format(''))

subfolder.add_folder('second_level')
print('second_level created:', subfolder.list_folders())
show_maildir('Example')

print('.{:#^30}.'.format(''))

subfolder.remove_folder('second_level')
print('second_level removed:', subfolder.list_folders())
show_maildir('Example')

文件夹的目录名称是通过在文件夹名称前加上句点(.)来构造的。

$ python3 mailbox_maildir_folders.py

Example
Example/new
Example/cur
Example/cur/1521404460.M303200P41689Q1.hubert.local
Example/tmp
Example
Example/.subfolder
Example/.subfolder/maildirfolder
Example/.subfolder/new
Example/.subfolder/cur
Example/.subfolder/tmp
Example/new
Example/cur
Example/cur/1521404460.M303200P41689Q1.hubert.local
Example/tmp
Example
Example/.subfolder
Example/.subfolder/.second_level
Example/.subfolder/.second_level/maildirfolder
Example/.subfolder/.second_level/new
Example/.subfolder/.second_level/cur
Example/.subfolder/.second_level/tmp
Example/.subfolder/maildirfolder
Example/.subfolder/new
Example/.subfolder/cur
Example/.subfolder/tmp
Example/new
Example/cur
Example/cur/1521404460.M303200P41689Q1.hubert.local
Example/tmp
Example
Example/.subfolder
Example/.subfolder/maildirfolder
Example/.subfolder/new
Example/.subfolder/cur
Example/.subfolder/tmp
Example/new
Example/cur
Example/cur/1521404460.M303200P41689Q1.hubert.local
Example/tmp
Before: []

##############################

subfolder created: ['subfolder']
subfolder contents: []

##############################

second_level created: ['second_level']

##############################

second_level removed: []

消息标志

邮箱中的邮件具有用于跟踪各个方面的标志,例如邮件是否已被阅读,阅读者将其标记为重要或以后标记为删除。标志以特定于格式的字母代码序列存储,Message类具有检索和更改标志值的方法。此示例在添加标记以表明该消息被视为重要之前,在Example maildir中显示消息上的标志。

mailbox_maildir_add_flag.py

import mailbox

print('Before:')
mbox = mailbox.Maildir('Example')
mbox.lock()
try:
    for message_id, message in mbox.iteritems():
        print('{:6} "{}"'.format(message.get_flags(),
                                 message['subject']))
        message.add_flag('F')
        # 告诉邮箱更新消息
        mbox[message_id] = message
finally:
    mbox.flush()
    mbox.close()

print('.After:')
mbox = mailbox.Maildir('Example')
for message in mbox:
    print('{:6} "{}"'.format(message.get_flags(),
                             message['subject']))

默认情况下,消息没有标志。添加标志会更改内存中的消息,但不会更新磁盘上的消息。若要更新磁盘上的邮件,请使用其现有标识符将邮件对象存储在邮箱中。

$ python3 mailbox_maildir_add_flag.py

Before:
       "Sample message 1"

After:
F      "Sample message 1"

使用add_flag()添加标志会保留所有现有标志。使用set_flags()覆盖任何现有的标志集,并将其替换为传递给该方法的新值。

mailbox_maildir_set_flags.py

import mailbox

print('Before:')
mbox = mailbox.Maildir('Example')
mbox.lock()
try:
    for message_id, message in mbox.iteritems():
        print('{:6} "{}"'.format(message.get_flags(),
                                 message['subject']))
        message.set_flags('S')
        # 告诉邮箱更新消息
        mbox[message_id] = message
finally:
    mbox.flush()
    mbox.close()

print('.After:')
mbox = mailbox.Maildir('Example')
for message in mbox:
    print('{:6} "{}"'.format(message.get_flags(),
                             message['subject']))

在本示例中,当set_flags()将标志替换为S时,上一示例添加的F标志丢失。

$ python3 mailbox_maildir_set_flags.py

Before:
F      "Sample message 1"

After:
S      "Sample message 1"

其他格式

mailbox支持其他几种格式,但没有一种比mbox或Maildir受欢迎。 MH是某些邮件处理程序使用的另一种多文件邮箱格式。 Babyl和MMDF是单文件格式,具有与mbox不同的消息分隔符。单文件格式支持与mbox相同的API,MH包括Maildir类中与文件夹相关的方法。

另请参见

-邮箱的标准库文档
-邮箱的Python 2至3移植说明
-来自qmail的mbox联机帮助页– mbox格式的文档。
-来自qmail的Maildir联机帮助页– Maildir格式的文档。
-电子邮件-电子邮件模块。
-[imaplib](pymotw.com/3/imaplib/index.html#mo...“ imaplib:IMAP4客户端库”)– – imaplib模块可以在IMAP服务器上使用已保存的电子邮件。

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

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

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

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

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


暂无话题~