使用 Python 和 imaplib 在 GMail 中移动电子邮件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3527933/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 11:34:51  来源:igfitidea点击:

Move an email in GMail with Python and imaplib

pythongmailimapimaplib

提问by Dan

I want to be able to move an email in GMail from the inbox to another folder using Python. I am using imaplib and can't figure out how to do it.

我希望能够使用 Python 将 GMail 中的电子邮件从收件箱移动到另一个文件夹。我正在使用 imaplib,但不知道该怎么做。

采纳答案by Manoj Govindan

There is no explicit move command for IMAP. You will have to execute a COPYfollowed by a STORE(with suitable flag to indicate deletion) and finally expunge. The example given below worked for moving messages from one label to the other. You'll probably want to add more error checking though.

IMAP 没有明确的移动命令。您必须执行 aCOPY后跟 a STORE(带有合适的标志以指示删除)和 finally expunge。下面给出的示例用于将消息从一个标签移动到另一个标签。不过,您可能希望添加更多错误检查。

import imaplib, getpass, re
pattern_uid = re.compile('\d+ \(UID (?P<uid>\d+)\)')

def connect(email):
    imap = imaplib.IMAP4_SSL("imap.gmail.com")
    password = getpass.getpass("Enter your password: ")
    imap.login(email, password)
    return imap

def disconnect(imap):
    imap.logout()

def parse_uid(data):
    match = pattern_uid.match(data)
    return match.group('uid')

if __name__ == '__main__':
    imap = connect('<your mail id>')
    imap.select(mailbox = '<source folder>', readonly = False)
    resp, items = imap.search(None, 'All')
    email_ids  = items[0].split()
    latest_email_id = email_ids[-1] # Assuming that you are moving the latest email.

    resp, data = imap.fetch(latest_email_id, "(UID)")
    msg_uid = parse_uid(data[0])

    result = imap.uid('COPY', msg_uid, '<destination folder>')

    if result[0] == 'OK':
        mov, data = imap.uid('STORE', msg_uid , '+FLAGS', '(\Deleted)')
        imap.expunge()

    disconnect(imap)

回答by Avadhesh

I suppose one has a uid of the email which is going to be moved.

我想一个人有一个将要移动的电子邮件的 uid。

import imaplib
obj = imaplib.IMAP4_SSL('imap.gmail.com', 993)
obj.login('username', 'password')
obj.select(src_folder_name)
apply_lbl_msg = obj.uid('COPY', msg_uid, desti_folder_name)
if apply_lbl_msg[0] == 'OK':
    mov, data = obj.uid('STORE', msg_uid , '+FLAGS', '(\Deleted)')
    obj.expunge()

回答by scraplesh

As for Gmail, based on its api working with labels, the only thing for you to do is adding dest label and deleting src label:

至于 Gmail,基于它的api 与 labels 一起工作,你唯一要做的就是添加 dest 标签和删除 src 标签:

import imaplib
obj = imaplib.IMAP4_SSL('imap.gmail.com', 993)
obj.login('username', 'password')
obj.select(src_folder_name)
typ, data = obj.uid('STORE', msg_uid, '+X-GM-LABELS', desti_folder_name)
typ, data = obj.uid('STORE', msg_uid, '-X-GM-LABELS', src_folder_name)

回答by Neal Young

None of the previous solutions worked for me. I was unable to delete a message from the selected folder, and unable to remove the label for the folder when the label was the selected folder. Here's what ended up working for me:

以前的解决方案都不适合我。我无法从所选文件夹中删除邮件,并且当标签是所选文件夹时无法删除文件夹的标签。以下是最终为我工作的内容:

import email, getpass, imaplib, os, sys, re

user = "[email protected]"
pwd = "password" #getpass.getpass("Enter your password: ")

m = imaplib.IMAP4_SSL("imap.gmail.com")
m.login(user,pwd)

from_folder = "Notes"
to_folder = "food"

m.select(from_folder, readonly = False)

response, emailids = imap.search(None, 'All')
assert response == 'OK'

emailids = emailids[0].split()

errors = []
labeled = []
for emailid in emailids:
    result = m.fetch(emailid, '(X-GM-MSGID)')
    if result[0] != 'OK':
        errors.append(emailid)
        continue

    gm_msgid = re.findall(r"X-GM-MSGID (\d+)", result[1][0])[0]

    result = m.store(emailid, '+X-GM-LABELS', to_folder)

    if result[0] != 'OK':
        errors.append(emailid)
        continue

    labeled.append(gm_msgid)

m.close()
m.select(to_folder, readonly = False)

errors2 = []

for gm_msgid in labeled:
    result = m.search(None, '(X-GM-MSGID "%s")' % gm_msgid)

    if result[0] != 'OK':
        errors2.append(gm_msgid)
        continue

    emailid = result[1][0]
    result = m.store(emailid, '-X-GM-LABELS', from_folder)

    if result[0] != 'OK':
        errors2.append(gm_msgid)
        continue

m.close()
m.logout()

if errors: print >>sys.stderr, len(errors), "failed to add label", to_folder
if errors2: print >>sys.stderr, len(errors2), "failed to remove label", from_folder

回答by Thanos

