处理csv文件时经常会遇到下面的问题:
1. 文件过大(需要进行文件分割)
2. 列异常(列不一致,如原始数据的列为10列,但导出的csv文件有些行是11列,或者4列)
本脚本用于解决此问题。
使用说明:
> python csvtoolkit.py -h
usage: csvtoolkit.py [-h] [-f CSV_FILENAME] [-d DELIMITER_CHAR]
[-n SPLIT_FILE_NUMBERS] [-e OUTPUT_ENCODING] 本脚本用来分割处理csv文件,其中解决了csv文件的列异常问题。
使用示例如下:
python csvtoolkit.py -f "2013.csv" -n 10 -e "utf-8" 示例说明:
要分割的csv文件: 2013.csv
分割成的文件个数:10
分割后的文件使用的字符编码:utf-8
分割文件的字符编码,在简体中文系统中推荐使用的字符编码为“gb18030、utf-8、utf-8-sig”,不要使用gbk或者gb2312 optional arguments:
-h, --help show this help message and exit
-f CSV_FILENAME, --filename CSV_FILENAME
要处理的csv文件名
-d DELIMITER_CHAR, --delimiterchar DELIMITER_CHAR
csv文件的分隔符号
-n SPLIT_FILE_NUMBERS, --splitnumbers SPLIT_FILE_NUMBERS
分割成的文件数
-e OUTPUT_ENCODING, --outputencoding OUTPUT_ENCODING
分割成的文件数
#!/usr/bin/env python3
#coding=utf-8 import os, csv, sys, locale, codecs, chardet, time
from argparse import ArgumentParser, RawTextHelpFormatter #操作系统中默认的文件编码(当文件编码为中文扩展字符集时,统一使用gb18030编码--比gb2312和gbk支持的汉字多,同时兼容gb2312和gbk)
#中文扩展字符集编码列表
chinese_charsetstr='|gbk|gb2312|gb18030|cp936|'
default_chinese_charset='gb18030'
#使用gb18030解决了类似下面的错误:UnicodeDecodeError: 'gbk' codec can't decode byte 0xf8 in position 5902: illegal multibyte sequence
defaultencoding = default_chinese_charset if chinese_charsetstr.find(locale.getpreferredencoding().lower())>0 else locale.getpreferredencoding().lower() def detectfileencoding(filename, filerowcount=None, info_fileobj=None):
#获取文件编码(为空则设置为操作系统默认文件编码,其中中文扩展字符集统一设置为'gb18030'大字符集)
time_start=time.time()
#编码检测结果
detectresult=''
with codecs.open(filename, 'rb') as fobj:
if filerowcount==None:
fcontent = fobj.read()
detectresult = chardet.detect(fcontent)
else:
linenum = 0
maxdetectrownum = 100 if filerowcount > 100 else filerowcount
#初始化要检测编码的内容
fcontent = bytes()
for line in fobj.readlines():
linenum += 1
if linenum < maxdetectrownum:
fcontent += line
else:
break
print_twice("\r\n【字符编码检测】:\r\n字符编码检测行数: %d"%(linenum), fileobj=info_fileobj)
detectresult = chardet.detect(fcontent)
detectencoding=detectresult.get('encoding')
print_twice("检测出来的字符编码: %s"%(detectencoding), fileobj=info_fileobj)
if detectencoding is None:
fileencoding = defaultencoding
else:
fileencoding = default_chinese_charset if chinese_charsetstr.find(detectencoding.lower())>0 else detectencoding
time_end=time.time()
print_twice("检测字符编码耗时: %d s"%(time_end-time_start), fileobj=info_fileobj)
return fileencoding def getfilerowscount(filename):
# 计算文件总行数
count=-1
#, encoding=fileencoding
with codecs.open(filename, 'rb') as fobj:
for count,line in enumerate(fobj):
pass
count += 1
return count def print_twice(output_string, fileobj):
#打印输出两次,“终端输出”和“输出到文件”
print(output_string)
print(output_string, file=fileobj) def spit_csvfile(filename, delimiterchar=",", splitnumbers=0, outputencoding='utf-8' ):
if not os.path.isfile(filename):
print('不存在文件: %s'%(filename))
#退出脚本
sys.exit()
file_path = os.path.split(filename)[0]
short_filename = os.path.basename(os.path.splitext(filename)[0])
file_ext = os.path.splitext(filename)[1]
#日志文件名
info_filename = os.path.join(file_path, short_filename+'-info'+'.txt') #获取文件行数
rowscount = getfilerowscount(filename)
#行号,初始值为1
rowsnum = 1
#列数,初始值为0(处理过程中将以第一行为标准,即以第一行的列数为正确的列数)
columncount = 0
#列正确的文件,生成正常的分割文件: 源文件-0.csv 源文件-1.csv
#列错误的文件: 源文件名-错误列.csv
error_filename = os.path.join(file_path, short_filename+'-错误列'+'.txt')
#打开日志文件
fileobj_info = open(info_filename, 'w+', newline='\r\n', encoding=outputencoding) #获取csv文件信息
statinfo = os.stat(filename)
print_twice('\r\n【源文件信息】: ', fileobj=fileobj_info)
print_twice("源文件名称: %s"%(filename), fileobj=fileobj_info)
print_twice("源文件行数: %s"%(str(rowscount)), fileobj=fileobj_info)
#获取文件编码
file_encoding = detectfileencoding(filename, filerowcount=rowscount, info_fileobj=fileobj_info) print_twice('\r\n【文件处理】:', fileobj=fileobj_info)
print_twice('处理源文件时使用的字符编码: %s'%(file_encoding), fileobj=fileobj_info)
print_twice("源文件大小: %s"%(str(statinfo.st_size//1024//1024)+"M"), fileobj=fileobj_info) print_twice("分割成的文件数:%s"%(str(splitnumbers)), fileobj=fileobj_info)
splitLineCount = rowscount//splitnumbers
print_twice("分割文件的行数:%s"%(str(splitLineCount)), fileobj=fileobj_info) # 可以用一个list包含文件对象列表
# 源文件名-文件序号.csv
fileList = []
fileIndex = 0
print_twice("分割后的文件名:", fileobj=fileobj_info)
while fileIndex < splitnumbers:
filename_tmp=os.path.join(file_path, short_filename+'-'+str(fileIndex)+file_ext)
print_twice(" "+filename_tmp, fileobj=fileobj_info)
file_tmp=codecs.open(filename_tmp, 'w+', encoding=outputencoding)
fileList.append(file_tmp)
fileIndex=fileIndex+1 fileobj_error=codecs.open(error_filename, 'w+', encoding=outputencoding)
print_twice("分割后的文件名(列异常数据):", fileobj=fileobj_info)
print_twice(" %s"%(error_filename), fileobj=fileobj_info) print_twice("分割后文件的字符编码: %s"%(outputencoding), fileobj=fileobj_info) with codecs.open(filename, encoding=file_encoding) as csvfile:
spamreader = csv.reader(csvfile,delimiter=delimiterchar)
for line in spamreader:
# 列数为0时,读取第一行作为准确的列数。
if ( columncount == 0 ):
columncount = len(line)
# 列数不为0时,当前行的列数与其匹配,将匹配的和不匹配的保存到不同的文件。
else:
#列数和第一行的列数匹配,则输出到分割的文件中
if ( columncount == len(line) ):
# 输出到对应文件序号的文件中: 行数“整除”分割行数
if ( rowsnum//splitLineCount > len(fileList)-1 ):
print((','.join(line)), file=fileList[len(fileList)-1])
else:
print((','.join(line)), file=fileList[rowsnum//splitLineCount])
else :
#列数与第一行的列数不匹配,则输出到异常文件中
print((','.join(line)), file=fileobj_error)
rowsnum=rowsnum+1 fileIndex=0
#文件列表中的文件处理:刷新缓存区,关闭文件
while fileIndex < splitnumbers:
if not fileList[fileIndex].closed:
fileList[fileIndex].flush()
fileList[fileIndex].close()
fileIndex=fileIndex+1
#关闭文件
if not fileobj_error.closed:
fileobj_error.flush()
fileobj_error.close()
if not fileobj_info.closed:
fileobj_info.flush()
fileobj_info.close() if __name__ == "__main__":
description="\n本脚本用来分割处理csv文件,其中解决了csv文件的列异常问题。\n使用示例如下:"
description=description+'\npython csvtoolkit.py -f "2013.csv" -n 10 -e "utf-8"'
description=description+'\n\n'+"示例说明:"
description=description+'\n'+"要分割的csv文件: 2013.csv"
description=description+'\n'+"分割成的文件个数:10"
description=description+'\n'+"分割后的文件使用的字符编码:utf-8"
description=description+'\n'+"分割文件的字符编码,在简体中文系统中推荐使用的字符编码为“gb18030、utf-8、utf-8-sig”,不要使用gbk或者gb2312" # 添加程序帮助,程序帮助支持换行符号
parser = ArgumentParser(description=description, formatter_class=RawTextHelpFormatter) # 添加命令行选项 parser.add_argument("-f", "--filename",
dest="csv_filename",
default="",
help="要处理的csv文件名")
parser.add_argument("-d", "--delimiterchar",
dest="delimiter_char",
default=",",
help="csv文件的分隔符号")
parser.add_argument("-n", "--splitnumbers",
dest="split_file_numbers",
default=0,
help="分割成的文件数")
parser.add_argument("-e", "--outputencoding",
dest="output_encoding",
default='utf-8',
help="分割成的文件数") args = parser.parse_args() #try:
spit_csvfile(args.csv_filename, args.delimiter_char, int(args.split_file_numbers), args.output_encoding)
#except:
# print('Error to split csv file:')
# print(sys.exc_info()[0],sys.exc_info()[1])