范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

基于Python的数据导出和邮件发送

  背景
  由于运维工作需要,经常需要将一些数据从数据库中导出,发送给运营和需求部门,天天去手动查询,又有点太费时间了,于是研究学习了Python的基本功能,通过Python脚本和Linux 的crontab命令实现了每天自动化的数据查询和邮件发送。代码实现
  定义了以下几个代码模块实现了配置文件读取、日志记录、数据库连接访问查询、导出到xlsx和带附件的邮件发送功能。
  1、demo.py 示例文件
  2、config.yml 配置文件
  3、common_log.py 实现日志记录
  4、common_db.py 实现数据库连接和访问
  5、common_xlsx.py 实现数据表格的处理
  6、common_email.py 实现带附件的邮件发送1、demo.py 示例# coding: utf-8 import common_db as mydb import common_xlsx as my_xlsx import common_log as mylog import common_email as my_email  if __name__ == "__main__":     phone = "13********7"     sql = """ select orderno,orderAmount,actualAmount,phone order where phone= "{0}" """.format(phone)     # 查询订单     result = mydb.select_by_parameters(sql)     filename = "测试.xlsx"     sheet_name = "订单查询"     # 邮件接收人     receivers = "zhangsan@aliyun.com"     receivers_cc = "lisi@aliyun.com"     # 邮件抄送人     if len(result) > 0:         # 创建工作表         my_xlsx.create_xlsx(filename)         mylog.logger.info("创建工作表成功"+filename)         # 将查询结果放入工作表         my_xlsx.create_sheet_in_xlsx(filename, sheet_name, result,0)         mylog.logger.info("创建工作簿成功"+sheet_name)         # 对工作簿求和         my_xlsx.sum_col_for_sheet(filename, sheet_name)         mylog.logger.info("求和汇总成功")         # 删除空工作表         my_xlsx.delete_sheet_from_xlsx(filename,"Sheet")         # 发送邮件         my_email.to_send_email("测试查询结果", receivers, receivers_cc, filename, "订单查询结果.xlsx")         mylog.logger.info("发送邮件成功")2、config.yml——配置文件mysql:   host: 192.168.x.x   port: 3306   username: xxxx   password: xxxx   database: xxxx log:   log_path: D:log   log_size: 8   log_num: 3 email:   smtp: smtp.126.com   # 发送方邮件地址   from: xxxx@aliyun.com   # 发送方授权码   password: fdsafafdsafsa 3、common_log.py 实现日志记录import logging.handlers import logging import yaml import os import sys  # 提供日志功能 class logger:     # 先读取XML文件中的配置数据     # 由于config.xml放置在与当前文件相同的目录下,因此通过 __file__ 来获取XML文件的目录,然后再拼接成绝对路径     # 这里利用了lxml库来解析XML     # root = etree.parse(os.path.join(os.path.dirname(__file__), "config.xml")).getroot()      # 先读取yml中的配置数据     # 由于     # 读取日志文件保存路径     with open("config.yml", "r") as f:         result = yaml.load(f, Loader=yaml.FullLoader)         config_log = result["log"]      logpath = config_log["log_path"]     # root.find("logpath").text     # 读取日志文件容量,转换为字节     logsize = 1024*1024*int(config_log["log_size"])     # 读取日志文件保存个数     lognum = int(config_log["log_num"])      # 日志文件名:由用例脚本的名称,结合日志保存路径,得到日志文件的绝对路径     logname = os.path.join(logpath, sys.argv[0].split("/")[-1].split(".")[0])+".log"      # 初始化logger     log = logging.getLogger()     # 日志格式,可以根据需要设置     fmt = logging.Formatter("[%(asctime)s][%(filename)s][line:%(lineno)d][%(levelname)s] %(message)s", "%Y-%m-%d %H:%M:%S")      # 日志输出到文件,这里用到了上面获取的日志名称,大小,保存个数     handle1 = logging.handlers.RotatingFileHandler(logname, maxBytes=logsize, backupCount=lognum)     handle1.setFormatter(fmt)     # 同时输出到屏幕,便于实施观察     handle2 = logging.StreamHandler(stream=sys.stdout)     handle2.setFormatter(fmt)     log.addHandler(handle1)     log.addHandler(handle2)      # 设置日志基本,这里设置为INFO,表示只有INFO级别及以上的会打印     log.setLevel(logging.INFO)      # 日志接口,用户只需调用这里的接口即可,这里只定位了INFO, WARNING, ERROR三个级别的日志,可根据需要定义更多接口     @classmethod     def info(cls, msg):         cls.log.info(msg)         return      @classmethod     def warning(cls, msg):         cls.log.warning(msg)         return      @classmethod     def error(cls, msg):         cls.log.error(msg)         return4、common_db.py 实现数据库连接和访问import time import pymysql from common_log import *  # 连接数据库 def get_connection():     _conn_status = True     _max_retries_count = 10  # 设置最大重试次数     _conn_retries_count = 0  # 初始重试次数     _conn_timeout = 3  # 连接超时时间为3秒     with open("config.yml", "r") as f:         result = yaml.load(f, Loader=yaml.FullLoader)         config_mysql = result["mysql"]     while _conn_status and _conn_retries_count <= _max_retries_count:         try:             connect = pymysql.connect(host=config_mysql["host"], user=config_mysql["username"],                                   password=config_mysql["password"], database=config_mysql["database"],                                   port=config_mysql["port"])             _conn_status = False # 如果conn成功则_status为设置为False则退出循环,返回db连接对象             logger.info("连接数据库成功")             return connect         except Exception as e:             _conn_retries_count += 1             logger.info("第%s次连接数据库失败"%(_conn_retries_count))             logger.error(e)         time.sleep(3)         continue  # 查询函数 def select_by_parameters(sql, params=None):     try:         connect = get_connection()         cursor = connect.cursor(pymysql.cursors.DictCursor)         cursor.execute(sql, params)         result = cursor.fetchall()         return result     except Exception as e:         logger.error("执行查询报错")         logger.info(sql)         logger.error(e)     finally:         try:             cursor.close()         except Exception as e:             logger.error("关闭游标对象报错")             logger.error(e)         try:             connect.close()         except Exception as e:             print(e)             print("数据库链接关闭异常")5、common_xlsx.py 实现数据表格的处理import openpyxl import os from openpyxl.styles import PatternFill, Border, Side, Alignment, Protection, Font, colors from openpyxl.utils import get_column_letter  # 定义边框 thin_border = Border(left=Side(style="thin", color="FFFFFF"), right=Side(style="thin", color="FFFFFF"),                      top=Side(style="thin", color="FFFFFF"), bottom=Side(style="thin", color="FFFFFF")) # 居中对齐 alignment_center = Alignment(horizontal="center", vertical="center") # 右对齐 alignment_right = Alignment(horizontal="center", vertical="center")  # 双行填充 fill_double = PatternFill(fgColor="FFDCE6F1", fill_type="solid") # 单行填充 fill_single = PatternFill(fgColor="FFB8CCE4", fill_type="solid")  # 表头填充 fill_head = PatternFill(fgColor="FF366092", fill_type="solid") font_head = Font(bold=True, color="FFFFFFFF") # 1、创建xlsx脚本——在指定的filepath,创建指定的filename的xlsx文件 def create_xlsx(filename):     wb = openpyxl.Workbook()     wb.save(filename)  # 2、增加工作簿脚本——读取指定路径下的xlsx文件,在工作表第index位置增加一个工作簿,并将mysql查询结果result写入到该工作簿 def create_sheet_in_xlsx(filename, sheet_name, result, index):     # 加载文件     wb = openpyxl.load_workbook(filename)     # 在指定位置创建工作表     wb.create_sheet(sheet_name, index)     # 获取新建的工作表     ws = wb[sheet_name]     j = 1     if len(result) > 0:         # 写表头         for key, value in (result[0].items()):             ws.cell(1, j, format(key)).border = thin_border             # 定义对齐方式             ws.cell(1, j).alignment = alignment_center             # 字体 颜色为白色             ws.cell(1, j).font = font_head             # 填充             ws.cell(1, j).fill = fill_head             # 边框             j = j + 1             ws.row_dimensions[1].height = 30         # 写数据         # 根据结果集行数量进行循环         for i in range(len(result)):             # 循环当前行,定义j变量为列使用             j = 1             for key, value in (result[i].items()):                 if i % 2 == 0:                     ws.cell(i+2, j).fill = fill_double                 else:                     ws.cell(i+2, j).fill = fill_single                 ws.cell(i + 2, j, value)                 ws.cell(i + 2, j).border = thin_border                 ws.cell(i + 2, j).alignment = alignment_center                 ws.row_dimensions[i + 2].height = 20                 if "时间" in format(key):                     ws.cell(i+2, j).alignment = alignment_center                     ws.cell(i+2, j).number_format = "yyyy-mm-dd hh:mm:ss"                     j = j + 1                     continue                 # 如果字段名称是数量的话,不保留小数                 if "数量" in format(key):                     ws.cell(i+2, j).number_format = "0"                     j = j + 1                     continue                 # 如果是字段名称包含金额的话,则右对齐                 if "金额" in format(key):                     ws.cell(i+2, j).alignment = alignment_right                     ws.cell(i+2, j).number_format = "#,##0.00"                     j = j + 1                     continue                 j = j + 1     # 保存工作表     wb.save(filename)  # 3、删除工作簿 def delete_sheet_from_xlsx(filename,sheet_name):     wb = openpyxl.load_workbook(filename)     wb.remove_sheet(wb[sheet_name])     wb.save(filename)  # 4、往工作簿中追加行 将mysql的查询结果追加到工作表sheet_name末尾 def add_result_to_sheet(filename,new_sheet_name,result):     wb = openpyxl.load_workbook(filename)     ws = wb[new_sheet_name]     # 获取最大行     mr = ws.max_row     if len(result) > 0:         for i in range(len(result)):             # 循环当前行,定义j变量为列使用             j = 1             for key, value in (result[i].items()):                 if i % 2 == 0:                     ws.cell(i + mr, j).fill = fill_double                 else:                     ws.cell(i + mr, j).fill = fill_single                 ws.cell(i + mr, j, value)                 ws.cell(i + mr, j).border = thin_border                 ws.cell(i + mr, j).alignment = alignment_center                 ws.row_dimensions[i + 2].height = 20                 if "时间" in format(key):                     ws.cell(i + mr, j).number_format = "yyyy-mm-dd hh:mm:ss"                     j = j + 1                     continue                 # 如果字段名称是数量的话,不保留小数                 if "数量" in format(key):                     ws.cell(i + mr, j).number_format = "0"                     j = j + 1                     continue                 # 如果是字段名称包含金额的话,则右对齐                 if "金额" in format(key):                     ws.cell(i + mr, j).alignment = alignment_right                     ws.cell(i + mr, j).number_format = "#,##0.00"                     j = j + 1                     continue                 j = j + 1     wb.save(filename)  # 5、删除工作簿中最大行 def delete_max_row_from_sheet(filename,sheet_name):     wb = openpyxl.load_workbook(filename)     ws = wb[sheet_name]     ws.delete_rows(ws.max_row)     wb.save(filename)  # 6、删除工作簿中指定列 def delete_col_from_sheet(filename,sheet_name,colno):     wb = openpyxl.load_workbook(filename)     ws = wb[sheet_name]     ws.delete_cols(colno)     wb.save(filename)  # 7、对表格中金额和数量字段进行求和 def sum_col_for_sheet(filename,sheet_name):     wb = openpyxl.load_workbook(filename)     ws = wb[sheet_name]     for col in list(ws.columns):         l = [c.value for c in col]         if "金额" in l[0] or "数量" in l[0]:             # 本列行的数量             row_size = len(col) + 1             # 本列的列号是             col_no = col[1].column             col_code = get_column_letter(col[1].column)             ws.cell(row_size, col_no, "=sum(" + str(col_code) + str(2) + ":" + str(col_code) + str(                 row_size - 1) + ")").border = thin_border             ws.cell(row_size, col_no).font = font_head             # 填充             ws.cell(row_size, col_no).fill = fill_head             # 测试添加样式             if "金额" in l[0]:                 ws.cell(row_size, col_no).number_format = "#,##0.00"                 ws.cell(row_size, col_no).alignment = alignment_right             if "数量" in l[0]:                 ws.cell(row_size, col_no).number_format = "0"                 ws.cell(row_size, col_no).alignment = alignment_center             ws.row_dimensions[col_no].height = 20     wb.save(filename)6、common_email.py 实现带附件的邮件发送from email.mime.text import MIMEText from email.header import Header from email.mime.multipart import MIMEMultipart from smtplib import SMTP_SSL from common_log import * import yaml  # # file_Name是路径名称加文件名和扩展名; # new_file_name是在邮件附件中显示的名称 # receivers是收件人列表,中间逗号隔开 # receivers_cc是抄送人列表 # mail_subject是邮件主题 def to_send_email(mail_subject,receivers,receivers_cc,file_name,new_file_name):     with open("config.yml", "r") as f:         result = yaml.load(f, Loader=yaml.FullLoader)         config_email = result["email"]     password = config_email["password"]     msg = MIMEMultipart("related")     msgAlternative = MIMEMultipart("alternative")     msgAlternative.attach(MIMEText("

