发送带有数据框的电子邮件作为附件
Posted
技术标签:
【中文标题】发送带有数据框的电子邮件作为附件【英文标题】:Sending email with dataframe as an attached file 【发布时间】:2021-02-28 16:03:08 【问题描述】:我正在尝试创建一个函数,该函数可以发送带有作为csv
文件附加的数据框的电子邮件。附加文件通常需要先将文件保存到磁盘,所以我不知道是否有任何直接的方法可以解决这个问题?
我已经创建了一个可以将数据框附加为 html 的函数,以及一个可以将附件作为电子邮件发送的函数,但没有可以直接将数据框作为附件发送的函数
常规设置
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
import os
_server = 'MY_SERVER'
_port = 9999
_sender = 'my.email@domain.com'
def create_simple_mail(to, title):
mail = MIMEMultipart()
mail['Subject'] = title
mail['To'] = to
mail['From'] = _sender
以 html 格式发送数据帧
def mail_dataframe_as_html(to, msg, title, df):
mail = create_simple_mail(to, msg, title)
html_str = msg
html_str += '<tr></tr><tr></tr>'
html_str += df.to_html()
mail.attach(MIMEText(html_str, 'html'))
smtp_connection = smtplib.SMTP(_server, _port, timeout=120)
smtp_connection.sendmail(_sender, to, mail.as_string())
发送附件
def attach_file_to_mail(mail,f):
with open(f, "rb") as fil:
part = MIMEApplication(fil.read(), Name=os.path.basename(f))
part['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename(f)
mail.attach(part)
return mail
def mail_html(to, title, html, attachments=None):
mail = create_simple_mail(to=to, msg=None, title=title)
mail.attach(MIMEText(html, 'html'))
if attachments is not None:
for f in attachments:
mail = attach_file_to_mail(mail,f)
smtp_connection = smtplib.SMTP(_server, _port, timeout=120)
smtp_connection.sendmail(_sender, to, mail.as_string())
【问题讨论】:
【参考方案1】:试试pandas.DataFrame.to_csv 示例。使用 pandas 数据框作为 .csv 附件发送邮件:
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib
import os
import pandas as pd
from datetime import datetime
_server = 'smtp.example.com'
_port = 587
_sender = 'some_sender@example.com'
_pass = 'pass_value'
def create_simple_mail(to, title):
mail = MIMEMultipart()
mail['Subject'] = title
mail['To'] = to
mail['From'] = _sender
return mail
def attach_file_to_mail(mail,f):
with open(f, "rb") as fil:
part = MIMEApplication(fil.read(), Name=os.path.basename(f))
part['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename(f)
mail.attach(part)
return mail
def mail_html(to, title, html, attachments=None):
mail = create_simple_mail(to=to, title=title)
mail.attach(MIMEText(html, 'html'))
if attachments is not None:
for f in attachments:
mail = attach_file_to_mail(mail,f)
smtp_connection = smtplib.SMTP(_server, _port, timeout=120)
# I tested with TLS server connection
smtp_connection.ehlo()
smtp_connection.starttls()
smtp_connection.ehlo()
smtp_connection.login(_sender, _pass)
smtp_connection.sendmail(_sender, to, mail.as_string())
if __name__ == "__main__":
df_data = 'num': [1, 2, 3],
'name': ['some val 1','some val 2','some val 3'],
'year': [2001, 2002, 2003],
df = pd.DataFrame(df_data, columns = ['num', 'name','year'])
now = datetime.now()
date_time = now.strftime("%d_%m_%Y__%H_%M_%S")
file_name = f'date_time.csv'
df.to_csv(file_name, index = False)
mail_html('some@example.com','Some title','<b>html text</b>',[file_name])
# If need
os.remove(file_name)
结果:
【讨论】:
【参考方案2】:我想出了一个非常巧妙的解决方案,使用 __enter__
和 __exit__
清理可以附加到电子邮件的临时生成文件。
import os
import uuid
class CreateAttachments:
def __init__(self, dfs, file_names=None):
self._attachments = dfs
# Create generic file names if none was specified
if file_names is None:
self._names = [f'attached_dfi.csv' for i in range(len(dfs))]
# Ensure all file names ends with the .csv extension
else:
self._names = [fn if fn.endswith('.csv') else fn + '.csv' for fn in file_names]
# If less file names than attachments were provided, generic names are appended
if len(self._names) < len(self._attachments):
self._names += [f'attached_dfi.csv' for i in range(len(self._attachments) - len(self._names))]
# Create a temporary folder for the files
self._file_location = 'C:/Users/%s/%s/' % (os.getenv('username'), str(uuid.uuid4()))
def __enter__(self):
# If by the odd chance the temporary folder already exists, we abort the program
if os.path.isdir(self._file_location):
raise IsADirectoryError("Directory already exists. Aborting")
# Create temporary folder
os.makedirs(self._file_location)
# For each attachment, save the dataframe to a .csv in the temporary folder
attach_paths = []
for attach, name in zip(self._attachments, self._names):
tmp_path = self._file_location + name
attach.to_csv(tmp_path)
attach_paths.append(tmp_path)
# Save all paths to class, as they are needed in the __exit__ function
self.paths = attach_paths
# Return paths to use for attachments
return self.paths
def __exit__(self, exc_type, exc_val, exc_tb):
# Remove the .csv files for each attachment
for path in self.paths:
os.remove(path)
# Remove the temporary folder
os.removedirs(self._file_location)
使用上面的代码,我现在可以像这样创建附件:
import pandas as pd
df1 = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
df2 = pd.DataFrame([[2, 4, 6], [3, 6, 9], [4, 8, 12]])
html_str = '''<table style="width:100%">'''
html_str += '<tr> <font size="4"><b>Some HTML example</b> </font size> </tr>'
html_str += '<br> This is a test mail.'
with CreateAttachments([df1, df2]) as f:
mail_html(to='receiver@domain.com',
html=html_str,
title='Test email with attachments',
attachments=f)
【讨论】:
以上是关于发送带有数据框的电子邮件作为附件的主要内容,如果未能解决你的问题,请参考以下文章