python3.6导出oracle表数据到excel、压缩、发送邮件

运行环境:

redhat6 + python3.6 + crontab + Oracle客户端

用到的python模块:
模块 用到的功能
cx_Oracle 连接Oracle数据库
openpyxl 导出excel
zipfile 压缩文件
smtplib 发送邮件
email 添加邮件附件

操作步骤:

1. 安装python3.6 请参考redhat6安装python3.6.9
2. 安装python模块:用pip安装即可
3. 安装Oracle客户端:可以下载Oracle轻量级客户端,请参考oracle客户端下载
4. 配置环境变量 LD_LIBRARY_PATH=[oracle客户端安装目录]:$LD_LIBRARY_PATH ,NLS_LANG=[oracle数据库NLS_LANGUAGE_NLS_TERRITORY.NLS_CHARACTERSET] ,并添加到 ~/.bash_profile 中
5. 编写代码,代码分为两部分,第一个配置文件,如下:
##数据库相关参数:主机 、数据库用户、数据库密码、数据库sid、数据库字符集nls_lan
ora_host = '192.168.1.100'
ora_user = 'test'
ora_pwd = 'test'
ora_sid = 'test'
##nls_lan = 'AMERICAN_AMERICA.ZHS16GBK'

##数据库共享库目录
##lib_path = '/opt/oracle/instantclient_11_2'

##执行脚本(py,sql)目录
exe_path = '/root/python/prod/test/bin'

##数据库导出sql语句文件,多个文件用逗号隔开;如果sql文件在执行脚本目录下,可以使用相对路径,否则使用绝对路径;
##注意:标点符号都是英文的,sql文件中的时间变量必须是 &date 或 &yyyymmdd
sql_files = '测试脚本1.sql,测试脚本2.sql'

##sql语句中的导数日期 yesterday :表示昨天 ,目前只支持这一种情况,以后可以酌情增加其它日期
exp_date = 'yesterday'

##导出excel目录
excel_path = "/root/python/prod/test/data"

##导出excel文件sheet名,sheet名称与上面参数sql_files中的每个sql文件中sql条数一一对应,如果没有设置对应名称,则sheet名称默认
excel_sheet_names = [['测试脚本1'], ['测试脚本2'], ['测试脚本3']]

##导出exel文件是否压缩 1:压缩 0:不压缩 删除此参数
##is_zip = 1

##压缩后文件名前缀 ,压缩文件名=前缀+导数日期
zipfile_name = '测试脚本'

##是否发送邮件 1: 发送 0:不发送
is_mail = 1
##邮箱发送服务器
mail_server = 'smtp.163.com'

##邮箱发送服务器端口
mail_port = '25'

##发送邮件
mail_from = '[email protected]'

##发送邮件密码
mail_pass = 'example'

##接收邮件
mail_to = '[email protected],[email protected]'

##抄送邮件
mail_cc = '[email protected],[email protected]'

##邮件标题
mail_subject = '测试脚本'

##邮件内容
mail_body = """

您好:

     提取数据见附件,请查收;

-------------------------------------------------------------------------------------

test

test

系统在使用过程中如遇到问题,请及时与运维团队联系

热线电话:11111111111

运维邮箱:[email protected]

"""

第二个执行脚本,如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cx_Oracle as cx
from openpyxl import Workbook
import datetime
import os
import zipfile
from config_ora2excel import *   ##导入配置文件
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email import encoders
from email.header import Header
from email.mime.base import MIMEBase

##设置环境变量,不知道为什么不生效,程序还是报错:Error: DPI-1047: Cannot locate a 64-bit Oracle Client library: "libclntsh.so: cannot open shared object file: No such file or irectory". See https://oracle.github.io/odpi/doc/installation.html#linux for help
#os.environ['LD_LIBRARY_PATH'] = lib_path
#os.environ['NLS_LANG'] = nls_lan

#os.environ.setdefault("LD_LIBRARY_PATH", lib_path)
#os.environ.setdefault("NLS_LANG", nls_lan)

