如何在Python中使用Gmail API?用法教程

2021年11月16日20:57:19 发表评论 3,748 次浏览

Python如何使用Gmail API?了解如何使用 Gmail API 发送电子邮件、通过查询搜索电子邮件、删除电子邮件、在 Python 中将电子邮件标记为已读或未读。

Gmail是目前最流行的邮件服务,个人和组织都在使用它。它的许多功能都通过人工智能得到了增强,包括其安全性(和欺诈性电子邮件的检测)以及撰写电子邮件时的建议。

在之前的教程中,我们解释了如何使用 Python发送电子邮件和阅读电子邮件,如果你还没有阅读它们,我强烈建议你查看它们。

Python Gmail API用法示例:虽然之前的教程是直接使用IMAP/SMTP协议,但在本教程中,我们将使用 Google 的 API 发送和阅读电子邮件,通过这样做,我们可以使用特定于 Google Mail 的功能,例如;为一些电子邮件添加标签,将电子邮件标记为未读/已读等。

如何在Python中使用Gmail API?在本指南中,我们将探索 Gmail API 的一些主要功能,我们将编写几个 Python 脚本,这些脚本能够发送电子邮件、搜索电子邮件、删除以及标记为已读或未读,它们将用作如下:

$ python send_emails.py destination@gmail.com "Subject" "Message body" --files file1.txt file2.pdf file3.png
$ python read_emails.py "search query"
$ python delete_emails.py "search query"
$ python mark_emails.py --read "search query"
$ python mark_emails.py --unread "search query"

这是目录:

  • 启用 Gmail API
  • 发送电子邮件
  • 搜索电子邮件
  • 阅读电子邮件
  • 将电子邮件标记为已读
  • 将电子邮件标记为未读
  • 删除电子邮件

首先,让我们安装必要的依赖项:

$ pip3 install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib

启用 Gmail API

Python如何使用Gmail API?要使用 Gmail API,我们需要一个令牌来连接到 Gmail 的 API,我们可以从Google API 的仪表板中获取

我们首先启用 Google 邮件 API,前往仪表板,使用搜索栏搜索 Gmail API,点击它,然后启用:

如何在Python中使用Gmail API?用法教程

然后,我们通过创建凭据(通过前往“创建凭据”按钮)来创建OAuth 2.0 客户端 ID :

如何在Python中使用Gmail API?用法教程选择桌面应用程序作为应用程序类型并继续,你会看到一个这样的窗口:

如何在Python中使用Gmail API?用法教程

我们下载我们的凭证文件并将其保存credentials.json在当前目录中:

如何在Python中使用Gmail API?用法教程

注意:如果这是你第一次使用 Google API,你可能只需要创建一个 OAuth 同意屏幕并将你的电子邮件添加为测试用户。

Python Gmail API用法示例:现在我们已经完成了 API 的设置,让我们从导入必要的模块开始:

import os
import pickle
# Gmail API utils
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
# for encoding/decoding messages in base64
from base64 import urlsafe_b64decode, urlsafe_b64encode
# for dealing with attachement MIME types
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from mimetypes import guess_type as guess_mime_type

# Request all access (permission to read/send/receive emails, manage the inbox, and more)
SCOPES = ['https://mail.google.com/']
our_email = 'your_gmail@gmail.com'

显然,你需要更改our_email你的地址,确保你使用创建 API 身份验证的电子邮件。首先,让我们创建一个函数来加载credentials.json,使用 Gmail API 进行身份验证并返回一个服务对象,该对象稍后可以在我们即将推出的函数中使用:

def gmail_authenticate():
    creds = None
    # the file token.pickle stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first time
    if os.path.exists("token.pickle"):
        with open("token.pickle", "rb") as token:
            creds = pickle.load(token)
    # if there are no (valid) credentials availablle, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # save the credentials for the next run
        with open("token.pickle", "wb") as token:
            pickle.dump(creds, token)
    return build('gmail', 'v1', credentials=creds)

# get the Gmail API service
service = gmail_authenticate()

如何在Python中使用Gmail API?如果你之前已经使用过谷歌 API,你应该很熟悉,比如谷歌驱动 API,它基本上是在浏览器中通过谷歌验证后读取credentials.json并将其保存到token.pickle文件中,我们保存令牌,以便我们第二次运行代码我们不应该再次进行身份验证。

这将提示你在默认浏览器中接受此应用程序所需的权限,如果你看到一个表明该应用程序未经验证的窗口,你可能只想前往高级并单击转到Gmail API Python(不安全)

