Как разбить огромный текстовый файл на Python

У меня огромный текстовый файл (~ 1 ГБ), и, к сожалению, текстовый редактор, который я использую, не читает такой большой файл. Однако, если я могу просто разделить его на две или три части, я буду в порядке, поэтому в качестве упражнения я хотел написать программу на python, чтобы сделать это.

Я думаю, что мне нужно, чтобы программа находила размер файла, делила это число на части и для каждой части считывала до этого момента порциями, записывала в выходной файл имя файла.nnn, а затем считывала- до следующего разрыва строки и напишите это, затем закройте выходной файл и т. д. Очевидно, что последний выходной файл просто копируется в конец входного файла.

Можете ли вы помочь мне с ключевыми частями, связанными с файловой системой: размер файла, чтение и запись кусками и чтение до разрыва строки?

Я буду писать этот код сначала тестовым, поэтому нет необходимости давать мне полный ответ, если только он не однострочный ;-)

Нежелательное предложение: найдите лучший текстовый редактор. :-) Если вы работаете в Windows, я знаю, что EmEditor будет легко редактировать файлы без необходимости полностью загружать их в память.

bobince 15.11.2008 16:00
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
29
1
83 384
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Ответ принят как подходящий

Проверьте размер файла os.stat() и file.readlines([sizehint]). Эти две функции должны быть всем, что вам нужно для чтения, и, надеюсь, вы знаете, как писать :)

Спасибо за ответ - пока ваши предложения по чтению файла работают хорошо. Когда я закончу, я также попробую двоичную версию, которая не читает по одной строке за раз.

quamrana 15.11.2008 23:04

Вы можете использовать wc и split (см. Соответствующие страницы руководства), чтобы получить желаемый эффект. В bash:

split -dl$((`wc -l 'filename'|sed 's/ .*$//'` / 3 + 1)) filename filename-chunk.

производит 3 части одного и того же количества строк (конечно, с ошибкой округления в последнем) с именами от filename-chunk.00 до filename-chunk.02.

Да, это не Python, но зачем использовать отвертку, чтобы наклеивать гвоздь?

Svante 16.11.2008 04:05

Ну, на самом деле это не отвертка против гвоздя ... python часто является отличным способом для выполнения таких простых задач, как эта. И я не хочу bash bash (каламбур), но это не совсем ... читабельно :)

Agos 05.02.2010 02:22

@chrisfs: Naja, rückblickend würde ich vielleicht eher awk '{print $1}' statt der sed-Konstruktion verwenden. Trotzdem kann man ziemlich direkt sehen, был пассивен: wc zählt die Zeilen, sed zieht die reine Zahl aus der Ausgabe, die wird durch drei geteilt und um 1 erhöht; split erzeugt dann Teile dieser Länge aus filename und benennt sie filename.chunk. plus fortlaufende Nummer. Es wäre natürlich nett, wenn wc eine Option hätte, direkt nur die Zahl auszugeben, aber auch so kann man damit gut arbeiten.

Svante 05.08.2011 10:22

Или версия wc и split для Python:

lines = 0
for l in open(filename): lines += 1

Затем какой-нибудь код для чтения первых строк / 3 в один файл, следующих строк / 3 в другой и т. д.

Нет необходимости вести счет вручную, используйте enumerate: для l, строка в enumerate (open (filename)): ...

Matthew Trevor 16.11.2008 11:55

Я написал программу и вроде работает нормально. Так что спасибо Камилу Кисиэлю за то, что заставил меня начать. (Обратите внимание, что FileSizeParts () - это функция, которая здесь не показана)
Позже, возможно, я займусь версией, которая выполняет двоичное чтение, чтобы посмотреть, будет ли оно быстрее.

def Split(inputFile,numParts,outputName):
    fileSize=os.stat(inputFile).st_size
    parts=FileSizeParts(fileSize,numParts)
    openInputFile = open(inputFile, 'r')
    outPart=1
    for part in parts:
        if openInputFile.tell()<fileSize:
            fullOutputName=outputName+os.extsep+str(outPart)
            outPart+=1
            openOutputFile=open(fullOutputName,'w')
            openOutputFile.writelines(openInputFile.readlines(part))
            openOutputFile.close()
    openInputFile.close()
    return outPart-1

в linux есть команда разделения

разделить -l 100000 file.txt

разделится на файлы размером 100000 строк

А если вашей базовой ОС является Windows, вы можете получить Cygwin для доступа практически ко всем классным утилитам командной строки.

neilh 02.12.2013 23:11

Unixtools для Windows также имеет инструмент разделения: split.exe.

aldux 23.04.2014 02:37

У меня есть файл на 120 ГБ. При использовании этой команды он застревает после 1928613 строк. Никаких дальнейших действий не происходит. Я пытался сделать то, что было сказано в stackoverflow.com/a/291759/6143004, но возникла та же проблема.

Piyush Chauhan 12.06.2019 14:24

не забудьте стремиться() и mmap () для произвольного доступа к файлам.

def getSomeChunk(filename, start, len):
    fobj = open(filename, 'r+b')
    m = mmap.mmap(fobj.fileno(), 0)
    return m[start:start+len]

