Время прочтения: 6 мин.
Перед нами была поставлена задача по анализу выгруженной из базы данных информации. Сложность задачи оказалась в том, что получен очень большой многострочный текстовый файл (около 8 Гб, 14,4 млн. строк), который невозможно открыть с помощью общедоступных инструментов (например, Excel, у которого есть ограничение на количество записей в рабочем листе). В связи с этим возникла необходимость разбить его на несколько файлов так, чтобы они содержали заданное количество строк. После успешного решения данной задачи мы решили поделиться использованными методами.
В данной статье мы разберем три метода «нарезки» текстового файла на несколько с заданным количеством строк с помощью языка программирования Python. Далее заданное количество строк будем называть «count_lines».
Идея первого метода нарезки файла, назовем его «sequence_cut (последовательная нарезка)», состоит в том, что считывается строка из входящего файла и записывается в очередной файл. Такая последовательность действий продолжается до тех пор, пока количество записанных строк не превосходит count_lines. Далее делается тоже самое, но уже в новый файл для записи. И так далее. Завершается процесс, когда достигается конец входящего файла.
def sequence_cut(path_file_in, path_dir_out, count_lines=500000):
'''Функция sequence_cut разрезает файл с расширением .txt на файлы с расширением .txt c заданным количеством строк.
Параметры: path_file_in : str
Абсолютный или относительный путь до файла с расширением .txt, который нужно разрезать.
path_dir_out : str
Абсолютный или относительный путь до папки, в которую будут помещаться нарезанные файлы.
count_lines : int, default 500000
Количество строк, на которые разрезается исходный файл.
Возвращаемое значение: None
'''
def delete_line_feed_end(line):
if line[-1:] == '\n':
return line[:-1]
return line
path_dir_out += '\\' + path_file_in.split('\\')[-1][:-4]
stop_iteration = count_lines - 2
try:
file_number = 1
with open(path_file_in) as file_read:
line = file_read.readline()
while line:
new_file_name = path_dir_out + '_' + str(file_number) + '.txt'
with open(new_file_name, 'w') as file_write:
file_write.write(delete_line_feed_end(line))
line = file_read.readline()
i = 0
while line and (i < stop_iteration):
file_write.write('\n')
file_write.write(line[:-1])
line = file_read.readline()
i += 1
if line:
file_write.write('\n')
file_write.write(delete_line_feed_end(line))
file_number += 1
line = file_read.readline()
except Exception as err:
print('Ошибка: ', err)
Идея второго метода, назовем его «batch_cut (пакетная нарезка)», состоит в том, что считывается строка из входящего файла и сохраняется эта информация в список. Продолжаются такие операции до тех пор, пока длина списка не превосходит count_lines. Когда длина списка-накопителя будет равна count_lines, необходимо сделать из списка одну большую строку и записать ее в очередной файл. Далее делается тоже самое, но уже в новый файл для записи, не забывая очистить список. И так далее. Завершается процесс, когда достигается конец входящего файла.
def batch_cut(path_file_in, path_dir_out, count_lines=500000):
'''Функция batch_cut разрезает файл с расширением .txt на файлы с расширением .txt c заданным количеством строк.
Параметры: path_file_in : str
Абсолютный или относительный путь до файла с расширением .txt, который нужно разрезать.
path_dir_out : str
Абсолютный или относительный путь до папки, в которую будут помещаться нарезанные файлы.
count_lines : int, default 500000
Количество строк, на которые разрезается исходный файл.
Возвращаемое значение: None
'''
def delete_line_feed_end(line):
if line[-1:] == '\n':
return line[:-1]
return line
path_dir_out += '\\' + path_file_in.split('\\')[-1][:-4]
stop_iteration = count_lines - 2
try:
file_number = 1
with open(path_file_in) as file_read:
line = file_read.readline()
while line:
i = 0
batch = [delete_line_feed_end(line)]
line = file_read.readline()
while line and (i < stop_iteration):
batch.append(line[:-1])
line = file_read.readline()
i += 1
if line:
batch.append(delete_line_feed_end(line))
new_file_name = path_dir_out + '_' + str(file_number) + '.txt'
with open(new_file_name, 'w') as file_write:
file_write.write('\n'.join(batch))
file_number += 1
line = file_read.readline()
except Exception as err:
print('Ошибка: ', err)
Идея третьего метода, назовем его «pandas_cut», базируется на использовании модуля pandas, который написан на языке Python и применяется для обработки и анализа данных. С помощью функции read_csv и параметров skiprows (пропустить заданное количество строк файла), nrows (взять заданное количество строк файла), sep=’/n’ (разделитель), encoding=’cp1251′ (кодировка файла), будем считывать очередные count_lines строк файла path_file_in и записывать полученный результат в новый файл с помощью метода to_csv.
def pandas_cut(path_file_in, path_dir_out, count_lines=500000):
'''Функция pandas_cut разрезает файл с расширением .txt на файлы с расширением .txt c заданным количеством строк.
Параметры: path_file_in : str
Абсолютный или относительный путь до файла с расширением .txt, который нужно разрезать.
path_dir_out : str
Абсолютный или относительный путь до папки, в которую будут помещаться нарезанные файлы.
count_lines : int, default 500000
Количество строк, на которые разрезается исходный файл.
Возвращаемое значение: None
'''
import pandas as pd
df = pd.DataFrame([0])
path_out = path_dir_out + '\\' + path_file_in.split('\\')[-1][:-4]
file_number = 1
skiprows = 0
try:
while True:
df = pd.read_csv(path_file_in, header=None, skiprows=skiprows, nrows=count_lines, sep='\n', encoding='cp1251'),
new_file_name = path_out + '_' + str(file_number) + '.txt'
df.to_csv(new_file_name, header=None, index=False)
skiprows += count_lines
file_number += 1
except:
pass
Протестируем функции sequence_cut, batch_cut на время выполнения, соблюдая некоторые условия:
- Читаемый файл и директория для файлов, полученных после нарезки, хранятся на рабочей машине пользователя, то есть не будет траты времени на передачу данных на удаленный сервер.
- Процессор на рабочей машине AMD Ryzen 3 (3.60 GHz); накопитель SSD; оперативная память 8 Гб; операционная система Windows 1064x.
- Читаемый файл будет большого объема: интересно сравнить время выполнения функций на файлах с большим количеством строк, так как для файла с маленьким количеством строк разница по времени не ощутима.
- Входящий файл будет нарезан по 500 000 строк.
- В функции batch_cut длина списка batch будет равна count_lines.
- Функции будут протестированы на файле, полученном с помощью функции generate_data (реализация ниже), и «боевом файле».
def generate_data(path_file, count_lines):
'''Функция generate_data создает файл path_file c заданным количеством строк count_lines.
Параметры: path_file_in : str
Абсолютный или относительный путь до файла с расширением .txt для генерации.
count_lines : int, default 500000
Количество строк, которое будет содержаться в сгенерированном файле.
Возвращаемое значение: None
'''
from random import randint
dict_rand = {0: '', 1: '****', 2: str(randint(1,9999)).zfill(4), 3: str(randint(100000, 999999999)).zfill(9), 4: 'Привет', 5: 'Пока'}
with open(path_file, 'w') as file:
for i in range(count_lines - 1):
string = '~'.join([dict_rand[randint(0,4)] for _ in range(90)]) + '\n'
file.write(string)
file.write('~'.join([dict_rand[randint(0,4)] for _ in range(90)]))
Сделаем два тестовых файла на 10 000 000 и 20 000 000 строк, приближенные по качеству содержания к боевому файлу.
generate_data('test10^7.txt', 10 ** 7)
generate_data('test10^7 prod 2.txt', 10 ** 7 * 2)
Засечем время выполнения функций sequence_cut, batch_cut, pandas_cut на файлах ‘battle_file.txt’, ‘test10^7.txt’, ‘test10^7 prod 2.txt’. Нарезанные файлы сохраним в соответствующие директории.
%%time
sequence_cut('battle_file.txt', 'Folder battle_file sequence_cut', count_lines=500000)
Wall time: 4min
%%time
batch_cut('battle_file.txt', 'Folder battle_file batch_cut', count_lines=500000)
Wall time: 3min 50s
%%time
pandas_cut('battle_file.txt', 'Folder battle_file pandas_cut', count_lines=500000)
Wall time: 13min 14s
%%time
sequence_cut('test10^7.txt', 'Folder test10^7 sequence_cut', count_lines=500000)
Wall time: 2min 40s
%%time
batch_cut('test10^7.txt', 'Folder test10^7 batch_cut', count_lines=500000)
Wall time: 2min 20s
%%time
pandas_cut('test10^7.txt', 'Folder test10^7 pandas_cut', count_lines=500000)
Wall time: 7min 30s
%%time
sequence_cut('test10^7 prod 2.txt', 'Folder test10^7 prod 2 sequence_cut', count_lines=500000)
Wall time: 5min 22s
%%time
batch_cut('test10^7 prod 2.txt', 'Folder test10^7 prod 2 batch_cut', count_lines=500000)
Wall time: 4min 25s
%%time
pandas_cut('test10^7 prod 2.txt', 'Folder test10^7 prod 2 pandas_cut', count_lines=500000)
Wall time: 19min 39s
В ходе нашего тестирования получили следующие результаты:
Название файла | Размер файла | Количество строк в файле | Время sequence_cut | Время batch_cut | Время pandas_cut |
battle_file.txt | 7 982 472 КБ | 14 400 000 | 4min | 3min 50s | 13min 14s |
test10^7.txt | 4 931 645 КБ | 10 000 000 | 2min 40s | 2min 20s | 7min 30s |
test10^7 prod 2.txt | 9 863 322 КБ | 20 000 000 | 5min 22s | 4min 25s | 19min 39s |
Видно, что метод пакетной нарезки (batch_cut) дает преимущество во времени и работает быстрее на файлах с очень большим объемом данных. Стоит учитывать, что пакетный способ нарезки использует список, который хранит большое количество данных в памяти, чего нет у последовательного способа нарезки (sequence_cut).
В любом случае выбор за вами!