¿Cómo puedo dividir un archivo de texto enorme en python

Tengo un archivo de texto enorme (~ 1 GB) y, lamentablemente, el editor de texto que uso no lee un archivo tan grande. Sin embargo, si puedo dividirlo en dos o tres partes, estaré bien, así que, como ejercicio, quería escribir un progtwig en Python para hacerlo.

Lo que creo que quiero que haga el progtwig es encontrar el tamaño de un archivo, dividir ese número en partes y, para cada parte, leer hasta ese punto en trozos, escribir en un archivo .nnn archivo de salida, luego leer hacia arriba al siguiente salto de línea y escriba eso, luego cierre el archivo de salida, etc. Obviamente, el último archivo de salida solo se copia al final del archivo de entrada.

¿Puede ayudarme con las partes clave relacionadas con el sistema de archivos: tamaño de archivo, lectura y escritura en trozos y lectura de un salto de línea?

Primero escribiré este código de prueba, así que no hay necesidad de darme una respuesta completa, a menos que sea de una sola línea 😉

Echa un vistazo a os.stat() para el tamaño del archivo y file.readlines([sizehint]) . Esas dos funciones deberían ser todo lo que necesitas para la parte de lectura, y espero que sepas cómo escribir 🙂

Linux tiene un comando de división

dividir -l 100000 file.txt

Se dividiría en archivos de igual tamaño de 100,000 líneas.

Como método alternativo, utilizando la biblioteca de registro:

 >>> 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()) 

Sus archivos aparecerán de la siguiente manera:

filename.txt (final del archivo)
nombre_archivo.txt.1
nombre_archivo.txt.2

filename.txt.10 (inicio de archivo)

Esta es una forma rápida y fácil de hacer que un enorme archivo de registro coincida con su implementación de RotatingFileHandler .

Este método generador es una forma (lenta) de obtener una porción de líneas sin explotar su memoria.

 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) 

Puede usar wc y split (consulte las páginas de manual correspondientes) para obtener el efecto deseado. En bash

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

produce 3 partes del mismo conteo de líneas (con un error de redondeo en el último, por supuesto), llamado filename-chunk.00 a filename-chunk.02 .

no olvide buscar () y mmap () para acceso aleatorio a archivos.

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

Si bien la respuesta de Ryan Ginstrom es correcta, lleva más tiempo del que debería (como ya ha señalado). Aquí hay una manera de evitar las múltiples llamadas a itertools.islice iterando sucesivamente sobre el descriptor de archivo abierto:

 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 

Ahora, hay un módulo pypi disponible que puede usar para dividir archivos de cualquier tamaño en partes. Mira esto

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

He escrito el progtwig y parece funcionar bien. Así que gracias a Kamil Kisiel por ayudarme a empezar.
(Tenga en cuenta que FileSizeParts () es una función que no se muestra aquí)
Más adelante, podré dedicarme a hacer una versión que haga una lectura binaria para ver si es más rápida.

 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() 

uso – split.py nombre de archivo 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) 

Esto funciono para mi

 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() 

O, una versión de python de wc y split:

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

Luego, un código para leer las primeras líneas / 3 en un archivo, las siguientes líneas / 3 en otro, etc.

Tenía el requisito de dividir los archivos csv para importarlos en Dynamics CRM, ya que el límite de tamaño de los archivos para importar es de 8 MB y los archivos que recibimos son mucho más grandes. Este progtwig permite al usuario ingresar FileNames y LinesPerFile, y luego divide los archivos especificados en el número de líneas solicitado. No puedo creer lo rápido que funciona!

 # 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() 

Aquí hay un script de Python que puede usar para dividir archivos grandes usando 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, ie 2 = aa, ab, ac etc.. if __name__ == "__main__": file_path = sys.argv[1] # ie 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 

Puedes llamarlo externamente:

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

También puede importar subprocess y ejecutarlo directamente en su progtwig.

El problema con este enfoque es el alto uso de memoria: el subprocess crea una bifurcación con una huella de memoria del mismo tamaño que su proceso y si su memoria de proceso ya es pesada, la duplica durante el tiempo que se ejecuta. Lo mismo con os.system .

Aquí hay otra forma pura de python de hacer esto, aunque no lo he probado en archivos grandes, va a ser más lento pero más magro en la memoria:

 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 

Aquí hay otro ejemplo usando 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)