如何在Python中使用Gmail API?用法教程

发送电子邮件

Python如何使用Gmail API?首先,让我们从发送电子邮件的函数开始,我们知道电子邮件可以包含附件,因此我们将定义一个向消息添加附件的函数,消息是MIMEMultipart(或者MIMEText,如果不包含附件)的实例:

# Adds the attachment with the given filename to the given message
def add_attachment(message, filename):
    content_type, encoding = guess_mime_type(filename)
    if content_type is None or encoding is not None:
        content_type = 'application/octet-stream'
    main_type, sub_type = content_type.split('/', 1)
    if main_type == 'text':
        fp = open(filename, 'rb')
        msg = MIMEText(fp.read().decode(), _subtype=sub_type)
        fp.close()
    elif main_type == 'image':
        fp = open(filename, 'rb')
        msg = MIMEImage(fp.read(), _subtype=sub_type)
        fp.close()
    elif main_type == 'audio':
        fp = open(filename, 'rb')
        msg = MIMEAudio(fp.read(), _subtype=sub_type)
        fp.close()
    else:
        fp = open(filename, 'rb')
        msg = MIMEBase(main_type, sub_type)
        msg.set_payload(fp.read())
        fp.close()
    filename = os.path.basename(filename)
    msg.add_header('Content-Disposition', 'attachment', filename=filename)
    message.attach(msg)

复制其次,我们编写了一个函数,该函数接受一些消息参数、构建并返回电子邮件消息:

def build_message(destination, obj, body, attachments=[]):
    if not attachments: # no attachments given
        message = MIMEText(body)
        message['to'] = destination
        message['from'] = our_email
        message['subject'] = obj
    else:
        message = MIMEMultipart()
        message['to'] = destination
        message['from'] = our_email
        message['subject'] = obj
        message.attach(MIMEText(body))
        for filename in attachments:
            add_attachment(message, filename)
    return {'raw': urlsafe_b64encode(message.as_bytes()).decode()}

最后,我们创建了一个接受消息参数的函数,使用 Google 邮件 API 发送一个由build_message()我们之前定义的构造的消息:

def send_message(service, destination, obj, body, attachments=[]):
    return service.users().messages().send(
      userId="me",
      body=build_message(destination, obj, body, attachments)
    ).execute()

这就是发送消息。让我们使用该函数发送示例电子邮件:

# test send email
send_message(service, "destination@domain.com", "This is a subject", 
            "This is the body of the email", ["test.txt", "anyfile.png"])

将你的电子邮件作为目标地址和文件的真实路径,你将看到消息确实已发送!

还学习:如何使用 smtplib 在 Python 中发送电子邮件。

搜索电子邮件

def search_messages(service, query):
    result = service.users().messages().list(userId='me',q=query).execute()
    messages = [ ]
    if 'messages' in result:
        messages.extend(result['messages'])
    while 'nextPageToken' in result:
        page_token = result['nextPageToken']
        result = service.users().messages().list(userId='me',q=query, pageToken=page_token).execute()
        if 'messages' in result:
            messages.extend(result['messages'])
    return messages

我们必须逐页检索消息,因为它们是分页的。此函数将返回与查询匹配的电子邮件的 ID,我们将使用它进行删除、标记为已读、标记为未读和搜索功能。

阅读电子邮件

如何在Python中使用Gmail API?在本节中,我们将编写 Python 代码,将搜索查询作为输入并读取所有匹配的电子邮件;打印电子邮件基本信息(ToFrom地址SubjectDate)和plain/text部分。

我们还将根据主题和下载text/html内容以及附加到电子邮件的任何文件为每封电子邮件创建一个文件夹,并将其保存在创建的文件夹中。

在我们深入研究根据搜索查询读取电子邮件的函数之前,我们将定义两个将使用的实用函数:

# utility functions
def get_size_format(b, factor=1024, suffix="B"):
    """
    Scale bytes to its proper byte format
    e.g:
        1253656 => '1.20MB'
        1253656678 => '1.17GB'
    """
    for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
        if b < factor:
            return f"{b:.2f}{unit}{suffix}"
        b /= factor
    return f"{b:.2f}Y{suffix}"


def clean(text):
    # clean text for creating a folder
    return "".join(c if c.isalnum() else "_" for c in text)

Python Gmail API用法示例:该get_size_format()函数将仅以一种很好的格式打印字节(从本教程中获取),我们将需要该clean()函数来创建一个不包含空格和特殊字符的文件夹名称。

