英文:
Connect to Gmail Using Email Address and Password with Python
问题 {#heading}
I am trying to connect to my Gmail account using Python. I want to connect to it using both SMTP and IMAP. I am aware that it is possible to use an app password to make this connection, but is there a way to use the actual email password instead?
我正在尝试使用Python连接到我的Gmail帐户。我希望能够同时使用SMTP和IMAP进行连接。我知道可以使用应用程序密码来建立连接,但是否有一种方法可以使用实际的电子邮件密码呢?
I have been reading this particular article,
我一直在阅读这篇特定的文章,
Do I have that right?
我理解得对吗?
Given below is the code I am using to send and search emails. Like I said though, this only works with an app password,
以下是我用来发送和搜索电子邮件的代码。不过,就像我之前说的,这仅适用于应用程序密码,
import smtplib
import imaplib
import email
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import pandas as pd
class EmailClient:
def __init__(self, email, password, smtp_server='smtp.gmail.com', smtp_port=587, imap_server="imap.gmail.com"):
self.email = email
self.password = password
self.smtp_server = smtplib.SMTP(smtp_server, smtp_port)
self.imap_server = imaplib.IMAP4_SSL(imap_server)
def send_email(self, to_addr, subject, body):
msg = MIMEMultipart()
msg['From'] = self.email
msg['To'] = to_addr
msg['Subject'] = subject
msg.attach(MIMEText(body, 'plain'))
self.smtp_server.starttls()
self.smtp_server.login(self.email, self.password)
self.smtp_server.send_message(msg)
self.smtp_server.quit()
def search_email(self, mailbox="INBOX", subject=None, to=None, from_=None, since_date=None, until_date=None,
since_emailid=None):
self.imap_server.login(self.email, self.password)
self.imap_server.select(mailbox)
query_parts = []
if subject is not None:
query_parts.append(f'(SUBJECT "{subject}")')
if to is not None:
query_parts.append(f'(TO "{to}")')
if from_ is not None:
query_parts.append(f'(FROM "{from_}")')
if since_date is not None:
since_date_str = since_date.strftime("%d-%b-%Y")
query_parts.append(f'(SINCE "{since_date_str}")')
if until_date is not None:
until_date_str = until_date.strftime("%d-%b-%Y")
query_parts.append(f'(BEFORE "{until_date_str}")')
if since_emailid is not None:
query_parts.append(f'(UID {since_emailid}:*)')
query = ' '.join(query_parts)
ret = []
resp, items = self.imap_server.uid('search', None, query)
items = items[0].split()
for emailid in items[::-1]:
resp, data = self.imap_server.uid('fetch', emailid, "(BODY[HEADER.FIELDS (SUBJECT TO FROM DATE)])")
try:
raw_email = data[0][1].decode("utf-8")
except UnicodeDecodeError:
ValueError(f"Could not decode email with id {emailid}")
email_message = email.message_from_string(raw_email)
email_line = {}
email_line['id'] = emailid
email_line["to"] = email_message['To']
email_line["from"] = email_message['From']
email_line["subject"] = str(email_message['Subject'])
email_line["created_at"] = email_message['Date']
resp, email_data = self.imap_server.uid('fetch', emailid, '(BODY[TEXT])')
email_line["body"] = email_data[0][1].decode('utf-8')
ret.append(email_line)
self.imap_server.logout()
return pd.DataFrame(ret)
英文:
I am trying to connect to my Gmail account using Python. I want to connect to it using both SMTP and IMAP. I am aware that it is possible to use an app password to make this connection, but is there a way to use the actual email password instead?
I have been reading this particular article,
<br>
https://support.google.com/accounts/answer/6010255#zippy=%2Cif-less-secure-app-access-is-on-for-your-account%2Cif-less-secure-app-access-is-off-for-your-account
The warning given at the top tells me that it is not possible to do so, even if 'Less secure app access' is allowed,
Do I have that right?
Given below is the code I am using to send and search emails. Like I said though, this only works with an app password,
import smtplib
import imaplib
import email
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import pandas as pd
class EmailClient:
def __init__(self, email, password, smtp_server='smtp.gmail.com', smtp_port=587, imap_server="imap.gmail.com"):
self.email = email
self.password = password
self.smtp_server = smtplib.SMTP(smtp_server, smtp_port)
self.imap_server = imaplib.IMAP4_SSL(imap_server)
def send_email(self, to_addr, subject, body):
msg = MIMEMultipart()
msg['From'] = self.email
msg['To'] = to_addr
msg['Subject'] = subject
msg.attach(MIMEText(body, 'plain'))
self.smtp_server.starttls()
self.smtp_server.login(self.email, self.password)
self.smtp_server.send_message(msg)
self.smtp_server.quit()
def search_email(self, mailbox="INBOX", subject=None, to=None, from_=None, since_date=None, until_date=None,
since_emailid=None):
self.imap_server.login(self.email, self.password)
self.imap_server.select(mailbox)
query_parts = []
if subject is not None:
query_parts.append(f'(SUBJECT "{subject}")')
if to is not None:
query_parts.append(f'(TO "{to}")')
if from_ is not None:
query_parts.append(f'(FROM "{from_}")')
if since_date is not None:
since_date_str = since_date.strftime("%d-%b-%Y")
query_parts.append(f'(SINCE "{since_date_str}")')
if until_date is not None:
until_date_str = until_date.strftime("%d-%b-%Y")
query_parts.append(f'(BEFORE "{until_date_str}")')
if since_emailid is not None:
query_parts.append(f'(UID {since_emailid}:*)')
query = ' '.join(query_parts)
ret = []
resp, items = self.imap_server.uid('search', None, query)
items = items[0].split()
for emailid in items[::-1]:
resp, data = self.imap_server.uid('fetch', emailid, "(BODY[HEADER.FIELDS (SUBJECT TO FROM DATE)])")
try:
raw_email = data[0][1].decode("utf-8")
except UnicodeDecodeError:
ValueError(f"Could not decode email with id {emailid}")
email_message = email.message_from_string(raw_email)
email_line = {}
email_line['id'] = emailid
email_line["to"] = email_message['To']
email_line["from"] = email_message['From']
email_line["subject"] = str(email_message['Subject'])
email_line["created_at"] = email_message['Date']
resp, email_data = self.imap_server.uid('fetch', emailid, '(BODY[TEXT])')
email_line["body"] = email_data[0][1].decode('utf-8')
ret.append(email_line)
self.imap_server.logout()
return pd.DataFrame(ret)
答案1 {#1}
得分: 1
"我知道可以使用应用程序密码进行连接,但是否可以使用实际的电子邮件密码呢?
不,不能这样做,您需要执行以下两种操作之一。
- 在帐户上启用两步验证并创建应用程序密码。
- 使用Xoauth2并请求用户授权以访问其帐户。
您的代码在我看来很好,只需创建一个应用程序密码。
Xoauth示例 {#xoauth}
from __future__ import print_function
import base64
import os.path
import smtplib
from email.mime.text import MIMEText
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
# 如果修改了这些范围,请删除token.json文件。
SCOPES = ['https://mail.google.com/']
# 用户令牌存储
USER_TOKENS = 'token.json'
# 应用程序凭据
CREDENTIALS = 'C:\YouTube\dev\credentials.json'
def getToken() -> str:
creds = None
# 文件token.json存储用户的访问和刷新令牌,并在首次授权流程完成时自动生成。
if os.path.exists(USER_TOKENS):
creds = Credentials.from_authorized_user_file(USER_TOKENS, SCOPES)
creds.refresh(Request())
# 如果没有(有效的)凭据可用,让用户登录。
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, SCOPES)
creds = flow.run_local_server(port=0)
# 保存凭据以供下次运行
with open(USER_TOKENS, 'w') as token:
token.write(creds.to_json())
return creds.token
def generate_oauth2_string(username, access_token) -> str:
auth_string = 'user=' + username + '\1auth=Bearer ' + access_token + '\1\1'
return base64.b64encode(auth_string.encode('ascii')).decode('ascii')
def send_email(host, port, subject, msg, sender, recipients):
access_token = getToken()
auth_string = generate_oauth2_string(sender, access_token)
msg = MIMEText(msg)
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = ', '.join(recipients)
server = smtplib.SMTP(host, port)
server.starttls()
server.docmd('AUTH', 'XOAUTH2 ' + auth_string)
server.sendmail(sender, recipients, msg.as_string())
server.quit()
def main():
host = "smtp.gmail.com"
port = 587
user = "REDACTED@gmail.com"
recipient = "REDACTED@gmail.com"
subject = "Test email Oauth2"
msg = "Hello world"
sender = user
recipients = [recipient]
send_email(host, port, subject, msg, sender, recipients)
if __name__ == '__main__':
main()
" 英文:
>I am aware that it is possible to use an app password to make this connection, but is there a way to use the actual email password instead?
No there is not, you need to do one of two things.
- enable 2fa on the account and create an apps password
- Use Xoauth2 and request authorization of the user to access their account.
Your code looks fine to me just crate an apps password.
Xoauth sample {#xoauth-sample}
from __future__ import print_function
import base64
import os.path
import smtplib
from email.mime.text import MIMEText
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://mail.google.com/']
# user token storage
USER_TOKENS = 'token.json'
# application credentials
CREDENTIALS = 'C:\YouTube\dev\credentials.json'
def getToken() -> str:
creds = None
# The file token.json 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(USER_TOKENS):
creds = Credentials.from_authorized_user_file(USER_TOKENS, SCOPES)
creds.refresh(Request())
# 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, SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open(USER_TOKENS, 'w') as token:
token.write(creds.to_json())
return creds.token
def generate_oauth2_string(username, access_token) -> str:
auth_string = 'user=' + username + '\1auth=Bearer ' + access_token + '\1\1'
return base64.b64encode(auth_string.encode('ascii')).decode('ascii')
def send_email(host, port, subject, msg, sender, recipients):
access_token = getToken()
auth_string = generate_oauth2_string(sender, access_token)
msg = MIMEText(msg)
msg['Subject'] = subject
msg['From'] = sender
msg['To'] = ', '.join(recipients)
server = smtplib.SMTP(host, port)
server.starttls()
server.docmd('AUTH', 'XOAUTH2 ' + auth_string)
server.sendmail(sender, recipients, msg.as_string())
server.quit()
def main():
host = "smtp.gmail.com"
port = 587
user = "REDACTED@gmail.com"
recipient = "REDACTED@gmail.com"
subject = "Test email Oauth2"
msg = "Hello world"
sender = user
recipients = [recipient]
send_email(host, port, subject, msg, sender, recipients)
if __name__ == '__main__':
main()