见附件


", "html", "utf-8")) msg.attach(msgAlternative) # file_name 是指文件路径加名称和扩展名 file1 = MIMEText( open(file_name, "rb").read(), "base64", "utf-8" ) file1["Content-Type"] = "application/octet-stream" # new_file_name是指邮件附件中显示的名称 file1.add_header("Content-Disposition", "attachment",filename=new_file_name) msg.attach(file1) msg["Subject"] = Header(mail_subject, "utf-8").encode() msg["From"] = config_email["from"] msg["To"] = receivers msg["Cc"] = receivers_cc try: smtp = SMTP_SSL(config_email["smtp"]) smtp.login(msg["From"], password) smtp.sendmail(msg["From"], msg["To"].split(",") + msg["Cc"].split(","), msg.as_string()) logger.info("发送邮件成功,邮件接收人是:%s,邮件抄送人是:%s"%(receivers, receivers_cc)) except Exception as e: logger.error("发送邮件出现错误,邮件接收人是:%s,邮件抄送人是:%s" % (receivers, receivers_cc)) logger.error(e) finally: try: smtp.quit() except Exception as e: logger.error(e)

时间旅行者背后的真相这些年,我们对穿越时空存在着无数的幻想和执念,近期在tiktok上就有一位名叫哈维尔的哥们儿,声称自己从2027年穿越到了2021年的平行世界,吸引流量无数。图片来源TikToku企业加油站照片新能源车暴增,4小时路程,充电花了3小时嘿!朋友们,十一小长假转瞬即逝,重新投入工作中的我们几乎都是迷迷糊糊,漫无目的,疲惫不堪,但,生活还在继续,挣钱,挣钱,挣钱是我们共同的目的!(我也是被迫上岗)继续给大家介绍撬装加字节跳动月薪5万招数据分析师,看到要求我傻眼了被经济学杂志誉为新石油的数据分析师,成了近年来各企业争抢的香饽饽。像阿里腾讯华为等大厂,给出的年薪甚至接近百万。但现实中更多的分析师被称为数据民工,懂点Excel和SQL就能上手。官宣香山提名来啦香山树仁教育创立于2015年。香山源于中山古称,树仁取自十年树木,百年树人的教育理念。成立6年以来,始终秉持树人成仁的办学信念,汇聚了数十位具有丰富公办学校教学及管理经验的名师,以德国柏林市民推动无车方案,禁止市民驾车,你如何看?近期德国柏林一个名为BerlineAutofrei的团体实行全民公投,推动柏林无车方案,禁止私家车在当地SBahn环(城市快捷铁路的缩写)火车线围绕约88平方公里的区域行驶。除了特中宝石油举行杭州宁波辽中运营中心签约仪式9月29日下午,中宝石油隆重举行杭州市宁波市辽中区运营中心签约仪式,中宝石油CEO潘天印同志主持仪式并向二市一区运营中心授牌。杭州市宁波市运营中心潘天印与杭州市宁波市运营中心总经理全面屏轻薄本!华为MateBookD系列成为学生党的最佳选择很多高校在大一新生刚入学时,都会明令禁止带电脑,而到了第一学期尾声,该规定就会自动解禁。因此,每年年底的时候就会有很多童鞋准备购买一款电脑来充实自己的大学生活。对于学生党而言,要购三十而立,一杯酒敬自己,天一亮便再次乘风破浪一直认为,电视上男主角喝着酒思考问题的情景那仅仅是导演构思的一个优雅桥段。当自己步入三十的时候才明白,这其实是现实男人的生活写照。都说三十而立是很有哲理的,三十真是一个男人一生中一千元机拍照哪家强?RedmiNote7VS荣耀Play3差距明显肉眼可见如今,相机已经成为智能手机上重中之重的一个功能,厂商也在不遗余力的宣传自家手机相机强大。正当我们把注意力放在旗舰机的相机上的时候,为什么我们不反过来看看一些入门的千元机在成像效果上高通骁龙8554500m魅族16T正式发布,1999起售10月23日,魅族科技正式在北京发布魅族大屏娱乐旗舰魅族16T。魅族16T采用了6。5英寸AMOLED屏幕,高通骁龙855处理器,前置1600万像素,后置三摄(IMX3621200vivoNEX3正式发布,4998元起售,这些细节你一定要vivoNEX正面采用6。89英寸瀑布屏设计,弯折角度约90,屏占比高达99。6,2亿元定制的屏幕,升降摄像头,全新屏幕指纹,出厂标配贴膜,物理按键全部取消。高通骁龙855Plus
马自达CX50专利渲染图曝光,搭3。0L六缸引擎如果大家想买一辆宝马,但预算又够不上,会不会考虑马自达?毕竟它也被称为东瀛宝马。近日,海外媒体曝光了一组马自达全新CX50的外观专利图,还有媒体制作了它的渲染图。新车将于明年正式发入门合资VS顶配自主,20万级SUV的鸡头凤尾该怎么选?选车,鸡头还是凤尾,是个永远的话题。尤其是买SUV时,到底该买合资品牌入门版还是自主品牌顶配?这个问题一直是都是许多消费者的痛点,因为相同的价格,合资品牌和自主品牌相差的或许就是入和保时捷同平台,奥迪Q6etron路试谍照曝光现如今,各大车企都在加速发布新能源车型。不像宝马iX1的糊弄人,采用油改电的做法作为奥迪的首款新能源车型,奥迪etron来自全新的纯电动平台,随后etronSportbacketr奔驰G级军用版官图曝光,外观性能更霸气,但只能看着眼馋?随着城市化的推进,传统的硬派越野车好像离我们越来越远了,很多车企甚至直接放弃了非承载车身。但不得不说的,得不到的永远在骚动,尽管硬派越野车变得越来越小众了,坐起来也并不舒服,但每个全新标致4008设计图曝光,外观前卫个性,能上演绝地大反击吗?近期,法系车的雪铁龙凡尔赛C5X,以独特的命名方式,及较高的性价比,似乎重新打开了国内的汽车市场。不过雪铁龙凡尔赛C5X的整体虽然时尚,跟之前法系车的前卫感有了一些区别。想要享受更和三缸机说拜拜,福克斯换1。6T四缸,PK本田思域?对于三缸机,不少人都敬而远之,再有口碑和销量的车型都扛不住。比如,福特福克斯,作为曾经小钢炮界的佼佼者,它在换装完三缸机后,销量和口碑都有明显下滑。但好消息是,在不久的将来,搭载四绿牌加油两不误,续航超800km的中大型SUV,岚图FREE还是理想ONE?经过了今年的国庆长假,很多相信有不少小伙伴深刻体会到纯电车型的痛点。因为这次的假期,部分驾驶纯电车型长途出行的小伙伴,不是因为堵车,而是因为充电问题,把8个小时能跑完的路程,硬生生2022款AMGS63e谍照曝光,V8插混,预计明年上市近日,海外媒体曝光了一组2022款奔驰AMGS63e的路试谍照。这次谍照除了前脸和车尾做了伪装,整车其余地方没有做任何伪装。据悉,2022款奔驰AMGS63e会搭载插电式混合动力系要买荣放的再等等,顶配版来了,你觉得凌放值得入手吗?10月13日,一汽丰田凌放曝光,车辆定位为中型SUV。有许多打算买荣放的网友表示要等等了,毕竟车辆的产品力会更强,也有些人表示买它还不如买荣放呢,性价比更高,你觉得呢?其实,凌放并比亚迪汉DMi路试谍照曝光,油耗低至0。8L,比Model3更有吸引力?在刚刚过去的9月,比亚迪汉的销量突破了一万台,同比增长115。而从去年7月份上市至今,它的累计销量已经超过12万台,足以见得它在市场中的认可度。所以,当比亚迪官方公布将要推出这款车长安欧尚X7PLUS上市7。99万起,动力配置都升级,值得买吗?10月17日,长安欧尚X7PLUS正式上市。新车全系标配蓝鲸新一代NE1。5T发动机,共推出9款车型,提供6种车身配色供用户选择,官方指导售价7。99万13。39万元。作为长安汽车