接下来,让我们定义一个解析电子邮件分区内容的函数:

def parse_parts(service, parts, folder_name, message):
    """
    Utility function that parses the content of an email partition
    """
    if parts:
        for part in parts:
            filename = part.get("filename")
            mimeType = part.get("mimeType")
            body = part.get("body")
            data = body.get("data")
            file_size = body.get("size")
            part_headers = part.get("headers")
            if part.get("parts"):
                # recursively call this function when we see that a part
                # has parts inside
                parse_parts(service, part.get("parts"), folder_name, message)
            if mimeType == "text/plain":
                # if the email part is text plain
                if data:
                    text = urlsafe_b64decode(data).decode()
                    print(text)
            elif mimeType == "text/html":
                # if the email part is an HTML content
                # save the HTML file and optionally open it in the browser
                if not filename:
                    filename = "index.html"
                filepath = os.path.join(folder_name, filename)
                print("Saving HTML to", filepath)
                with open(filepath, "wb") as f:
                    f.write(urlsafe_b64decode(data))
            else:
                # attachment other than a plain text or HTML
                for part_header in part_headers:
                    part_header_name = part_header.get("name")
                    part_header_value = part_header.get("value")
                    if part_header_name == "Content-Disposition":
                        if "attachment" in part_header_value:
                            # we get the attachment ID 
                            # and make another request to get the attachment itself
                            print("Saving the file:", filename, "size:", get_size_format(file_size))
                            attachment_id = body.get("attachmentId")
                            attachment = service.users().messages() \
                                        .attachments().get(id=attachment_id, userId='me', messageId=message['id']).execute()
                            data = attachment.get("data")
                            filepath = os.path.join(folder_name, filename)
                            if data:
                                with open(filepath, "wb") as f:
                                    f.write(urlsafe_b64decode(data))

现在,让我们编写用于阅读电子邮件的主要函数:

def read_message(service, message):
    """
    This function takes Gmail API `service` and the given `message_id` and does the following:
        - Downloads the content of the email
        - Prints email basic information (To, From, Subject & Date) and plain/text parts
        - Creates a folder for each email based on the subject
        - Downloads text/html content (if available) and saves it under the folder created as index.html
        - Downloads any file that is attached to the email and saves it in the folder created
    """
    msg = service.users().messages().get(userId='me', id=message['id'], format='full').execute()
    # parts can be the message body, or attachments
    payload = msg['payload']
    headers = payload.get("headers")
    parts = payload.get("parts")
    folder_name = "email"
    has_subject = False
    if headers:
        # this section prints email basic info & creates a folder for the email
        for header in headers:
            name = header.get("name")
            value = header.get("value")
            if name.lower() == 'from':
                # we print the From address
                print("From:", value)
            if name.lower() == "to":
                # we print the To address
                print("To:", value)
            if name.lower() == "subject":
                # make our boolean True, the email has "subject"
                has_subject = True
                # make a directory with the name of the subject
                folder_name = clean(value)
                # we will also handle emails with the same subject name
                folder_counter = 0
                while os.path.isdir(folder_name):
                    folder_counter += 1
                    # we have the same folder name, add a number next to it
                    if folder_name[-1].isdigit() and folder_name[-2] == "_":
                        folder_name = f"{folder_name[:-2]}_{folder_counter}"
                    elif folder_name[-2:].isdigit() and folder_name[-3] == "_":
                        folder_name = f"{folder_name[:-3]}_{folder_counter}"
                    else:
                        folder_name = f"{folder_name}_{folder_counter}"
                os.mkdir(folder_name)
                print("Subject:", value)
            if name.lower() == "date":
                # we print the date when the message was sent
                print("Date:", value)
    if not has_subject:
        # if the email does not have a subject, then make a folder with "email" name
        # since folders are created based on subjects
        if not os.path.isdir(folder_name):
            os.mkdir(folder_name)
    parse_parts(service, parts, folder_name, message)
    print("="*50)

Python如何使用Gmail API?由于先前定义的函数search_messages()返回匹配电子邮件的 ID 列表,因此read_message()下载电子邮件的内容并执行上面已经提到的操作。

read_message()函数用于parse_parts()解析不同的电子邮件分区,如果是text/plain,则我们只需将其解码并将其打印到屏幕上,如果是text/html,则我们只需将其保存在使用名称创建的文件夹中index.html,如果是文件(附件) ,然后我们通过它下载附件attachment_id并将其保存在创建的文件夹下。