Этот метод генератора - (медленный) способ получить кусок строк, не увеличивая вашу память.

import itertools

def slicefile(filename, start, end):
    lines = open(filename)
    return itertools.islice(lines, start, end)

out = open("/blah.txt", "w")
for line in slicefile("/python27/readme.txt", 10, 15):
    out.write(line)

В качестве альтернативного метода с использованием библиотеки журналов:

>>> import logging.handlers
>>> log = logging.getLogger()
>>> fh = logging.handlers.RotatingFileHandler("D://filename.txt", 
     maxBytes=2**20*100, backupCount=100) 
# 100 MB each, up to a maximum of 100 files
>>> log.addHandler(fh)
>>> log.setLevel(logging.INFO)
>>> f = open("D://biglog.txt")
>>> while True:
...     log.info(f.readline().strip())

Ваши файлы будут выглядеть следующим образом:

filename.txt (end of file)
filename.txt.1
filename.txt.2
...
filename.txt.10 (start of file)

Это быстрый и простой способ привести огромный файл журнала в соответствие с вашей реализацией RotatingFileHandler.

поскольку он разбивается построчно, как это сделать быстрее?

Luis Felipe 23.01.2017 19:10

Это сработало для меня

import os

fil = "inputfile"
outfil = "outputfile"

f = open(fil,'r')

numbits = 1000000000

for i in range(0,os.stat(fil).st_size/numbits+1):
    o = open(outfil+str(i),'w')
    segment = f.readlines(numbits)
    for c in range(0,len(segment)):
        o.write(segment[c]+"\n")
    o.close()

У меня было требование разделить файлы CSV для импорта в Dynamics CRM, поскольку ограничение на размер файла для импорта составляет 8 МБ, а файлы, которые мы получаем, намного больше. Эта программа позволяет пользователю вводить FileNames и LinesPerFile, а затем разбивает указанные файлы на требуемое количество строк. Не могу поверить, как быстро это работает!

# user input FileNames and LinesPerFile
FileCount = 1
FileNames = []
while True:
    FileName = raw_input('File Name ' + str(FileCount) + ' (enter "Done" after last File):')
    FileCount = FileCount + 1
    if FileName == 'Done':
        break
    else:
        FileNames.append(FileName)
LinesPerFile = raw_input('Lines Per File:')
LinesPerFile = int(LinesPerFile)

for FileName in FileNames:
    File = open(FileName)

    # get Header row
    for Line in File:
        Header = Line
        break

    FileCount = 0
    Linecount = 1
    for Line in File:

        #skip Header in File
        if Line == Header:
            continue

        #create NewFile with Header every [LinesPerFile] Lines
        if Linecount % LinesPerFile == 1:
            FileCount = FileCount + 1
            NewFileName = FileName[:FileName.find('.')] + '-Part' + str(FileCount) + FileName[FileName.find('.'):]
            NewFile = open(NewFileName,'w')
            NewFile.write(Header)

        NewFile.write(Line)
        Linecount = Linecount + 1

    NewFile.close()

Хотя Ответ Райана Гинстрома верен, это занимает больше времени, чем должно (как он уже отмечал). Вот способ обойти множественные вызовы itertools.islice, последовательно перебирая дескриптор открытого файла:

def splitfile(infilepath, chunksize):
    fname, ext = infilepath.rsplit('.',1)
    i = 0
    written = False
    with open(infilepath) as infile:
        while True:
            outfilepath = "{}{}.{}".format(fname, i, ext)
            with open(outfilepath, 'w') as outfile:
                for line in (infile.readline() for _ in range(chunksize)):
                    outfile.write(line)
                written = bool(line)
            if not written:
                break
            i += 1

использование - split.py имя файла splitsizeinkb

import os
import sys

def getfilesize(filename):
   with open(filename,"rb") as fr:
       fr.seek(0,2) # move to end of the file
       size=fr.tell()
       print("getfilesize: size: %s" % size)
       return fr.tell()

def splitfile(filename, splitsize):
   # Open original file in read only mode
   if not os.path.isfile(filename):
       print("No such file as: \"%s\"" % filename)
       return

   filesize=getfilesize(filename)
   with open(filename,"rb") as fr:
    counter=1
    orginalfilename = filename.split(".")
    readlimit = 5000 #read 5kb at a time
    n_splits = filesize//splitsize
    print("splitfile: No of splits required: %s" % str(n_splits))
    for i in range(n_splits+1):
        chunks_count = int(splitsize)//int(readlimit)
        data_5kb = fr.read(readlimit) # read
        # Create split files
        print("chunks_count: %d" % chunks_count)
        with open(orginalfilename[0]+"_{id}.".format(id=str(counter))+orginalfilename[1],"ab") as fw:
            fw.seek(0) 
            fw.truncate()# truncate original if present
            while data_5kb:                
                fw.write(data_5kb)
                if chunks_count:
                    chunks_count-=1
                    data_5kb = fr.read(readlimit)
                else: break            
        counter+=1 

