有没有方法可以防止SMTP连接超时?smtplib, python

10
我有一个需要无限运行的脚本。该脚本设置为每天向我发送完成某些步骤的确认邮件。我正在尝试使用smtplib来实现此功能。
初始连接设置为在脚本的最初阶段使用getpass输入我的登录名(写入脚本中)和密码。我不想将我的密码写入脚本中,甚至不想在脚本中引用密码,比如在配置文件中。因此,我希望在初始化时输入密码并保留smtp连接。
在脚本中根据需要重新连接smtp连接会破坏完全离开脚本并无限运行的目的。
我目前使用的示例代码如下:
import smtplib
import getpass

smtpObj = smtplib.SMTP('smtp.gmail.com',587)
smtpObj.ehlo()
smtpObj.starttls()
smtpObj.login('myemail@gmail.com',password = getpass.getpass('Enter Password: '))

然后我输入密码,输出结果为:
(235, b'2.7.0 Accepted')

所以这一切都很顺利。

问题在于,脚本需要暂停一段时间,从几分钟到可能几天不等。这是通过一个while循环和时间条件实现的,直到某个时间调用发送函数为止:

smtpObj.sendmail('myemail@gmail.com','recipient@gmail.com','This is a test')

然而,经过约20/30分钟的一段时间后(即如果暂停足够长),似乎会出现问题。然后,由于超时错误,smptObj.sendmail调用将失败。
具体错误如下:
SMTPSenderRefused: (451, b'4.4.2 Timeout - closing connection. l22sm2469172wre.52 - gsmtp', 'myemail@gmail.com')

我迄今为止尝试了以下操作:
使用以下超时参数化实例化连接对象:
smtpObj = smtplib.SMTP('smtp.gmail.com',587,timeout=None)
smtpObj = smtplib.SMTP('smtp.gmail.com',587,timeout=86400)

这两种方法似乎都不能解决连接的“超时”问题(即相同的问题仍然存在)。

我也尝试了这篇帖子中建议的解决方案:

如何使用smtplib和Python保持SMTP连接开放?

然而,这个方法也没有起作用!

我想避免每次发送电子邮件时都必须重新连接的解决方案,因为我只想手动输入一次连接的密码,而不是直接或间接地将其编写到脚本中。

肯定有一种方法来处理超时问题!如果有人能在这里提供帮助,请告诉我!不过,如果您认为在脚本需要发送电子邮件之前重新连接的“更明显”的解决方案是更好的方法,请让我知道。

谢谢!...

1个回答

10
如果不想在脚本中包含敏感凭证,应该使用环境变量。
从终端 shell(而不是 Python 中):
$ EXPORT secretVariable=mySecretValue
$ echo $secretVariable
$ mySecretValue
$

所以要在您的代码中利用这个...
>>> import os
>>> myPW = os.getenv('secretVariable')
>>> myPW
'mySecretVal'
>>>

通过这样做,您无需手动输入密码。除此之外,试图在潜在的几天内保持闲置的SMTP连接并不十分实用,只需要实现一个try/except结构即可。
import smtplib
import os

def smtp_connect():
    # Instantiate a connection object...
    password = os.getenv('secretVariable')
    smtpObj = smtplib.SMTP('smtp.gmail.com',587)
    smtpObj.ehlo()
    smtpObj.starttls()
    smtpObj.login('myemail@gmail.com',password=password)
    return smtpObj

def smtp_operations():
    try:
        # SMTP lib operations...
        smtpObj.sendmail('myemail@gmail.com','recipient@gmail.com','This is a test')
        # SMTP lib operations...

    except Exception: # replace this with the appropriate SMTPLib exception

        # Overwrite the stale connection object with a new one
        # Then, re-attempt the smtp_operations() method (now that you have a fresh connection object instantiated).
        smtpObj = smtp_connect() 
        smtp_operations()

smtpObj = smtp_connect()
smtp_operations()

通过将except Exception替换为当连接过期时引发的实际SMTP Exception,您可以确保不捕获与过期连接无关的异常。

因此,使用try/except,脚本将尝试执行SMTP操作。如果连接过期,则会实例化一个新的连接对象,然后使用新的连接对象尝试重新执行自身。


感谢您的迅速回复。我应该补充说明,脚本将在云实例上运行,我希望避免将密码存储在云实例上。我认为,使用环境变量并在代码中引用它们是您建议所需的。在这种情况下,即使密码不在代码中,它仍将与代码一起存储。 - agftrading
try / except 的解释很有帮助。已点赞,但声望还不够!谢谢。 - agftrading

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接