影刀RPA 邮件附件处理:批量发送与下载
📅 2026/7/25 6:55:59
👁️ 阅读次数
📝 编程学习
拼多多店群自动化上架方案
影刀RPA 邮件附件处理:批量发送与下载
署名:林焱
什么情况用什么
RPA流程中经常需要处理邮件附件——每天采集的报表自动发送给相关人员;从收件箱下载附件并处理;批量发送不同附件给不同收件人。
| 场景 | 推荐方式 | 特点 |
|---|---|---|
| 发送带附件邮件 | smtplib + email | 标准库,无需安装 |
| 下载邮件附件 | imaplib | IMAP协议读取 |
| 批量个性化发送 | smtplib + 模板 | 每人不同附件 |
怎么做
一:发送带附件的邮件
importsmtplibfromemail.mime.multipartimportMIMEMultipartfromemail.mime.textimportMIMETextfromemail.mime.baseimportMIMEBasefromemailimportencodersimportosdefsend_email_with_attachments(smtp_server,smtp_port,sender_email,sender_password,to_emails,subject,body,attachments=None,is_html=False):"""发送带附件的邮件"""msg=MIMEMultipart()msg["From"]=sender_email msg["To"]=", ".join(to_emails)ifisinstance(to_emails,list)elseto_emails msg["Subject"]=subject# 邮件正文content_type="html"ifis_htmlelse"plain"msg.attach(MIMEText(body,content_type,"utf-8"))# 添加附件ifattachments:forfile_pathinattachments:ifnotos.path.exists(file_path):print(f"附件不存在:{file_path}")continuewithopen(file_path,"rb")asf:part=MIMEBase("application","octet-stream")part.set_payload(f.read())encoders.encode_base64(part)part.add_header("Content-Disposition",f'attachment; filename="{os.path.basename(file_path)}"')msg.attach(part)# 发送try:server=smtplib.SMTP(smtp_server,smtp_port)server.starttls()server.login(sender_email,sender_password)server.sendmail(sender_email,to_emails,msg.as_string())server.quit()print(f"邮件发送成功:{subject}")returnTrueexceptExceptionase:print(f"发送失败:{e}")returnFalse# 使用send_email_with_attachments(smtp_server="smtp.qq.com",smtp_port=587,sender_email="sender@qq.com",sender_password="授权码",# 注意:QQ邮箱用授权码不是密码to_emails=["recipient@example.com"],subject="2024年6月销售报表",body="请查收附件中的月度销售报表。",attachments=[r"D:\报表\6月销售.xlsx",r"D:\报表\6月利润.pdf"])二:下载邮件附件
importimaplibimportemailimportosfromemail.headerimportdecode_headerdefdownload_attachments(imap_server,email_addr,email_password,folder="INBOX",search_criteria="ALL",save_dir=r"D:\附件",max_emails=50):"""下载邮件附件"""os.makedirs(save_dir,exist_ok=True)# 连接IMAPmail=imaplib.IMAP4_SSL(imap_server)mail.login(email_addr,email_password)mail.select(folder)# 搜索邮件status,messages=mail.search(None,search_criteria)mail_ids=messages[0].split()# 只取最近的N封mail_ids=mail_ids[-max_emails:]iflen(mail_ids)>max_emailselsemail_ids downloaded=[]formail_idinreversed(mail_ids):status,msg_data=mail.fetch(mail_id,"(RFC822)")forresponse_partinmsg_data:ifisinstance(response_part,tuple):msg=email.message_from_bytes(response_part[1])# 获取发件人和主题subject=decode_header_str(msg["Subject"])sender=decode_header_str(msg["From"])# 遍历邮件部分forpartinmsg.walk():content_disposition=str(part.get("Content-Disposition"))if"attachment"incontent_disposition:filename=part.get_filename()iffilename:filename=decode_header_str(filename)# 保存附件filepath=os.path.join(save_dir,filename)# 处理重名base,ext=os.path.splitext(filename)counter=1whileos.path.exists(filepath):filepath=os.path.join(save_dir,f"{base}_{counter}{ext}")counter+=1withopen(filepath,"wb")asf:f.write(part.get_payload(decode=True))downloaded.append({"filename":filename,"path":filepath,"from":sender,"subject":subject})print(f"下载:{filename}(来自:{sender})")mail.logout()returndownloadeddefdecode_header_str(value):"""解码邮件头部字符串"""ifvalueisNone:return""decoded=decode_header(value)result=[]forpart,charsetindecoded:ifisinstance(part,bytes):result.append(part.decode(charsetor"utf-8",errors="replace"))else:result.append(part)return"".join(result)# 使用attachments=download_attachments(imap_server="imap.qq.com",email_addr="receiver@qq.com",email_password="授权码",search_criteria='SUBJECT "报表"',# 只下载标题含"报表"的邮件save_dir=r"D:\附件",max_emails=20)print(f"共下载{len(attachments)}个附件")三:批量个性化发送
importsmtplibfromemail.mime.multipartimportMIMEMultipartfromemail.mime.textimportMIMETextfromemail.mime.baseimportMIMEBasefromemailimportencodersimportos# yd_input: smtp_config, recipientssmtp_config=yd_input.get("smtp_config",{"server":"smtp.qq.com","port":587,"email":"sender@qq.com","password":"授权码"})recipients=yd_input.get("recipients",[])# recipients格式: [{"email": "a@x.com", "name": "张三", "attachment": "D:\\报表\\张三.xlsx"}, ...]success=0errors=[]forrecipientinrecipients:try:msg=MIMEMultipart()msg["From"]=smtp_config["email"]msg["To"]=recipient["email"]msg["Subject"]=f"{recipient['name']}的月度报表"# 个性化正文body=f"""{recipient['name']},您好: 附件是您的本月数据报表,请查收。 如有疑问请回复此邮件。 此邮件由RPA系统自动发送。 """msg.attach(MIMEText(body,"plain","utf-8"))# 添加附件attachment_path=recipient.get("attachment","")ifattachment_pathandos.path.exists(attachment_path):withopen(attachment_path,"rb")asf:part=MIMEBase("application","octet-stream")part.set_payload(f.read())encoders.encode_base64(part)part.add_header("Content-Disposition",f'attachment; filename="{os.path.basename(attachment_path)}"')msg.attach(part)# 发送server=smtplib.SMTP(smtp_config["server"],smtp_config["port"])server.starttls()server.login(smtp_config["email"],smtp_config["password"])server.sendmail(smtp_config["email"],recipient["email"],msg.as_string())server.quit()success+=1print(f"✓{recipient['name']}<{recipient['email']}>")exceptExceptionase:errors.append(f"{recipient.get('name','unknown')}:{e}")print(f"✗{recipient.get('name','unknown')}:{e}")yd_output={"status":"ok","success":success,"errors":errors,"total":len(recipients)}有什么坑
坑一:QQ邮箱/网易邮箱登录报认证失败
现象:用邮箱密码登录SMTP/IMAP报Authentication failed。
原因:QQ邮箱、网易163邮箱等需要用"授权码"而不是登录密码。
解决:在邮箱设置中开启SMTP/IMAP服务并获取授权码:
# QQ邮箱授权码获取方式:# 1. 登录QQ邮箱网页版# 2. 设置 → 账户 → POP3/IMAP/SMTP服务 → 开启# 3. 按提示用手机发短信获取授权码# 4. 代码中用授权码代替密码# 错误:用QQ登录密码# server.login("sender@qq.com", "qq登录密码") ❌# 正确:用授权码server.login("sender@qq.com","授权码")# ✓# 常见SMTP服务器:# QQ邮箱: smtp.qq.com, 端口587# 163邮箱: smtp.163.com, 端口587# Gmail: smtp.gmail.com, 端口587# Outlook: smtp-mail.outlook.com, 端口587# 企业微信邮箱: smtp.exmail.qq.com, 端口465(SSL)坑二:中文附件名乱码
现象:附件文件名是中文(如"销售报表.xlsx"),收件人看到的文件名是乱码。
原因:邮件标准对非ASCII文件名的处理方式特殊,需要用RFC 2231编码。
解决:
fromemail.utilsimportformataddrfromemail.headerimportHeader# 方案1:用Header编码文件名filename="销售报表.xlsx"encoded_filename=Header(filename,"utf-8").encode()part.add_header("Content-Disposition",f'attachment; filename="{encoded_filename}"')# 方案2:用RFC 2231方式part.add_header("Content-Disposition","attachment",[video(video-B5ZstbUk-1784928796160)(type-csdn)(url-https://live.csdn.net/v/embed/524993)(image-https://v-blog.csdnimg.cn/asset/a547123d88ad712dccba346c9217e237/cover/Cover0.jpg)(title-TEMU店群如何管理运营?)]filename=("utf-8","",filename)# 三元组:(编码, 语言, 文件名))# 方案3:同时提供ASCII和UTF-8文件名(兼容性最好)importurllib.parse ascii_name="report.xlsx"# 纯英文备用名part.add_header("Content-Disposition",f'attachment; filename="{ascii_name}"; filename*=UTF-8\'\'{urllib.parse.quote(filename)}')坑三:附件太大导致发送失败
现象:附件超过20MB,邮件发送失败。
原因:大多数邮箱服务商限制单封邮件大小(通常20-50MB)。
解决:压缩附件或拆分发送:
importzipfileimportosdefcompress_attachments(file_paths,output_zip):"""压缩多个文件为zip"""withzipfile.ZipFile(output_zip,"w",zipfile.ZIP_DEFLATED)aszf:forfile_pathinfile_paths:zf.write(file_path,os.path.basename(file_path))returnoutput_zip# 压缩后发送files=[r"D:\报表\大文件1.xlsx",r"D:\报表\大文件2.xlsx"]zip_path=r"D:\temp\报表压缩包.zip"compress_attachments(files,zip_path)# 检查压缩后大小size=os.path.getsize(zip_path)/(1024*1024)# MBifsize>20:print(f"压缩后仍超过20MB ({size:.1f}MB),考虑上传到网盘分享链接")# 上传到网盘,邮件中放链接else:send_email_with_attachments(...,attachments=[zip_path])# 清理临时文件os.remove(zip_path)坑四:IMAP读取邮件时编码混乱
现象:下载的附件文件名乱码,或邮件主题乱码。
原因:邮件头部的编码方式多样(Base64、Quoted-Printable等),需要正确解码。
解决:用decode_header正确处理:
fromemail.headerimportdecode_headerdefsafe_decode(value):"""安全解码邮件头部"""ifvalueisNone:return""ifisinstance(value,str):# 先尝试直接返回try:parts=decode_header(value)except:returnvalueelse:parts=decode_header(value)result=[]forpart,charsetinparts:ifisinstance(part,bytes):# 尝试多种编码forencin[charset,"utf-8","gbk","gb2312","latin-1"]:ifenc:try:result.append(part.decode(enc))breakexcept(UnicodeDecodeError,LookupError):continueelse:result.append(part.decode("latin-1",errors="replace"))else:result.append(str(part))return"".join(result)
编程学习
技术分享
实战经验