if __name__ == "__main__":
   if len(sys.argv) < 3: print("Filename or splitsize not provided: Usage:     filesplit.py filename splitsizeinkb ")
   else:
       filesize = int(sys.argv[2]) * 1000 #make into kb
       filename = sys.argv[1]
       splitfile(filename, filesize)

Сработал у меня отлично в 2017 году! Большое спасибо @Mudit

Bhaskar Pramanik 12.10.2017 21:05

Можете ли вы сделать так, чтобы этот код извлекался построчно, а не по символам. Есть ли способ узнать количество символов в следующей строке?

Piyush Chauhan 12.06.2019 14:20

Вот скрипт Python, который вы можете использовать для разделения больших файлов с помощью subprocess:

"""
Splits the file into the same directory and
deletes the original file
"""

import subprocess
import sys
import os

SPLIT_FILE_CHUNK_SIZE = '5000'
SPLIT_PREFIX_LENGTH = '2'  # subprocess expects a string, i.e. 2 = aa, ab, ac etc..

if __name__ == "__main__":

    file_path = sys.argv[1]
    # i.e. split -a 2 -l 5000 t/some_file.txt ~/tmp/t/
    subprocess.call(["split", "-a", SPLIT_PREFIX_LENGTH, "-l", SPLIT_FILE_CHUNK_SIZE, file_path,
                     os.path.dirname(file_path) + '/'])

    # Remove the original file once done splitting
    try:
        os.remove(file_path)
    except OSError:
        pass

Вы можете назвать это внешне:

import os
fs_result = os.system("python file_splitter.py {}".format(local_file_path))

Вы также можете импортировать subprocess и запустить его прямо в своей программе.

Проблема с этим подходом заключается в высоком использовании памяти: subprocess создает вилку с объемом памяти того же размера, что и ваш процесс, и, если ваша память процесса уже является большой, она удваивает ее на время выполнения. То же самое и с os.system.

Вот еще один способ сделать это на чистом питоне, хотя я не тестировал его на огромных файлах, он будет медленнее, но с меньшим объемом памяти:

CHUNK_SIZE = 5000

def yield_csv_rows(reader, chunk_size):
    """
    Opens file to ingest, reads each line to return list of rows
    Expects the header is already removed
    Replacement for ingest_csv
    :param reader: dictReader
    :param chunk_size: int, chunk size
    """
    chunk = []
    for i, row in enumerate(reader):
        if i % chunk_size == 0 and i > 0:
            yield chunk
            del chunk[:]
        chunk.append(row)
    yield chunk

with open(local_file_path, 'rb') as f:
    f.readline().strip().replace('"', '')
    reader = unicodecsv.DictReader(f, fieldnames=header.split(','), delimiter=',', quotechar='"')
    chunks = yield_csv_rows(reader, CHUNK_SIZE)
    for chunk in chunks:
        if not chunk:
            break
        # Do something with your chunk here

Вот еще один пример использования readlines():

"""
Simple example using readlines()
where the 'file' is generated via:
seq 10000 > file
"""
CHUNK_SIZE = 5


def yield_rows(reader, chunk_size):
    """
    Yield row chunks
    """
    chunk = []
    for i, row in enumerate(reader):
        if i % chunk_size == 0 and i > 0:
            yield chunk
            del chunk[:]
        chunk.append(row)
    yield chunk


def batch_operation(data):
    for item in data:
        print(item)


with open('file', 'r') as f:
    chunks = yield_rows(f.readlines(), CHUNK_SIZE)
    for _chunk in chunks:
        batch_operation(_chunk)

Пример readlines демонстрирует, как разбить данные на части для передачи фрагментов функции, которая ожидает фрагменты. К сожалению, readlines открывает весь файл в памяти, поэтому для повышения производительности лучше использовать пример чтения. Хотя, если вы можете легко поместить то, что вам нужно, в память и обрабатывать его по частям, этого должно быть достаточно.

Первый - вызвать внешнюю команду linux, я не понял ... Во-вторых, строки чтения будут читать весь файл, который потребляет много памяти, кроме того, зачем нам нужны другие куски для этого ???

Jay 13.05.2020 10:22

Использование команды linux split во многих случаях быстрее, использует больше памяти, так как используется подпроцесс ... все это объясняется в ответе. Пример readlines демонстрирует, как разбить данные на части для передачи фрагментов функции, которая ожидает фрагменты.

radtek 13.05.2020 10:49

Теперь доступен модуль pypi, который можно использовать для разделения файлов любого размера на куски. Проверь это

https://pypi.org/project/filesplit/

Поддерживает ли этот пакет разбиение по количеству строк? Я вижу, что он разделен по заданному размеру.

davidbilla 18.11.2020 10:02

Вы можете разделить любой файл на фрагменты, как показано ниже, здесь CHUNK_SIZE составляет 500000 байт (500 КБ), а содержимое может быть любым файлом:

for idx,val in enumerate(get_chunk(content, CHUNK_SIZE)):
    data=val
    index=idx

def get_chunk(content,size):
        for i in range(0,len(content),size):
            yield content[i:i+size]

Другие вопросы по теме