def get_yesterday():
   today = datetime.date.today()
   oneday = datetime.timedelta(days=1)
   yesterday = today - oneday
   return yesterday


def get_data_date():
   # 获取数据日期
   ##data_date = input('请输入数据日期,格式yyyymmdd,默认为昨天:')
   data_date = exp_date
   if data_date.strip().lower() == 'yesterday':
       yesterday = get_yesterday()
       data_date = yesterday.strftime("%Y%m%d")
       ##print(data_date)
   return data_date

def get_weeks():
   #获取周次
   data_date = get_data_date()
   y = int(data_date[:4])
   m = int(data_date[4:6])
   d = int(data_date[6:])
   weeks = datetime.date(y,m,d).isocalendar()[1]
   return weeks
  


def ora_to_excel(data_date):
   try:
       ret = 0


       #print(os.environ['LD_LIBRARY_PATH'])
       #print(os.environ['NLS_LANG'])

       # 连接数据库
       # connect = cx.connect(user+'/'+pwd+'@'+host+'/'+sid)
       connect = cx.connect('{}/{}@{}/{}'.format(ora_user, ora_pwd, ora_host, ora_sid))
       print('连接数据库成功!!')

       # 获取数据日期
       data_date = data_date
       print(data_date)

       ##切换到脚本执行目录
       os.chdir(exe_path)

       # 获取sql执行并导出到excel,可能是多条sql,需要分别执行
       fi = 0
       for f_sql in sql_files.split(','):
           start = datetime.datetime.now()

           ##print(type(f_sql))
           cursor = connect.cursor()
           print(f_sql)
           with open(f_sql, 'r', encoding='utf-8') as f:
               sqls = f.read()

           j = 0
           
           wb = Workbook()
           for sql in sqls.split(';'):
               sql = sql.strip()
               if not sql:
                   break

               ##替换变量 sql中的变量必须是 &date 或 &yyyymmdd
               # sql = sql.lower()
               sql = sql.replace('&date', data_date)
               sql = sql.replace('&yyyymmdd', data_date)
               ##sql = sql.decode('utf-8')
               print('-----------------------------------------开始执行sql:\n', sql)
               cursor.execute(sql)
               print('-----------------------------------------执行sql成功\n')

               print('-----------------------------------------开始导出excel\n')
               
               
               sht = wb.create_sheet(title='Mysheet'+str(j),index=j)
               
               ## 如果变量设置了sheet名称,则修改
               if len(excel_sheet_names) >= fi + 1:
                   if len(excel_sheet_names[fi]) >= j + 1 and excel_sheet_names[fi][j].strip():
                       sht.title = excel_sheet_names[fi][j]
               print(sht.title)

               # 处理表头
               i = 1

               for col in cursor.description:
                   sht.cell(1, i).value = col[0]
                   i += 1
                   
               ##print('11')
               # 写数据
               ##offset = 2
               while True:
                   results = cursor.fetchmany(10000)
                   if not results:
                       break
                   resLen = len(results)
                   for res in results:
                       sht.append(res)
               ##在开头插入一列
               ###print('1111')         
               sht.insert_cols(1)
               ###print('22')
               ###print(sht.max_row)
               # 给第一列写入行标
               for k in range(2, sht.max_row+1):
                   sht.cell(k,1).value = k - 1

               # 获取导出文件目录及文件名              
               exl_file = f_sql.split(os.sep)[-1].split('.')[0] + '_' + data_date + '.xlsx'
               if excel_path.strip():
                   exl_file = excel_path + os.sep + exl_file
               print(exl_file)
               j += 1

           wb.save(exl_file)
           ##wb.close()
           cursor.close()
           print('-----------------------------------------导出excel成功!\n')

           print('导出excel 花费时间:', datetime.datetime.now() - start, '\n\n')

           fi += 1
       ret = 1            
   except Exception as e:
       print('Error:', e)
       ##raise e
   finally:
       connect.close()
       return ret


