python之数据文件批量清洗,入库
因为最近有一个数据接入清洗的项目,别的工具用起来有些觉得不太顺手,就学着写了一个数据清洗入库的通用模板,节省一些工作量。主要流程为:数据库连接---文件批量导入----文件批量清洗---根据清洗后的文件在数据库自动创表----清洗后的数据直接入库。
主要用到的模块为pandas、cx_Oracle、os、datetime、sqlalchemy。
```python
#连接数据库
import cx_Oracle
import pandas as pd
print("\033[10;31;50m准备连接数据库\033[0m")
try:
conn=cx_Oracle.connect('数据库账号','数据库登录密码','host/数据库名称')
cursor=conn.cursor() #创建游标
print("\033[10;31;50m数据库连接成功\033[0m")
print(' ')
except:
print("\033[10;31;50m数据库连接失败\033[0m")
print(' ')
数据库连接好之后开始从本地批量接入需要清洗的数据文件。这里有设定清洗规则码表,一个字段对应一种或多种清洗的规则,码表的大致格式如下,可以根据自己的需求来定义清洗规则:
|表格名称|字段中文名称|字段英文名称|字段顺序|清洗规则|清洗规则中文
|表1|字段1|字段1名称| 1 |1 | 处理空格
|表1|字段2|字段2名称| 2 |1, 3| 处理空格,统一大小写
|表1|字段3|字段3名称| 3 |4 | 日期格式转换
……
|表n|字段n|字段n名称| n |1,2,3,4,5,6,7…… | ……
```python
#批量读入数据文件
import os
import shutil
import re
import glob
import time
from datetime import datetime
print("\033[10;31;50m批量接入文件\033[0m")
filepath=r'E:/测试/样例数据/' #该地址为文件夹地址
newpath=r'E:/测试/清洗后的数据文件/'
fieldcode=pd.read_excel('E:/测试/fieldcode.xlsx',index=False) #导入清洗规则码表
fieldcode['表名']=fieldcode['表名'] + '.xlsx' #加上后缀
files=os.listdir(filepath)
files_xlsx=list(filter(lambda x:x[-5:] == '.xlsx',files)) #将文件夹中的所有表的表名读过来,存为列表
在批量读取文件的这一块代码中有些包是不需要的,是我在写的时候没改之前用到的,可以自行选择。
文件导入之后开始数据清洗程序。
print("\033[10;31;50m开始数据文件清洗程序\033[0m")
for i in files_xlsx:
for i in files_xlsx:
data=pd.read_excel(filepath+i,index=False).fillna(value='null')#将所有需要清洗的文件的空值全部填充为null
for j in data.columns:
etl_rule=fieldcode[fieldcode['表名']==i][fieldcode['字段英文名']==j]
#将清洗规则转为list然后进行遍历
dc_rule=etl_rule["清洗规则"].map(lambda x:list(str(x)))
for k in dc_rule:
for m in k[:]:
if m==",":
k.remove(m) #移除list中的","元素
else:
m
for n in dc_rule:
for p in n:
if p == '1': #去空格
data[j]=data[j].map(lambda x:''.join(str(x).split()))
elif p == '3': #统一大小写
data[j]=data[j].map(lambda x:str(x).upper())
elif p == '4': #日期格式转换,将yyyymmddhhmiss转为以yyyy-mm-dd HH:MI:SS
data[j]=data[j].map(lambda x:datetime.strptime(str(x),"%Y%m%d%H%M%S").strftime("%Y-%m-%d %H:%M:%S"))
elif p == '5': #匹配验证手机号或固话,并把固话格式统一,将固话中间的横杠全部去掉
data[j]=data[j].map(lambda x:(''.join(list([x[0] for x in re.findall(r'((?,str(x))])).replace('-',''))) #join是去除list外面的中括号,list([x[0]是取正则后的第一个元素,replace是将固话格式统一
elif p == '6': #时间戳转为日期格式
data[j]=data[j].map(lambda x:time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(float(x/1000))))
elif p == '7': #正则匹配清洗身份证号
data[j]=data[j].map(lambda x:''.join(list([x[0] for x in re.findall(r'((?,str(x))])))
elif p == '8': #正则匹配仅保留数字
data[j]=data[j].map(lambda x:(''.join(re.findall(r'\d+\.?\d*',str(x)))))
else:
data[j]=data[j]
write=pd.ExcelWriter(newpath+i)
data.to_excel(writer,sheet_name='清洗后的表',index=False) #将清洗后的文件数据另存到本地
writer.close()
print(i+':写入成功')
数据清洗之后开始在数据库中创建表。
print("\033[10;31;50m根据清洗后的文件在数据库建表\033[0m")
#从文件夹中导入数据并在数据库中建表
for i in files_xlsx:
new_data=pd.read_excel(newpath+i,index=False)
columns=new_data.columns.tolist()
types=new_data.ftypes
field=[] #用来接收字段名称的列表
table=[] #用来接收字段名称和字段类型的列表
for item in columns: #开始对字段类型进行转换
if 'int64' in types[item]:
char=item + 'VARCHAR(255)'
elif 'float64' in types[item]:
char=item + 'NUMBER'
elif 'obiect' in types[item]:
char=item + 'VARCHAR(255)'
elif 'datetime' in types[item]:
char=item + 'Date'
else:
char=item + 'VARCHAR(255)'
table.append(char)
fields.append(item)
tables=','.join(table)
fields=','.join(field)
#组建建表语句
table_sql='create table nb_dc_'+ i.split('.')[0]+' ('+tables+' )' #在这里面创建数据库表时最后不能带分号
#开始创建数据库表
print("表:nb_dc_" + i.split('.')[0]+',开始创建___')
cursor.execute(table_sql)
conn.commit()
print("表:nb_dc_" + i.split('.')[0]+',创建成功')
#用于测试删表时的操作
#drop_table_sql='drop table nb_dc_'+i.split('.')[0]
#cursor.execute(drop_table_sql)
#conn.commit()
数据库建表之后,将清洗后的数据批量导入。
print("\033[10;31;50m将清洗好的数据导入数据库\033[0m")
from sqlalchemy import create_engine
engine=create_engine("oracle://数据库名称:数据库密码@host/数据库名称",encoding='utf-8',echo=True)
#将清洗好的数据导入到数据库中
for i in files_xlsx:
new_data=pd.read_excel(newpath+i,index=False)
new_data.to_sql("nb_dc_"+i.split('.')[0],engine,index=False,if_exists='append',chunksize=10000)#如果表在数据库中不存在就新建表,已存在就直接导入数据,chunksize=10000是指将数据分批次导入,一次导入10000条
print("\033[10;31;50m数据导入成功\033[0m")
print("表nb_dc_"+i.split('.')[0]+"导入成功")
整个流程大致如上,创建表的那个代码模块其实可以单独放,以防运行报错的时候,不能再重跑代码。
**********************************************************************************************************
因为实际项目中,数据的清洗量都会很大,所以可以考虑采用多线程的方式提高数据的清洗、入库效率。
#连接数据库
import cx_Oracle
import pandas as pd
import os
import re
import time
from time import ctime
from datetime import datetime
from sqlalchemy import create_engine
import threading
engine=create_engine("oracle://数据库名称:数据库密码@host/数据库名称",encoding='utf-8',echo=True)
print("\033[10;31;50m准备连接数据库\033[0m")
try:
conn=cx_Oracle.connect('数据库账号','数据库登录密码','host/数据库名称')
cursor=conn.cursor() #创建游标
print("\033[10;31;50m数据库连接成功\033[0m")
print(' ')
except:
print("\033[10;31;50m数据库连接失败\033[0m")
print(' ')
#批量读入数据文件
print("\033[10;31;50m批量接入文件\033[0m")
filepath=r'E:/测试/样例数据/' #该地址为输入文件地址
newpath=r'E:/测试/清洗后的数据文件/' #该地址为处理后的数据文件地址
fieldcode=pd.read_excel('E:/测试/fieldcode.xlsx',index=False) #导入清洗规则码表
fieldcode['表名']=fieldcode['表名'] + '.xlsx' #加上后缀
files=os.listdir(filepath)
files_xlsx=list(filter(lambda x:x[-5:] == '.xlsx',files)) #将文件夹中的所有表的表名读过来,存为列表
#统一文本中的所有标点符号的方法是去除所有半角全角符号,只保留字母、数字、中文
def remove_punctuation(line):
rule=re.compile(r"[^a-zA-Z0-9\u4e00-\u9fa5]")
line=rule.sub('|',line)
return line
#读取文件写入并开始清洗
print("\033[10;31;50m开始数据文件清洗程序\033[0m")
def readfile(filepath,newpath,filename,fieldcode):
i=filepath+filename
data=pd.read_excel(i,index=False).fillna(value='null')#将所有需要清洗的文件的空值全部填充为null
for j in data.columns:
etl_rule=fieldcode[fieldcode['表名']==filename][fieldcode['字段英文名']==j]
#将清洗规则转为list然后进行遍历
dc_rule=etl_rule["清洗规则"].map(lambda x:list(str(x)))
for k in dc_rule:
for m in k[:]:
if m==",":
k.remove(m) #移除list中的","元素
else:
m
for n in dc_rule:
for p in n:
if p == '1': #去空格
data[j]=data[j].map(lambda x:''.join(str(x).split()))
elif p == '3': #统一大小写
data[j]=data[j].map(lambda x:str(x).upper())
elif p == '4': #日期格式转换,将yyyymmddhhmiss转为以yyyy-mm-dd HH:MI:SS
data[j]=data[j].map(lambda x:datetime.strptime(str(x),"%Y%m%d%H%M%S").strftime("%Y-%m-%d %H:%M:%S"))
elif p == '5': #匹配验证手机号或固话,并把固话格式统一,将固话中间的横杠全部去掉
data[j]=data[j].map(lambda x:(''.join(list([x[0] for x in re.findall(r'((?,str(x))])).replace('-',''))) #join是去除list外面的中括号,list([x[0]是取正则后的第一个元素,replace是将固话格式统一
elif p == '6': #时间戳转为日期格式
data[j]=data[j].map(lambda x:time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(float(x/1000))))
elif p == '7': #正则匹配清洗身份证号
data[j]=data[j].map(lambda x:''.join(list([x[0] for x in re.findall(r'((?,str(x))])))
elif p == '8': #正则匹配仅保留数字
data[j]=data[j].map(lambda x:(''.join(re.findall(r'\d+\.?\d*',str(x)))))
#elif p=='9': #把所有的符号都替换成|,然后把一行数据拆分多行
# data[j]=data[j].map(lambda x:remove_punctuation(x))
# end_data=data.drop(j,axis=1).join(data[j].str.split('|',expand=True).stack().reset_index(level=1,drop=True).rename(j))
else:
pass#data[j]=data[j]
write=pd.ExcelWriter(newpath+filename)
data.to_excel(writer,sheet_name='清洗后的表',index=False) #将清洗后的文件数据另存到本地
#当表格中有需要用到第9个清洗规则的再把数据文件存为这个,因为第九个规则没有生效的时候,数据框名称不会变
#end_data.to_excel(writer,sheet_name='清洗后的表',index=False)
writer.close()
print(filename+':写入成功')
#启用多线程清洗数据
files_xlsx
threads=[]
files=range(len(files_xlsx))
#创建线程
for i in files: #通过文件数来设定线程的数量
start_time=time.time()
t1=threading.Thread(target=readfile(filepath,newpath,files_xlsx[i],fieldcode),args=(files_xlsx[i],))
threads.append(t1)
if __name__ == '__main__':
#启动线程
for i in files:
threads[i].start()
#阻塞线程
threads[i].join()
#主线程
print("all over %s" %ctime())
end_time=time.time()
time_buff=end_time - start_time #计算总消耗时间
print("共耗时:%s" %time_buff)
#从文件夹中导入数据并在数据库中建表
#建表成功后不能再继续跑这一块的程序,因为数据库中表名已被占用
print("\033[10;31;50m根据清洗后的文件在数据库建表\033[0m")
for i in files_xlsx:
new_data=pd.read_excel(newpath+i,index=False)
columns=new_data.columns.tolist()
types=new_data.ftypes
field=[] #用来接收字段名称的列表
table=[] #用来接收字段名称和字段类型的列表
for item in columns: #开始对字段类型进行转换
if 'int64' in types[item]:
char=item + 'VARCHAR(255)'
elif 'float64' in types[item]:
char=item + 'NUMBER'
elif 'obiect' in types[item]:
char=item + 'VARCHAR(255)'
elif 'datetime' in types[item]:
char=item + 'Date'
else:
char=item + 'VARCHAR(255)'
table.append(char)
fields.append(item)
tables=','.join(table)
fields=','.join(field)
#组建建表语句
table_sql='create table nb_dc_'+ i.split('.')[0]+' ('+tables+' )' #在这里面创建数据库表时最后不能带分号
#开始创建数据库表
print("表:nb_dc_" + i.split('.')[0]+',开始创建___')
cursor.execute(table_sql)
conn.commit()
print("表:nb_dc_" + i.split('.')[0]+',创建成功')
#用于测试删表时的操作
#drop_table_sql='drop table nb_dc_'+i.split('.')[0]
#cursor.execute(drop_table_sql)
#conn.commit()
#将清洗好的数据导入到数据库中
print("\033[10;31;50m将清洗好的数据导入数据库\033[0m")
def insertintoDB(newpath,filename):
new_data=pd.read_excel(newpath+filename,index=False)
new_data.to_sql("nb_dc_"+filename.split('.')[0],engine,index=False,if_exists='append',chunksize=10000)#如果表在数据库中不存在就新建表,已存在就直接导入数据,chunksize=10000是指将数据分批次导入,一次导入10000条
print("\033[10;31;50m数据导入成功\033[0m")
print("表nb_dc_"+filename.split('.')[0]+"导入成功")
#启用多线程插入数据
files_xlsx
threads=[]
files=range(len(files_xlsx))
#创建线程
for i in files: #通过文件数来设定线程的数量
start_time=time.time()
t2=threading.Thread(target=insertintoDB(newpath,files_xlsx[i]),args=(files_xlsx[i],))
threads.append(t2)
if __name__ == '__main__':
#启动线程
for i in files:
threads[i].start()
#阻塞线程
threads[i].join()
#主线程
print("all over %s" %ctime())
end_time=time.time()
time_buff=end_time - start_time #计算总消耗时间
print("共耗时:%s" %time_buff)