此外,如果两封电子邮件具有相同的Subject,那么我们需要为文件夹的名称添加一个简单的计数器,这就是我们对folder_counter.

让我们在行动中使用它:

# get emails that match the query you specify
results = search_messages(service, "Python Code")
# for each email matched, read it (output plain/text to console & save HTML and attachments)
for msg in results:
    read_message(service, msg)

这将下载并解析所有包含 Python Code 关键字的电子邮件,这是输出的一部分:

==================================================
From: Python Code <email@domain.com>
To: "email@gmail.com" <email@gmail.com>
Subject: How to Play and Record Audio in Python
Date: Fri, 21 Feb 2020 09:24:58 +0000

Hello !

I have no doubt that you already encountered with an application that uses sound (either recording or playing) and you know how useful is that !
<...SNIPPED..>

Saving HTML to How_to_Play_and_Record_Audio_in_Python\index.html
==================================================
From: Python Code <email@domain.com>
To: "email@gmail.com" <email@gmail.com>
Subject: Brute-Forcing FTP Servers in Python
Date: Tue, 25 Feb 2020 21:31:09 +0000‌ ‌ ‌ ‌  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌

Hello,
A brute-force attack consists of an attack that submits many passwords with the hope of guessing correctly.
<...SNIPPED...>

Saving HTML to Brute_Forcing_FTP_Servers_in_Python_1\index.html
==================================================
<...SNIPPED...>

你还将看到在当前目录中为匹配的每封电子邮件创建的文件夹:

如何在Python中使用Gmail API?用法教程

在每个文件夹中,它都有相应的电子邮件 HTML 版本,以及任何可用的附件。

相关:如何使用 imaplib 在 Python 中阅读电子邮件。

将电子邮件标记为已读

def mark_as_read(service, query):
    messages_to_mark = search_messages(service, query)
    return service.users().messages().batchModify(
      userId='me',
      body={
          'ids': [ msg['id'] for msg in messages_to_mark ],
          'removeLabelIds': ['UNREAD']
      }
    ).execute()

我们使用 batchModify() 方法并在参数中设置 removeLabelIds 为 从匹配的电子邮件中删除未读标签。["UNREAD"]body

例如,让我们将所有 Google 电子邮件标记为已读:

mark_as_read(service, "Google")

将电子邮件标记为未读

如何在Python中使用Gmail API?可以以类似的方式将消息标记为未读,这次是通过添加标签["UNREAD"]

def mark_as_unread(service, query):
    messages_to_mark = search_messages(service, query)
    # add the label UNREAD to each of the search results
    return service.users().messages().batchModify(
        userId='me',
        body={
            'ids': [ msg['id'] for msg in messages_to_mark ],
            'addLabelIds': ['UNREAD']
        }
    ).execute()

示例运行:

# search query by sender/receiver
mark_as_unread(service, "email@domain.com")

Python Gmail API用法示例:删除电子邮件

现在,对于删除消息功能:

def delete_messages(service, query):
    messages_to_delete = search_messages(service, query)
    # it's possible to delete a single message with the delete API, like this:
    # service.users().messages().delete(userId='me', id=msg['id'])
    # but it's also possible to delete all the selected messages with one query, batchDelete
    return service.users().messages().batchDelete(
      userId='me',
      body={
          'ids': [ msg['id'] for msg in messages_to_delete]
      }
    ).execute()

这次我们使用的batchDelete()方法是删除所有匹配的邮件,比如从谷歌快讯中删除所有邮件:

delete_messages(service, "Google Alerts")

相关:如何使用 imaplib 在 Python 中删除电子邮件。

结论

Gmail 查询支持过滤器,可用于选择特定邮件,其中一些过滤器如下所示,这是搜索电子邮件时显示的对话框,我们可以填写它,并获得相应的搜索查询:

如何在Python中使用Gmail API?用法教程

Python如何使用Gmail API?Gmail 不仅提供出色且友好的用户界面,为要求苛刻的用户提供许多功能,而且还提供强大的 API 供开发人员使用 Gmail 并与之交互,我们得出结论,以编程方式处理来自 Google 邮件的电子邮件非常简单。

如果你想了解有关 API 的更多信息,我建议你查看官方 Gmail API 页面

最后,我为我们在本教程中完成的每个任务创建了 Python 脚本,请查看此页面以获取完整代码。

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: