def split_file(file_path, log_level, split_count=os.cpu_count()): """ 有可能每个系统的换行符不一样, 可能会造成数据分割之后的数据"变多" 例如: 如果有一行的数据为: CHEN^M GUIDE,,,VSA,USER,NAME, Python 会把 ^M 当成换行符, 从而数据变成两行: 1. CHEN 2. GUIDE,,,VSA,USER,NAME, 这个只能当做坏数据处理, 使用 error.log 手动导入即可, 不过这种情况应该不会很多, so do not worry about it. :param file_path: 需要分割文件的文件路径 :param log_level: log level :param split_count: 需要分成多少份 :return: Boolean, 成功或者失败 """ try: # setting Logger object logger = Logger(log_level=log_level) # debug.log 应该包含所有的日志信息, 如果命令行没有指定 log_level 为3 的话, debug信息 不会出现在 debug.log 中 debug_logger = logger.get_logger(logger_name='debug_logger') # error.log 只包含错误信息 error_logger = logger.get_logger(logger_name='error_logger') # normalization file path file_path = '\ '.join(file_path.split()) ColorFormatter.info('开始计算需要分割的文件 "{}" 的行数'.format(file_path)) debug_logger.info('开始计算需要分割的文件 "{}" 的行数'.format(file_path)) # 计算 csv 文件的行 cmd = "wc -l %s | awk '{print $1}'" % file_path debug_logger.debug('开始运行 {} 命令'.format(cmd)) p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True) (stdout, stderr) = p.communicate() return_code = p.returncode if return_code != 0: ColorFormatter.error('计算需要分割文件 "{}" 行数时出错, 出错信息为: {}'.format( file_path, stderr.strip())) debug_logger.error('计算需要分割文件 "{}" 行数时出错, 出错信息为: {}'.format( file_path, stderr.strip())) error_logger.error('计算需要分割文件 "{}" 行数时出错, 出错信息为: {}'.format( file_path, stderr.strip())) ColorFormatter.fatal( 'Please contact author or look through logs/error.log file to examine error place' ) # 异常退出 sys.exit(1) else: ColorFormatter.info('计算需要分割文件 "{}" 行数结束, 一共 {} 行'.format( file_path, str(stdout.strip()))) debug_logger.debug('计算需要分割文件 "{}" 行数命令结果, 返回码: {}, 输出为: {}'.format( file_path, return_code, stdout.strip())) file_line = int(stdout.strip()) debug_logger.info('计算需要分割文件 "{}" 行数结束, 一共 {} 行'.format( file_path, str(file_line))) if file_line > 0: per_file_line = int(file_line / split_count) else: per_file_line = 1 # 计数器 flag = 0 # 文件名 name = 1 # 存放数据 data_list = [] ColorFormatter.info('开始分割文件') debug_logger.info('开始分割文件') file_path = ''.join(file_path.split('\\')) try: with open(file_path, 'r') as r: for line in r: flag += 1 data_list.append(line) # 如果等于设置的每个文件的行数, 就分一个文件, 直到全部分完 if flag == per_file_line: split_file = os.path.join( SPLIT_PATH, 'split_file_{}.txt'.format(str(name))) with open(split_file, 'w+') as f: for data in data_list: f.write(data) ColorFormatter.info("文件{}: {} 分割完成".format( name, split_file)) debug_logger.info("文件{}: {} 分割完成".format( name, split_file)) # 分割一次 重新初始化一次 name += 1 flag = 0 data_list = [] except Exception as e: ColorFormatter.error('分割文件 {} 时候出错, 出错信息为: {}'.format(name, e)) ColorFormatter.fatal('Please contact author') debug_logger.error('分割文件 {} 时候出错, 出错信息为: {}'.format(name, e)) error_logger.error('分割文件 {} 时候出错, 出错信息为: {}'.format(name, e)) try: if name != split_count: # 如果不相等, 就是还有多出来的数据 last_split_file = os.path.join( SPLIT_PATH, 'split_file_{}.txt'.format(str(name - 1))) # 处理最后一批 with open(last_split_file, 'a+') as f_target: for data in data_list: f_target.write(data) except Exception as e: ColorFormatter.error('分割文件 合并最后一个文件时出错, 出错信息为: {}'.format(e)) ColorFormatter.fatal('Please contact author') debug_logger.error('分割文件 合并最后一个文件时出错, 出错信息为: {}'.format(e)) error_logger.error('分割文件 合并最后一个文件时出错, 出错信息为: {}'.format(e)) ColorFormatter.success("文件 {} 分割完成".format(file_path)) debug_logger.info("文件 {} 分割完成".format(file_path)) return True except: return False
def main(): banner() opts = CmdLineParser().cmd_parser() python_version = platform.python_version() if not python_version.startswith('3'): ColorFormatter.fatal('此脚本只能运行在 Python3 之中') sys.exit(1) if opts.show_config: space = "-" line_len_list = [] line_1 = "| # ['database']" line_2 = "| DATABASE_USER = '******'".format(DATABASE_USER) line_3 = "| DATABASE_PASSWORD = '******'".format(DATABASE_PASSWORD) line_4 = "| DATABASE = '{}'".format(DATABASE) line_5 = "| TABLE_NAME = '{}'".format(TABLE_NAME) line_len_list.append(len(line_2)) line_len_list.append(len(line_3)) line_len_list.append(len(line_4)) line_len_list.append(len(line_5)) line_len_list.sort(reverse=True) longest_line = line_len_list[0] print(colored('Show database config', 'white')) print(colored(space * (longest_line + 2), 'white')) chajia = longest_line - len(line_1) print(colored("{}{} |".format(line_1, chajia * ' '), 'white')) chajia = longest_line - len(line_2) print(colored("{}{} |".format(line_2, chajia * ' '), 'white')) chajia = longest_line - len(line_3) print(colored("{}{} |".format(line_3, chajia * ' '), 'white')) chajia = longest_line - len(line_4) print(colored("{}{} |".format(line_4, chajia * ' '), 'white')) chajia = longest_line - len(line_5) print(colored("{}{} |".format(line_5, chajia * ' '), 'white')) print(colored(space * (longest_line + 2), 'white')) # 安全退出程序 sys.exit(0) # control log level, default is 1 log_level = str(opts.log_level) if log_level == '1': log_level = 'WARNING' elif log_level == '2': log_level = 'INFO' elif log_level == '3': log_level = 'DEBUG' else: log_level = 'DEBUG' # setting Logger object logger = Logger(log_level=log_level) # debug.log 应该包含所有的日志信息, 如果命令行没有指定 log_level 为3 的话, debug信息 不会出现在 debug.log 中 debug_logger = logger.get_logger(logger_name='debug_logger') # error.log 只包含错误信息 error_logger = logger.get_logger(logger_name='error_logger') # 初始化数据库 try: database = Database(host='127.0.0.1', database=opts.database_name, logger=logger) except pymysql.err.InternalError as e: ColorFormatter.error('Init database InternalError: {}'.format(e)) debug_logger.error('Init database InternalError: {}'.format(e)) error_logger.error('Init database InternalError: {}'.format(e)) if 'Unknown database' in str(e): ColorFormatter.fatal( 'Please open the lib/settings.py file and move to database section content, modify ' 'DATABASE parameter and try again, for example see below: ') error_string = """ ---------------------------------------- | # ['database'] | | DATABASE_USER = '******' | | DATABASE_PASSWORD = '******' | | DATABASE = 'testdb' | | TABLE_NAME = 'test_table' | ---------------------------------------- """ ColorFormatter.fatal(error_string) sys.exit(1) except Exception as e: ColorFormatter.error('Init database Unknow Error: {}'.format(e)) debug_logger.error('Init database Unknow Error: {}'.format(e)) error_logger.error('Init database Unknow Error: {}'.format(e)) sys.exit(1) # 运行程序 try: if not len(sys.argv[1:]): ColorFormatter.fatal( "You failed to provide an option, redirecting to help menu") debug_logger.debug( 'You failed to provide an option, redirecting to help menu') # 停顿2秒之后再显示 help banner time.sleep(2) print() CmdLineParser().cmd_parser(get_help=True) else: if opts.drop_table: prompt_result = ColorFormatter.prompt( 'Are you sure to drop "{}" table?(y/N)'.format( opts.drop_table), opts='y/n') debug_logger.debug('type --drop-table option') if prompt_result == 'y': # result = db.create_table('大爷', ['id', 'name', 'age']) result = database.drop_table(opts.drop_table) debug_logger.warning('input y') if result: ColorFormatter.info('Drop table {} successful'.format( opts.drop_table)) debug_logger.warning('Drop table {} successful'.format( opts.drop_table)) else: ColorFormatter.error('Drop table {} failed'.format( opts.drop_table)) error_logger.error('Drop table {} failed'.format( opts.drop_table)) debug_logger.error('Drop table {} failed'.format( opts.drop_table)) # 执行完 drop table 就正常退出程序 sys.exit(0) if opts.csv_to_database: csv_file_path = opts.csv_to_database if not os.path.isfile(csv_file_path): # 不存在 csv 文件 debug_logger.warning( 'File "{}" not exists'.format(csv_file_path)) ColorFormatter.fatal( 'File "{}" not exists'.format(csv_file_path)) time.sleep(1) ColorFormatter.fatal('Program exit') debug_logger.warning('Program exist') sys.exit(1) else: if opts.fast: start_time = time.time() # 使用多核前, 将大文件进行分割, 分割成 4 份, 再加上不足的那一份 ColorFormatter.info('启用多进程导入数据库') ColorFormatter.info('开始分割原数据文件') # 这里需要再分割前 删除 split 目录下的所有文件 cmd = 'rm -rf split && mkdir split' debug_logger.debug('开始运行 {} 命令'.format(cmd)) p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True) (stdout, stderr) = p.communicate() return_code = p.returncode if return_code != 0 or stderr: ColorFormatter.error( '删除split文件时出错, 出错信息为: {}'.format( stderr.strip())) debug_logger.error( '删除split文件时出错, 出错信息为: {}'.format( stderr.strip())) error_logger.error( '删除split文件时出错, 出错信息为: {}'.format( stderr.strip())) # 异常退出 sys.exit(1) else: ColorFormatter.success('初始化split文件夹成功') debug_logger.debug( '初始化split文件夹成功, 返回码: {}, 输出为: {}'.format( return_code, stdout)) debug_logger.info('初始化split文件夹成功') # 开始分割 split_result = split_file(csv_file_path, log_level) if split_result: ColorFormatter.success('原数据文件分割完成') debug_logger.info('原数据文件分割完成') else: ColorFormatter.error('原数据文件分割失败') ColorFormatter.fatal('异常, 退出程序') debug_logger.error('原数据文件分割失败') error_logger.error('原数据文件分割失败') sys.exit(1) # 分割完成 # 多进程, 使用多核一起干 table_name = TABLE_NAME process_list = [] for i in range(1, os.cpu_count() + 1): process = Process(target=handle_data_to_database, args=( i, database, table_name, debug_logger, error_logger, opts.skip_error, )) process_list.append(process) process.start() # 计算 csv 文件的总行 cmd = "wc -l split/split_file_* | grep total | awk '{print $1}'" debug_logger.debug('开始运行 {} 命令'.format(cmd)) p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True) (stdout, stderr) = p.communicate() try: total_lines = int(stdout.strip()) except: total_lines = 0 return_code = p.returncode if return_code != 0 or stderr: ColorFormatter.error( '计算文件 "{}" 总行数 时出错, 出错信息为: {}'.format( csv_file_path, stderr.strip())) debug_logger.error( '计算文件 "{}" 总行数 时出错, 出错信息为: {}'.format( csv_file_path, stderr.strip())) error_logger.error( '计算文件 "{}" 总行数 时出错, 出错信息为: {}'.format( csv_file_path, stderr.strip())) ColorFormatter.fatal( 'Please contact author or look through logs/error.log file to examine error place' ) # 异常退出 sys.exit(1) else: ColorFormatter.info( '计算文件 "{}" 总行数结束, 一共 {} 行'.format( csv_file_path, str(total_lines))) debug_logger.debug( '计算文件 "{}" 总行数命令结果, 返回码: {}, 输出为: {}'.format( csv_file_path, return_code, str(total_lines))) debug_logger.info( '计算需要分割文件 "{}" 行数结束, 一共 {} 行'.format( csv_file_path, str(total_lines))) # 进度条 对象 progress_bar = ProgressBar(total_line=total_lines, description='正在插入数据: ') while True: # 每 2 秒读一次总数文件 time.sleep(2) all_progress = 0 for suffix_name in range(1, os.cpu_count() + 1): record_line_file = os.path.join( SPLIT_PATH, 'record_line_{}.txt'.format( str(suffix_name))) try: with open(record_line_file, 'r') as r: per_line = r.readline() all_progress += int(per_line) except: # 有可能开始还没有读到文件 pass # 打印进度条 progress_bar.handle_multiprocessing_progress( current_progress=all_progress) if all_progress >= total_lines: # 有可能这个判断不准确, 然后卡在这里 break for _ in process_list: _.join() end_time = time.time() print( colored('总共用时: {}'.format(end_time - start_time), 'white')) # 清除 split 文件夹 cmd = "rm -rf split" debug_logger.debug('开始运行 {} 命令'.format(cmd)) p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True) (stdout, stderr) = p.communicate() return_code = p.returncode if return_code != 0 or stderr: ColorFormatter.error( '清除 split 文件夹时出错, 出错信息为: {}'.format( csv_file_path, stderr.strip())) debug_logger.error( '清除 split 文件夹时出错, 出错信息为: {}'.format( csv_file_path, stderr.strip())) error_logger.error( '清除 split 文件夹时出错, 出错信息为: {}'.format( csv_file_path, stderr.strip())) ColorFormatter.fatal( 'Please contact author or look through logs/error.log file to examine error place' ) # 异常退出 sys.exit(1) else: ColorFormatter.info('清除 split 文件夹成功') debug_logger.debug('清除 split 文件夹成功') ColorFormatter.success('插入数据完成') debug_logger.info('insert data to database done') # 显示汇总信息 # 多进程无法显示 失败 和 成功 的条数, 暂时丢着吧 else: # 单进程, 速度很慢 start_time = time.time() table_name = TABLE_NAME with open(csv_file_path, 'r') as r: # 这里是用, 分隔的 csv文件 line = csv.reader(r, delimiter=',', quoting=csv.QUOTE_NONE) csv_file_path = '\ '.join(csv_file_path.split()) columns_str = '' ColorFormatter.info( '开始计算csv文件 {} 的行数'.format(csv_file_path)) debug_logger.info( '开始计算csv文件 {} 的行数'.format(csv_file_path)) # 计算 csv 文件的行 cmd = "wc -l %s | awk '{print $1}'" % csv_file_path debug_logger.debug('开始运行 {} 命令'.format(cmd)) p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True) (stdout, stderr) = p.communicate() return_code = p.returncode if return_code != 0 or stderr: ColorFormatter.error( '计算csv文件 "{}" 行数时出错, 出错信息为: {}'.format( csv_file_path, stderr.strip())) debug_logger.error( '计算csv文件 "{}" 行数时出错, 出错信息为: {}'.format( csv_file_path, stderr.strip())) error_logger.error( '计算csv文件 "{}" 行数时出错, 出错信息为: {}'.format( csv_file_path, stderr.strip())) # 异常退出 sys.exit(1) else: ColorFormatter.info( '计算 "{}" 文件行数结束, 一共 "{}" 行'.format( csv_file_path, stdout.strip())) debug_logger.debug( '计算 "{}" 文件行数命令结果, 返回码: {}, 输出为: {}'. format(csv_file_path, return_code, stdout.strip())) csv_file_line = int(stdout.strip()) debug_logger.info( '计算 "{}" 文件行数结束, 一共 "{}" 行'.format( csv_file_path, csv_file_line)) # 进度条 对象 progress_bar = ProgressBar( total_line=csv_file_line, description='正在插入数据: ') count = 1 for all_cols in line: # 跳过空行 if all_cols: if count == 1: # 第一行,获取所有数据的列名, 然后创建数据库表 columns_str = ", ".join(all_cols) debug_logger.debug( 'create table {}'.format( table_name)) create_result = database.create_table( table_name=table_name, table_column_list=all_cols) if create_result == 'True': debug_logger.info( 'create table {} successful'. format(table_name)) ColorFormatter.success( '创建表 "{}" 成功'.format( table_name)) elif 'already exists' or 'Duplicate column name' in create_result: debug_logger.warning( 'table "{}" has been existed'. format(table_name)) ColorFormatter.info( '表 "{}" 已经存在'.format( table_name)) else: debug_logger.error( 'create table "{}" failed'. format(table_name)) error_logger.error( 'create table "{}" failed'. format(table_name)) ColorFormatter.error( '创建表 "{}" 失败'.format( table_name)) else: # 不为第一行 # 剔除 \r\n 的列 all_cols = escape_file_string(all_cols) # 将每一列的数据拼成一行 data_list = ', '.join([ '"{}"'.format(_) for _ in all_cols ]) database.insert_data( table_name=table_name, column_list=columns_str, data_list=data_list, skip_error=opts.skip_error) progress_bar.handle_progress() count += +1 # 事务提交, 插入数据 debug_logger.debug('execute insert sql commit') database.execute_commit() debug_logger.info( 'execute insert sql commit successful') # 输出插入数据总共用时 end_time = time.time() print( colored( '总共用时: {:.2f}秒'.format(end_time - start_time), 'white')) r.close() ColorFormatter.success('插入数据完成') debug_logger.info('insert data to database done') # 显示汇总信息 ColorFormatter.info( '总共插入 "{}" 条数据, 成功 "{}" 条, 失败 "{}" 条'.format( database.insert_total_count, database.insert_success_count, database.insert_failed_count)) debug_logger.info( '总共插入 "{}" 条数据, 成功 "{}" 条, 失败 "{}" 条'.format( database.insert_total_count, database.insert_success_count, database.insert_failed_count)) # 安全退出 sys.exit(0) if opts.txt_to_database: txt_files = opts.txt_to_database table_name = opts.table_name skip_error = opts.skip_error # 用来分隔 txt 文件的列的 separator = opts.separator column_list = opts.column_list if not column_list: ColorFormatter.error( 'Must with -c option if you want to use txt-to-database option' ) ColorFormatter.fatal('sleep 2s redirect help page') time.sleep(2) print() CmdLineParser().cmd_parser(get_help=True) if not table_name: table_name = TABLE_NAME if separator == '\\t': separator = '\t' insert_txt_to_database(txt_files=txt_files, separator=separator, database_obj=database, column_list=column_list, debug_logger=debug_logger, error_logger=error_logger, table_name=table_name, skip_error=skip_error) # 正常退出程序 sys.exit(0) if opts.mysql_command: cmd = opts.mysql_command dict_result = database.execute_command(cmd) for i in dict_result: print(i) if opts.clean_cache: ColorFormatter.warning('Clean log cache') debug_logger.warning('Clean log cache') try: os.remove(os.path.join(LOG_PATH, 'error.log')) os.remove(os.path.join(LOG_PATH, 'debug.log')) except: pass ColorFormatter.success('Clean log cache successful') debug_logger.info('Clean log cache success') # 执行完 clean log cache 就正常的退出程序 sys.exit(0) except KeyboardInterrupt as e: ColorFormatter.fatal('user abort') debug_logger.error('user abort: {}'.format(e)) error_logger.error('user abort: {}'.format(e)) except Exception as e: ColorFormatter.error('Total program Unknow error: {}, type: {}'.format( e, type(e))) ColorFormatter.fatal('Please contact author') debug_logger.error('Total program Unknow error: {}, type: {}'.format( e, type(e))) error_logger.error('Total program Unknow error: {}, type: {}'.format( e, type(e)))