Python自动发邮件以及打包报错failed to execute script和DLL load failed while importing win32api: 找不到指定的模块
前言
就是新入职老是忘了发每天的日报邮件,然后刚好也不太忙,就想写个自动发邮件的功能来解放一下自己的脑子。
本来想用C#和Unity做这功能的,但因为受到网上这些吹python的人影响。就试了一下。
准备工作
1:安装python。(版本3.8.5)
2:安装PyCharm。(版本2020.1.4)
安装教程链接
没什么好说的,网上都是教程。
需求整理以及执行方案
需求大概就是,定时发送。工作文档作为附件发送。发送和抄送指定人。
原本我想用foxmail的邮件模板来自动修改内容和定时发送。结果只能用python打开这个程序。
然后搜了一下发现python本来就有这个功能。怎么说呢?大概就是真香的感觉吧。
python发邮件教程
结果
然后就把教程的文件修改的符合自己的需求。原本是定时发邮件,但是感觉不到过程,就是那种看不到就觉得没有的感觉。于是,又做了一个窗口。显示当前时间,到指定时间窗口会显示发送邮件,并且会在发送成功之后显示成功然后关闭窗口。
# coding:utf-8
import smtplib
from email.mime.text import MIMEText
import time
import datetime
import tkinter
import pywintypes #解决DLL load failed while importing win32api: 找不到指定的模块
from email.mime.multipart import MIMEMultipart
from email.header import Header
from email import encoders
from email.mime.base import MIMEBase
from email.utils import parseaddr, formataddr
from email.mime.image import MIMEImage
from win32api import GetSystemMetrics
from win32con import SM_CMONITORS, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN
def SendMail(sender, receivers, cc_mail, mail_pass, content, file):
# 第三方 SMTP 服务
mail_host = "smtp.exmail.qq.com" # 设置服务器
# message = MIMEText(content, 'plain', 'utf-8')#正文内容 plain代表纯文本
# 构造一个MIMEMultipart对象代表邮件本身
message = MIMEMultipart()
message.attach(MIMEText(content, 'html', 'utf-8')) # 正文内容 plain代表纯文本,html代表支持html文本
message['From'] = formataddr(["名字",sender]) # 括号里的对应发件人邮箱昵称、发件人邮箱账号
message['To'] = ','.join(receivers) # 与真正的收件人的邮箱不是一回事
message['Cc'] = ','.join(cc_mail)
subject = '名字-%s工作日志' % str(datetime.date.today()).replace('-','')
message['Subject'] = subject # 邮件标题
# 添加文件到附件
with open(file, 'rb') as f:
# MIMEBase表示附件的对象
mime = MIMEBase('名字-工作内容表.xls', 'xls')
# filename是显示附件名字
mime.add_header('Content-Disposition', 'attachment',filename="名字-工作内容表.xls")
# 获取附件内容
mime.set_payload(f.read())
encoders.encode_base64(mime)
# 作为附件添加到邮件
message.attach(mime)
try:
smtpObj = smtplib.SMTP_SSL(mail_host, 465)
smtpObj.login(sender, mail_pass)
smtpObj.sendmail(sender, receivers + cc_mail, str(message)) # message.as_string()
smtpObj.quit()
print ("邮件发送成功")
except smtplib.SMTPException as e:
print("邮件发送失败Error: %s" % e)
def gettime():
# 获取当前时间并转为字符串
time_now = time.strftime("%H:%M:%S", time.localtime())
# 重新设置标签文本
l.configure(text=time_now)
# 每隔一秒调用函数gettime自身获取时间
if time_now >= "19:25:00": # 此处设置每天定时的时间
l.configure(text="开始发送邮件")
window.update()
sender = 'aaa@qq.com' # 用户名与发送方
receivers = ['aaa@qq.com', 'aaa@qq.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
cc_mail = ['aaa@qq.com'] # 抄送人
# 口令,QQ邮箱是输入授权码,在qq邮箱设置 里用验证过的手机发送短信获得,不含空格
mail_pass = "XXXXXXXXXXXX"
tempText = '发送工作日志%s' % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
content = tempText;# 邮件文字内容
file = r'C:\Users\ZGM\Desktop\work\名字-工作内容表.xls' # 发送文件路径
# SendMail(sender, receivers, cc_mail, mail_pass, content, file)
l.configure(text="发送邮件成功")
window.update()
time.sleep(3)
window.quit()
else:
window.after(1000, gettime)
window = tkinter.Tk()
# 第2步,给窗口的可视化起名字
window.title('自动邮件')
# 第3步,设定窗口的大小(长 * 宽) 最右上面的位置
window.update()
window.geometry('+%s+1'% (GetSystemMetrics(SM_CXVIRTUALSCREEN)-window.winfo_width()-100)) # 这里的乘是小x
# 第4步,在图形界面上设定标签
l = tkinter.Label(window, text= "", bg='green', font=('arial', 12), width=30, height=2)
# 说明: bg为背景,font为字体,width为长,height为高,这里的长和高是字符的长和高,比如height=2,就是标签有2个字符这么高
# 第5步,放置标签
l.pack() # Label内容content区域放置位置,自动调节尺寸
# 放置lable的方法有:1)l.pack(); 2)l.place();
window.after(1000, gettime)
# 第6步,主窗口循环显示
window.mainloop()
print("结束进程")
# while True: #不使用计时窗口的话可以使用下面的代码。
# time_now = time.strftime("%H:%M:%S", time.localtime()) # 刷新
# if time_now >= "19:30:00": #此处设置每天定时的时间
# sender = 'aaa@qq.com' # 用户名与发送方
# receivers = ['aaa@qq.com', 'aaa@qq.com'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱
# cc_mail = ['aaa@qq.com'] # 抄送人
# # 口令,QQ邮箱是输入授权码,在qq邮箱设置 里用验证过的手机发送短信获得,不含空格
# mail_pass = "xxxxxxxxx"
#
# tempText = '发送工作日志%s' % time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
# content = tempText;
# file = r'C:\Users\ZGM\Desktop\work\名字-工作内容表.xls' # 发送文件路径
# SendMail(sender, receivers, cc_mail, mail_pass, content, file)
# break;
# time.sleep(5) # 因为以秒定时,所以暂停5秒,使之不会在1秒内执行多次
发送结果如下
提示窗口
python代码部分的坑总结
1:比较坑的就是Python的代码缩进问题。缩进问题会导致运行顺序的不一致。而且缩进有时候不报错。
2:tkinter窗口设定位置。我想要设置成右上角,但通过winfo_screenwidth()获取到是单个屏幕的宽高,双屏幕的要通过GetSystemMetrics(SM_CXVIRTUALSCREEN)这种获取。
以上代码部分结束。测试都没有问题。
那么还有个需求,就是我该如何定时运行它呢。
定时运行参考方案
然后因为要设置的参数竟然高达三个,我是不能忍,那么能不能直接把脚本生成Window可执行程序呢。
pyinstaller 打包Python为.exe
打包教程太多,不赘述了。
参考文档
需要注意的坑是:打包时的路径是你要打包的python文件夹的路径。
exe运行错误failed to execute script
本来在测试打包exe的时候已经没有什么问题了。但后续为了不影响美观我特意把提示窗口的位置移到了右上角。于是又添加了引用改动了代码把窗口移动到了右上角。
然后就打出的exe运行就会弹出failed to execute script的窗口。
看了比较多的文档,大多的描述都是文字里出现中文或者引用资源要用绝对路径,或者打包的时候不要加-w 。
然而我检查了代码之后并没有发现什么问题。但因为我打exe的时候用了pyinstaller -F -w xxxx.py
所以我就把-w移除打包运行。
然后出现了一个一闪而过的窗口。我多次尝试截到下图。
DLL load failed while importing win32api: 找不到指定的模块
大概这个才是failed to execute script的真正原因。
又是一系列的踩坑。
比如重装win32api。
使用-p增加引用不到的模块路径。然后,仍然找不到这俩个东西。
最终找到的结果是PyWin32和Python 3.8.0之间的Bug。
解决方案
就是这句代码
import pywintypes #解决DLL load failed while importing win32api: 找不到指定的模块
加上这句之后打包的exe。运行无误。
计划任务也不需要指定更多参数了。
以上。