基于Python的数据导出和邮件发送 python结果导出
yuyutoo 2024-10-15 16:53 9 浏览 0 评论
背景
由于运维工作需要,经常需要将一些数据从数据库中导出,发送给运营和需求部门,天天去手动查询,又有点太费时间了,于是研究学习了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)
return
4、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("<h1>见附件</h1> <br />", "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)
相关推荐
- Python操作Word文档神器:python-docx库从入门到精通
-
Python操作Word文档神器:python-docx库从入门到精通动动小手,点击关注...
- Python 函数调用从入门到精通:超详细定义解析与实战指南 附案例
-
一、函数基础:定义与调用的核心逻辑定义:函数是将重复或相关的代码块封装成可复用的单元,通过函数名和参数实现特定功能。它是Python模块化编程的基础,能提高代码复用性和可读性。定义语法:...
- 等这么长时间Python背记手册终于来了,入门到精通(视频400集)
-
本文毫无套路!真诚分享!前言:无论是学习任何一门语言,基础知识一定要扎实,基础功非常的重要,找一个有丰富编程经验的老师或者师兄带着你会少走很多弯路,你的进步速度也会快很多,无论我们学习的目的是什么,...
- 图解Python编程:从入门到精通系列教程(附全套速查表)
-
引言本系列教程展开讲解Python编程语言,Python是一门开源免费、通用型的脚本编程语言,它上手简单,功能强大,它也是互联网最热门的编程语言之一。Python生态丰富,库(模块)极其丰富,这使...
- Python入门教程(非常详细)从零基础入门到精通,看完这一篇就够
-
本书是Python经典实例解析,采用基于实例的方法编写,每个实例都会解决具体的问题和难题。主要内容有:数字、字符串和元组,语句与语法,函数定义,列表、集、字典,用户输入和输出等内置数据结构,类和对象,...
- Python函数全解析:从入门到精通,一文搞定!
-
1.为什么要用函数?函数的作用:封装代码,提高复用性,减少重复,提高可读性。...
- Python中的单例模式:从入门到精通
-
Python中的单例模式:从入门到精通引言单例模式是一种常用的软件设计模式,它保证了一个类只有一个实例,并提供一个全局访问点。这种模式通常用于那些需要频繁创建和销毁的对象,比如日志对象、线程池、缓存等...
- 【Python王者归来】手把手教你,Python从入门到精通!
-
用800个程序实例、5万行代码手把手教你,Python从入门到精通!...
- Python从零基础入门到精通:一个月就够了
-
如果想从零基础到入门,能够全职学习(自学),那么一个月足够了。...
- Python 从入门到精通:一个月就够了
-
要知道,一个月是一段很长的时间。如果每天坚持用6-7小时来做一件事,你会有意想不到的收获。作为初学者,第一个月的月目标应该是这样的:熟悉基本概念(变量,条件,列表,循环,函数)练习超过30个编...
- Python零基础到精通,这8个入门技巧让你少走弯路,7天速通编程!
-
Python学习就像玩积木,从最基础的块开始,一步步搭建出复杂的作品。我记得刚开始学Python时也是一头雾水,走了不少弯路。现在回头看,其实掌握几个核心概念,就能快速入门这门编程语言。来聊聊怎么用最...
- 神仙级python入门教程(非常详细),从0到精通,从看这篇开始!
-
python入门虽然简单,很多新手依然卡在基础安装阶段,大部分教程对一些基础内容都是一带而过,好多新手朋友,对一些基础知识常常一知半解,需要在网上查询很久。...
- Python类从入门到精通,一篇就够!
-
一、Python类是什么?大家在生活中应该都见过汽车吧,每一辆真实存在、能在路上跑的汽车,都可以看作是一个“对象”。那这些汽车是怎么生产出来的呢?其实,在生产之前,汽车公司都会先设计一个详细的蓝图...
- 学习Python从入门到精通:30天足够了,这才是python基础的天花板
-
当年2w买的全套python教程用不着了,现在送给有缘人,不要钱,一个月教你从入门到精通1、本套视频共487集,本套视频共分4季...
- 30天Python 入门到精通(3天学会python)
-
以下是一个为期30天的Python入门到精通学习课程,专为零基础新手设计。课程从基础语法开始,逐步深入到面向对象编程、数据处理,最后实现运行简单的大语言模型(如基于HuggingFace...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)