def get_zip_file(input_path, wd, results):
   """
   在目录下查找包含关键字的文件
   input_path: 目录
   wd: 关键字
   results:查找结果列表
   """
   try:
       files = os.listdir(input_path)
       for filename in files:
           fp = os.path.join(input_path, filename)
           if os.path.isfile(fp) and wd in filename:
               # 使用相对路径
               results.append('.' + os.sep + filename)
   except Exception as e:
       print(e)
       raise e


def zip_files(input_path, output_zip_name, data_date):
   """
   压缩文件
   input_path:目录
   output_zip_name:zip文件名
   data_date :数据日期
   """
   try:
       ret = None
       start = datetime.datetime.now()
       filelists = []
       wd = data_date + '.xlsx'
       get_zip_file(input_path, wd, filelists)
       
       if len(filelists) > 0:
           print('-----------------------------------------开始压缩文件')
           f = zipfile.ZipFile(input_path + os.sep + output_zip_name, 'w', zipfile.ZIP_DEFLATED)
       
           # 切换到打包目录
           os.chdir(input_path)
           for file in filelists:
               f.write(file)

           print(input_path + os.sep + output_zip_name)
           print('-----------------------------------------压缩文件完成')
           print('压缩文件 花费时间:', datetime.datetime.now() - start, '\n\n')
           f.close()
           print('压缩文件名:',input_path + os.sep + output_zip_name)
           ret = output_zip_name
           return ret
   except Exception as e:
       print('Error:', e)
       raise e
   finally:
       return ret

def send_mail(zipfile):
   # 构造一个MIMEMultipart对象代表邮件本身
   msg = MIMEMultipart()

   msg['From'] = mail_from
   msg['To'] = mail_to
   msg['Cc'] = mail_cc
   weeks = str(get_weeks())
   mail_sub = mail_subject.replace('{weeks}',weeks)
   msg['Subject'] = Header(mail_sub,'utf-8').encode()
   msg.attach(MIMEText(mail_body,'html','utf-8'))

   # 切换到zip文件目录
   os.chdir(excel_path)
   #二进制模式读取zip文件
   with open(zipfile,'rb') as f:
       zipf = zipfile.split('.')[0]
       # 设置附件的MIME和文件名,这里是zip类型:
       mime = MIMEBase('zip', 'zip', filename=zipfile)
       # 加上必要的头信息:
       #mime.add_header('Content-Disposition', 'attachment', file_name=Header(zipfile,'utf-8').encode())
       #mime.add_header('Content-Disposition', 'attachment', filename=('gbk', '', zipfile))
       #mime.add_header('Content-Disposition', 'attachment', filename=('utf-8', '', zipfile))   ##这种方式发送,邮件客户端收到邮件,中文附件名还是可能有乱码
       mime.add_header('Content-Disposition', 'attachment', filename= Header(zipfile,'gbk').encode() ) ## ##中文附件名称outlook,网页,foxmail均显示正常
       #mime.add_header('Content-ID', '<0>')
       #mime.add_header('X-Attachment-Id', '0')
       # 把附件的内容读进来:
       mime.set_payload(f.read())
       # 用Base64编码
       encoders.encode_base64(mime)
       #添加到MIMEMultipart
       msg.attach(mime)
   try:
       print('-----------------------------------------发送邮件开始')
       s = smtplib.SMTP()
       s.connect(mail_server,mail_port)
       s.login(mail_from,mail_pass)
       ##sendmail的第2个参数邮件地址是个list,所以将字符串用split转为list
       s.sendmail(mail_from,mail_to.split(',')+mail_cc.split(','),msg.as_string())
       s.quit()
       print('-----------------------------------------发送邮件结束')
   except smtplib.SMTPException as e:
       print('发送失败:',e)
   
   return
   
def main():
   try:
       print('--Program begin',datetime.datetime.now(),'--------------------------------------')
       print(os.environ['LD_LIBRARY_PATH'])
       print(os.environ['NLS_LANG'])
       data_date = get_data_date()
       oret = ora_to_excel(data_date)
       ##oret = 1
       
       weeks = str(get_weeks())
       
       zfile_name = zipfile_name.replace('{weeks}',weeks)
       
       output_zip_name = zfile_name + '_' + data_date + '.zip'
       

       if oret == 1:
           zfile = zip_files(excel_path, output_zip_name, data_date)
           ###zfile = output_zip_name
           if zfile:
               if is_mail == 1:
                   send_mail(zfile)

       print('--Program End', datetime.datetime.now(), '--------------------------------------')
   except Exception as e:
       print(e)
