【问题标题】: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())

【问题讨论】:

    标签: python pandas email smtp mime


    【解决方案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_df{i}.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_df{i}.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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-03-11
        • 2015-09-09
        • 2018-05-12
        • 1970-01-01
        相关资源
        最近更新 更多