目录
一、需求背景
二、需求分析
三、需求实现
3.1 python邮箱附件指定时间下载
3.2 python外部传参
3.3 C#界面设计
四、功能实现
4.1 邮箱设置
4.2 邮件附件下载实现
1、依赖的模块
2、核心代码
3、其他
4、异常处理
5、python代码调用测试
4.3 C#内部代码实现
1、保存路径中“选择路径”
2、运行结束后清空控件中输入的数据
3、各控件内容判断和处理
4、C#调用外部多参数的exe
五、问题或求教
5.1 超大附件无法下载问题
5.2 C#调用exe代码中的弊端
5.3 C#程序运行依赖.NET环境
六、结语
参考:
一、需求背景
由于女朋友做助教,每周需要通过邮箱收数学作业,一个班五十几号人给她发送邮件,一个一个的点击下载非常耗时。所以想使用程序实现一下附件下载功能。
根据网上相关资料,使用python实现邮箱附件批量下载,然后打包生成exe后,虽是脱离了python环境便于她使用,可对于她们这些没有接触编程的人员来说,cmd命令行调用exe的操作不便于理解和操作。为此,界面化程序开发选择了C#,从而实现了C#调用python开发的exe来完成邮箱附件批量下载功能。
二、需求分析
1、指定时间的附件下载:因为作业的收发是每周都要进行的,不可每次邮件全部下载,需要指定起始和终止日期来接收一段时间内的邮箱附件;
2、基于界面化的操作,特别涉及邮箱登录等用户名、密码等信息,需要外部参数传参操作,即python核心代码中的参数输入需要留下外部接口;
3、C#界面开发,需要提供基础的参数入口:邮箱类型、邮箱账号、密码、保存路径、起止时间;
三、需求实现
3.1 python邮箱附件指定时间下载
该部分需要在邮件遍历过程中,设置起止时间根据邮件的接收时间来判断是否下载当前邮件内附件(参考[1]):
# 解析邮件: msg = Parser().parsestr(msg_content) # 获取邮件日期,格式化收件时间 date = time.strptime(msg.get('Date')[:24], '%a, %d %b %Y %H:%M:%S') date = time.strftime('%Y%m%d', date)#邮件时间格式转换 # 只下载发送日期在start_date 到end_date 之间的邮件附件 if date < start_date : break if date > end_date : continue
3.2 python外部传参
结合需求3,需要在邮件附件下载代码中实现外部参数的传入,传入数量为邮箱账号、密码、保存路径、起止时间和邮箱类型(参考[2]):
import sys #------------------------------------------------------------------ arg1 = sys.argv[1] arg2 = sys.argv[2] arg3 = sys.argv[3] arg4 = sys.argv[4] arg5 = sys.argv[5] arg6 = sys.argv[6] host = "pop."+arg6 #163.com" #host = "pop.163.com" # 邮箱域名,其他邮箱类似 server = poplib.POP3(host) # 建立链接 username = arg1 # 用户名#"xxxxxxx@163.com" password = arg2 # 密码IMAP/SMTP的密码#"AAAAAAAAAAAAAAAA" start_date = arg4 #起始时间#'20210929' end_date = arg5 #终止时间#'20211008' # 设置储存路径 mail_att_dir = arg3 #r'D:\att_file'
3.3 C#界面设计
提供基础的参数入口:邮箱类型、邮箱账号、密码、保存路径、起止时间
四、功能实现
4.1 邮箱设置
在实现邮件附件下载功能之前,首先需要开启邮箱的pop协议,一般在设置里,以163邮箱为例,设置后会给个密码,这个就是程序访问邮箱的登录密码:
邮箱内测试邮件:
4.2 邮件附件下载实现
1、依赖的模块
import email import os import poplib import time from email.header import decode_header from email.parser import Parser from email.utils import parseaddr import sys
2、核心代码
python遍历邮件批量下载指定时间段的邮件附件代码(参考[1、3]):
def decode_str(s): """ 字符编码转换 """ value, charset = decode_header(s)[0] if charset: value = value.decode(charset) return value # 登录 server.user(username) server.pass_(password) resp, mails, octets = server.list() #获取所有邮件编号,mails的格式为['mesg_num octets', ...] # 倒序遍历邮件 index = len(mails) for i in range(index, 0, -1): # lines存储了邮件的原始文本的每一行 resp, lines, octets = server.retr(i) # 邮件的原始文本: msg_content = b'\r\n'.join(lines).decode('utf-8') #⭐ # 解析邮件: msg = Parser().parsestr(msg_content) # 获取邮件日期,格式化收件时间 date = time.strptime(msg.get('Date')[:24], '%a, %d %b %Y %H:%M:%S') date = time.strftime('%Y%m%d', date)#邮件时间格式转换 # 只下载发送日期在arg1到arg2之间的邮件附件 if date < start_date : break if date > end_date : continue # 下载附件 attachment_files = [] for part in msg.walk(): file_name = part.get_filename() # 获取附件名称类型 contType = part.get_content_type() if file_name: h = email.header.Header(file_name) dh = email.header.decode_header(h) # 对附件名称进行解码 filename = dh[0][0] if dh[0][1]: filename = decode_str(str(filename, dh[0][1])) # 将附件名称可读化 data = part.get_payload(decode=True) # 下载附件 # 创建附件存储文件夹 if not os.path.isdir(mail_att_dir ): os.mkdir(mail_att_dir ) # 在指定目录下创建文件,注意二进制文件需要用wb模式打开 att_file = open(mail_att_dir + os.sep + filename, 'wb') attachment_files.append(filename) att_file.write(data) # 保存附件 att_file.close() server.quit()
3、其他
若需要获取邮件其他信息可以在循环中添加(参考[3-1]):
#获取邮件的发件人,收件人, 抄送人,主题 # hdr, addr = parseaddr(msg.get('From')) # From = self.decode_str(hdr) # hdr, addr = parseaddr(msg.get('To')) # To = self.decode_str(hdr) # 方法2:from or Form均可 From = parseaddr(msg.get('from'))[1] To = parseaddr(msg.get('To'))[1] Cc = parseaddr(msg.get_all('Cc'))[1] # 抄送人 Subject = decode_str(msg.get('Subject')) print('from:%s,to:%s,Cc:%s,subject:%s'%(From,To,Cc,Subject))
这一块还有很多的功能或者细节可以完善(比如函数封装等等),具体的可以自己研究。
4、异常处理
对于有的同学,复制粘贴过去的代码,提示报错TabError: inconsistent use of tabs and spaces in indentation:参考[4-1],主要是代码中应该为空的缩进而没有为空,具体自己重新检查一下。
对于在遍历下载中有可能出现UnicodeDecodeError: 'utf-8' codec can't decode byte..报错,参考[4-2],具体解决将代码中注释为⭐的一行代码如下修改(加上‘ignore’参数):
#修改字符集参数,一般这种情况出现得较多是在国标码(GBK)和utf8之间选择出现了问题。 #出现异常报错UnicodeDecodeError是由于设置了decode()方法的第二个参数errors为严格(strict)形式造成的,因为默认就是这个参数,将其更改为ignore等即可。 msg_content = b'\r\n'.join(lines).decode('utf-8','ignore')
5、python代码调用测试
以上编写后的.py文件在python中的调用测试情况:
如此,则可以将其打包成exe,这个主要参考[2],通过Pyinstaller -F py_word.p打包成exe。