如何在不启用“不安全访问”的情况下通过 gmail 发送电子邮件?

新手上路,请多包涵

Google 正在推动我们提高脚本访问其 gmail smtp 服务器的安全性。对于那件事我没有任何疑问。事实上,我很乐意提供帮助。

但他们并没有让它变得容易。建议我们 Upgrade to a more secure app that uses the most up to date security measures 很好,但这并不能帮助我弄清楚如何升级如下所示的代码:

 server = smtplib.SMTP("smtp.gmail.com", 587)
server.ehlo()
server.starttls()
server.login(GMAIL_USER, GMAIL_PASSWORD)
server.sendmail(FROM, TO, MESSAGE)
server.close()

当然,我会打开“访问安全性较低的应用程序”,但如果有人想出用什么替换此代码,我将不胜感激。

原文由 John Mee 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 856
2 个回答

Python 3 和 GMail 当前 API 的更新示例如下。

请注意,要获取下面的 credentials.json 文件,您需要在选择相关 GCP 项目后 在此处 创建一个 Oauth 客户端 ID 凭证。创建它后,您将看到客户端密钥和客户端机密。关闭该提示,然后单击帐户旁边的向下箭头。这是您需要的文件。

在此处输入图像描述

 import base64
import logging
import mimetypes
import os
import os.path
import pickle
from email.mime.text import MIMEText
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient import errors
from googleapiclient.discovery import build

def get_service():
    """Gets an authorized Gmail API service instance.

    Returns:
        An authorized Gmail API service instance..
    """

    # If modifying these scopes, delete the file token.pickle.
    SCOPES = [
        'https://www.googleapis.com/auth/gmail.readonly',
        'https://www.googleapis.com/auth/gmail.send',
    ]

    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 available, 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)

    service = build('gmail', 'v1', credentials=creds)
    return service

def send_message(service, sender, message):
  """Send an email message.

  Args:
    service: Authorized Gmail API service instance.
    user_id: User's email address. The special value "me"
    can be used to indicate the authenticated user.
    message: Message to be sent.

  Returns:
    Sent Message.
  """
  try:
    sent_message = (service.users().messages().send(userId=sender, body=message)
               .execute())
    logging.info('Message Id: %s', sent_message['id'])
    return sent_message
  except errors.HttpError as error:
    logging.error('An HTTP error occurred: %s', error)

def create_message(sender, to, subject, message_text):
  """Create a message for an email.

  Args:
    sender: Email address of the sender.
    to: Email address of the receiver.
    subject: The subject of the email message.
    message_text: The text of the email message.

  Returns:
    An object containing a base64url encoded email object.
  """
  message = MIMEText(message_text)
  message['to'] = to
  message['from'] = sender
  message['subject'] = subject
  s = message.as_string()
  b = base64.urlsafe_b64encode(s.encode('utf-8'))
  return {'raw': b.decode('utf-8')}

if __name__ == '__main__':
    logging.basicConfig(
        format="[%(levelname)s] %(message)s",
        level=logging.INFO
    )

    try:
        service = get_service()
        message = create_message("from@gmail.com", "to@gmail.com", "Test subject", "Test body")
        send_message(service, "from@gmail.com", message)

    except Exception as e:
        logging.error(e)
        raise

原文由 Steve Gore 发布,翻译遵循 CC BY-SA 4.0 许可协议

这很痛苦,但我现在似乎有一些事情要做……

不支持 Python3(目前)

我认为这不会太难实现,因为我在转换包时跌跌撞撞,没有碰到任何大的东西:只是通常的 2to3 东西。然而几个小时后,我厌倦了逆流而上。在撰写本文时,我找不到用于 Python 3 的公共消费的已发布包。Python 2 的体验是直接的(相比之下)。

浏览 Google 网站是成功的一半

毫无疑问,随着时间的推移,这将会改变。最后你需要下载一个 client_secret.json 文件。您只能(可能)通过网络浏览器进行设置:

  1. 您需要一个 google 帐户 - google apps 或 gmail。所以,如果你没有,那就去买一个吧。
  2. 让自己进入 开发者控制台
  3. 创建一个新项目,等待 4 或 400 秒完成。
  4. 导航至 API's and Auth -> Credentials
  5. OAuth 下选择 Create New Client ID
  6. 选择 Installed Application 作为应用程序类型和 其他
  7. 您现在应该有一个按钮 Download JSON 。去做。这是你的 client_secret.json 可以这么说

但等等,这还不是全部!

你必须给你的应用程序一个“产品名称”以避免一些奇怪的错误。 (看看我受了多少苦才给你这个 ;-)

  1. 导航至 API's & auth -> Consent Screen
  2. 选择您的电子邮件
  3. 输入产品名称。不管它是什么。 “Foobar”会做的很好。

快讯!哇。现在还有更多!

  1. 导航到 API 的 & 身份验证 -> API -> Gmail API
  2. 单击按钮启用 API

好极了。现在我们可以更新电子邮件脚本了。

蟒蛇2

您需要第一次以交互方式运行脚本。它将在您的计算机上打开一个网络浏览器,您将授予权限(点击一个按钮)。本练习会将一个文件保存到您的计算机 gmail.storage 其中包含一个可重复使用的令牌。

[我没有运气将令牌转移到没有图形浏览器功能的机器——返回 HTTPError。我试图通过 lynx 图形浏览器来完成它。这也失败了,因为谷歌将最终的“接受”按钮设置为“禁用”!?我会提出另一个问题来跳过这个障碍(更多抱怨)]

首先你需要一些库:

 pip install --upgrade google-api-python-client
pip install --upgrade python-gflags
  • 您需要更改往返地址
  • 确保在 Storage 说明期望的任何地方都有 client_token.json 文件
  • 该目录需要可写,以便它可以保存 gmail.storage 文件

最后一些代码:

 import base64
import httplib2

from email.mime.text import MIMEText

from apiclient.discovery import build
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import run

# Path to the client_secret.json file downloaded from the Developer Console
CLIENT_SECRET_FILE = 'client_secret.json'

# Check https://developers.google.com/gmail/api/auth/scopes for all available scopes
OAUTH_SCOPE = 'https://www.googleapis.com/auth/gmail.compose'

# Location of the credentials storage file
STORAGE = Storage('gmail.storage')

# Start the OAuth flow to retrieve credentials
flow = flow_from_clientsecrets(CLIENT_SECRET_FILE, scope=OAUTH_SCOPE)
http = httplib2.Http()

# Try to retrieve credentials from storage or run the flow to generate them
credentials = STORAGE.get()
if credentials is None or credentials.invalid:
  credentials = run(flow, STORAGE, http=http)

# Authorize the httplib2.Http object with our credentials
http = credentials.authorize(http)

# Build the Gmail service from discovery
gmail_service = build('gmail', 'v1', http=http)

# create a message to send
message = MIMEText("Message goes here.")
message['to'] = "yourvictim@goes.here"
message['from'] = "you@go.here"
message['subject'] = "your subject goes here"
body = {'raw': base64.b64encode(message.as_string())}

# send it
try:
  message = (gmail_service.users().messages().send(userId="me", body=body).execute())
  print('Message Id: %s' % message['id'])
  print(message)
except Exception as error:
  print('An error occurred: %s' % error)

希望这能让我们所有人都开始。不像旧方法那么简单,但现在我可以亲眼看到它确实看起来不那么复杂。

原文由 John Mee 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题