I know that this is a very old question, but any way. The proposed solution by Manoj Govindanprobably works perfectly (I have not tested it but it looks like it. The problem that I encounter and I had to solve is how to copy/move more than one email!!!

我知道这是一个非常古老的问题,但无论如何。Manoj Govindan提出的解决方案可能效果很好(我没有测试过,但看起来很像。我遇到并且必须解决的问题是如何复制/移动不止一封电子邮件!!!

So I came up with solution, maybe someone else in the future might have the same problem.

所以我想出了解决方案,也许将来其他人可能会遇到同样的问题。

The steps are simple, I connect to my email (GMAIL) account choose folder to process (e.g. INBOX) fetch all uids, instead of email(s) list number. This is a crucial point to notice here. If we fetched the list number of emails and then we processed the list we would end up with a problem. When we move an email the process is simple (copy at the destination folder and delete email from each current location). The problem appears if you have a list of emails e.g. 4 emails inside the inbox and we process the 2nd email in inside the list then number 3 and 4 are different, they are not the emails that we thought that they would be, which will result into an error because list item number 4 it will not exist since the list moved one position down because 2 position was empty.

步骤很简单,我连接到我的电子邮件 (GMAIL) 帐户,选择要处理的文件夹(例如 INBOX)获取所有 uid,而不是电子邮件列表编号。这是这里需要注意的关键点。如果我们获取电子邮件的列表编号,然后我们处理列表,我们最终会遇到问题。当我们移动电子邮件时,过程很简单(在目标文件夹中复制并从每个当前位置删除电子邮件)。如果您有一个电子邮件列表,例如收件箱中有 4 封电子邮件,并且我们在列表中处理第二封电子邮件,那么问题就出现了,然后数字 3 和 4 是不同的,它们不是我们认为它们会是的电子邮件,这将导致进入错误,因为列表项目编号 4 它将不存在,因为列表向下移动了一个位置,因为 2 个位置为空。

So the only possible solution to this problem was to use UIDs. Which are unique numbers for each email. So no matter how the email will change this number will be binded with the email.

所以这个问题唯一可能的解决方案是使用 UID。这是每封电子邮件的唯一编号。所以无论电子邮件如何更改,这个号码都会与电子邮件绑定。

So in the example below, I fetch the UIDs on the first step,check if folder is empty no point of processing the folder else iterate for all emails found in the folder. Next fetch each email Header. The headers will help us to fetch the Subject and compare the subject of the email with the one that we are searching. If the subject matches, then continue to copy and delete the email. Then you are done. Simple as that.

因此,在下面的示例中,我在第一步中获取 UID,检查文件夹是否为空,无需处理文件夹,否则迭代文件夹中找到的所有电子邮件。接下来获取每个电子邮件标题。标题将帮助我们获取主题并将电子邮件的主题与我们正在搜索的主题进行比较。如果主题匹配,则继续复制并删除电子邮件。然后你就完成了。就那么简单。

#!/usr/bin/env python

import email
import pprint
import imaplib

__author__ = 'author'


def initialization_process(user_name, user_password, folder):
    imap4 = imaplib.IMAP4_SSL('imap.gmail.com')  # Connects over an SSL encrypted socket
    imap4.login(user_name, user_password)
    imap4.list()  # List of "folders" aka labels in gmail
    imap4.select(folder)  # Default INBOX folder alternative select('FOLDER')
    return imap4


def logout_process(imap4):
    imap4.close()
    imap4.logout()
    return


def main(user_email, user_pass, scan_folder, subject_match, destination_folder):
    try:
        imap4 = initialization_process(user_email, user_pass, scan_folder)
        result, items = imap4.uid('search', None, "ALL")  # search and return uids
        dictionary = {}
        if items == ['']:
            dictionary[scan_folder] = 'Is Empty'
        else:
            for uid in items[0].split():  # Each uid is a space separated string
                dictionary[uid] = {'MESSAGE BODY': None, 'BOOKING': None, 'SUBJECT': None, 'RESULT': None}
                result, header = imap4.uid('fetch', uid, '(UID BODY[HEADER])')
                if result != 'OK':
                    raise Exception('Can not retrieve "Header" from EMAIL: {}'.format(uid))
                subject = email.message_from_string(header[0][1])
                subject = subject['Subject']
                if subject is None:
                    dictionary[uid]['SUBJECT'] = '(no subject)'
                else:
                    dictionary[uid]['SUBJECT'] = subject
                if subject_match in dictionary[uid]['SUBJECT']:
                    result, body = imap4.uid('fetch', uid, '(UID BODY[TEXT])')
                    if result != 'OK':
                        raise Exception('Can not retrieve "Body" from EMAIL: {}'.format(uid))
                    dictionary[uid]['MESSAGE BODY'] = body[0][1]
                    list_body = dictionary[uid]['MESSAGE BODY'].splitlines()
                    result, copy = imap4.uid('COPY', uid, destination_folder)
                    if result == 'OK':
                        dictionary[uid]['RESULT'] = 'COPIED'
                        result, delete = imap4.uid('STORE', uid, '+FLAGS', '(\Deleted)')
                        imap4.expunge()
                        if result == 'OK':
                            dictionary[uid]['RESULT'] = 'COPIED/DELETED'
                        elif result != 'OK':
                            dictionary[uid]['RESULT'] = 'ERROR'
                            continue
                    elif result != 'OK':
                        dictionary[uid]['RESULT'] = 'ERROR'
                        continue
                else:
                    print "Do something with not matching emails"
                    # do something else instead of copy
            dictionary = {scan_folder: dictionary}
    except imaplib.IMAP4.error as e:
        print("Error, {}".format(e))
    except Exception as e:
        print("Error, {}".format(e))
    finally:
        logout_process(imap4)
        return dictionary

if __name__ == "__main__":
    username = '[email protected]'
    password = 'examplePassword'
    main_dictionary = main(username, password, 'INBOX', 'BOKNING', 'TMP_FOLDER')
    pprint.pprint(main_dictionary)
    exit(0)

Useful information regarding imaplib Python — imaplib IMAP example with Gmailand the imaplib documentation.

有关 imaplib Python 的有用信息— imaplib IMAP 示例与 Gmailimaplib 文档