##    finally:
##        end = input('请输入回车键结束!')


if __name__ == '__main__':
   main()




6.配置crontab定时
32 8 * * * . ~/.bash_profile; /root/python/prod/test/bin/test_ora_2_excel.py  >> /root/python/prod/test/logs/test_ora_2_excel.log 2>&1

上面配置中 . ~/.bash_profile; 是为了让配置的环境变量LD_LIBRARY_PATH,NLS_LANG 生效

遇到的问题

1、在windows编写的py脚本,上传到linux主机,并分配执行权限后,在目录下执行 python xxx.py 可以正常执行, 但是执行 ./xxx.py 或 xxx.py 报错:: No such file or directory
解决办法:vi xxx.py 打开文件,输入 : ,然后在底线命令模式下,输入 set ff 显示 fileformat=dos ,再在命令模式下数据 set ff=unix 回车 ,然后保存文件,再执行文件正常      
2、发送邮件带中文名附件时,在邮箱网页收取显示名称正常,用foxmail客户端收取时显示部分乱码
解决办法:
   将mime.add_header('Content-Disposition', 'attachment', filename=zipfile)
   替换为下面两种写法后显示正常
   mime.add_header('Content-Disposition', 'attachment', file_name=Header(zipfile,'utf-8').encode())
   mime.add_header('Content-Disposition', 'attachment', filename=('gbk', '', zipfile))
3、手动执行python脚本执行正常,用crontab调用时环境变量不生效,用下面的两种写法设置环境变量也没有成功:

os.environ[‘LD_LIBRARY_PATH’] = lib_path
os.environ[‘NLS_LANG’] = nls_lan

os.environ.setdefault(“LD_LIBRARY_PATH”, lib_path)
os.environ.setdefault(“NLS_LANG”, nls_lan)

临时解决办法:
  crontab 调用脚本时,前面添加 . ~/.bash_profile 让环境变量生效,
  或者可以写一个shell脚本,内容包含 . ~/.bash_profile 和 执行python脚本 ,然后crontab调用这个shell
4、用户反映用outlook客户端收取邮件时,附件名显示为 未命名的附件***.txt
解决办法:
  mime.add_header('Content-Disposition', 'attachment', filename= Header(zipfile,'gbk').encode() ) ## ##中文附件名称outlook,网页,foxmail均显示正常
5、pip安装模块时报错:WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by ‘SSLError(SSLError(1, ‘[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:852)’),)’: /simple/openpyxl/
解决办法:使用国内镜像安装
[root@Vsens bin]# pip install openpyxl -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
Looking in indexes: http://pypi.douban.com/simple
Collecting openpyxl
  Downloading http://pypi.doubanio.com/packages/f5/39/942a406621c1ff0de38d7e4782991b1bac046415bf54a66655c959ee66e8/openpyxl-2.6.3.tar.gz (173kB)
     |████████████████████████████████| 174kB 2.0MB/s 
Collecting jdcal (from openpyxl)
  Downloading http://pypi.doubanio.com/packages/f0/da/572cbc0bc582390480bbd7c4e93d14dc46079778ed915b505dc494b37c57/jdcal-1.4.1-py2.py3-none-any.whl
Collecting et_xmlfile (from openpyxl)
  Downloading http://pypi.doubanio.com/packages/22/28/a99c42aea746e18382ad9fb36f64c1c1f04216f41797f2f0fa567da11388/et_xmlfile-1.0.1.tar.gz
Installing collected packages: jdcal, et-xmlfile, openpyxl
  Running setup.py install for et-xmlfile ... done
  Running setup.py install for openpyxl ... done
Successfully installed et-xmlfile-1.0.1 jdcal-1.4.1 openpyxl-2.6.3

  

你可能感兴趣的:(python3,oracle相关)