web-dev-qa-db-fra.com

Comment diviser un gros fichier texte en python

J'ai un énorme fichier texte (~ 1 Go) et, malheureusement, l'éditeur de texte que j'utilise ne lit pas un fichier aussi volumineux. Cependant, si je peux simplement le scinder en deux ou trois parties, tout ira bien. En tant qu'exercice, je voulais écrire un programme en python pour le faire. 

Ce que je pense que je veux que le programme fasse, c'est trouver la taille d'un fichier, diviser ce nombre en parties et, pour chaque partie, lire jusqu'à ce point en morceaux, en écrivant dans un fichier de sortie filename .nnn, puis lisez-le jusqu'au saut de ligne suivant et écrivez-le, puis fermez le fichier de sortie, etc. Évidemment, le dernier fichier de sortie est simplement copié à la fin du fichier d'entrée.

Pouvez-vous m'aider avec les parties clés du système de fichiers: la taille du fichier, la lecture et l'écriture en gros morceaux et la lecture jusqu'à la rupture de ligne?

Je vais d'abord écrire ce test de code, il n'est donc pas nécessaire de me donner une réponse complète, à moins que ce ne soit un one-liner ;-)

21
quamrana

Consultez os.stat() pour la taille du fichier et file.readlines([sizehint]). Ces deux fonctions devraient être tout ce dont vous avez besoin pour la partie lecture et, espérons-le, vous savez faire l'écriture :)

15
Kamil Kisiel

linux a une commande split

split -l 100000 fichier.txt

serait divisé en fichiers de taille égale à 100 000 lignes

31
James

Comme méthode alternative, en utilisant la bibliothèque de journalisation:

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

Vos fichiers apparaîtront comme suit:

filename.txt (fin du fichier)
nomfichier.txt.1
nomfichier.txt.2
...
nomfichier.txt.10 (début du fichier)

C'est un moyen rapide et facile de faire correspondre un fichier journal volumineux à votre implémentation RotatingFileHandler.

9
Alex L

Cette méthode de génération est un moyen (lent) d’obtenir une tranche de lignes sans faire exploser votre mémoire.

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)
5
Ryan Ginstrom

Alors que la réponse de Ryan Ginstrom est correcte, cela prend plus de temps qu'il ne le devrait (comme il l'a déjà noté). Voici un moyen de contourner les appels multiples à itertools.islice en itérant successivement sur le descripteur de fichier ouvert:

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
4
inspectorG4dget

n'oubliez pas seek () et mmap () pour un accès aléatoire aux fichiers.

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

Il existe maintenant un module pypi que vous pouvez utiliser pour fractionner des fichiers de toute taille en morceaux. Regarde ça

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

4
Ram

Vous pouvez utiliser wc et split (voir les pages de manuel respectives) pour obtenir l'effet souhaité. Dans bash:

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

produit 3 parties du même nombre de lignes (avec une erreur d’arrondi dans la dernière, bien sûr), nommées filename-chunk.00 à filename-chunk.02.

3
Svante

usage - split.py nom de fichier 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 __== "__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)
2
Mudit Verma

J'ai écrit le programme et il semble bien fonctionner. Merci donc à Kamil Kisiel de m'avoir lancé.
(Notez que FileSizeParts () est une fonction non montrée ici)
Plus tard, je pourrai peut-être faire une version qui lira une lecture binaire pour voir si c'est plus rapide.

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
2
quamrana

Cela a fonctionné pour moi

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

Voici un script python que vous pouvez utiliser pour fractionner des fichiers volumineux à l'aide de 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 __== "__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

Vous pouvez l'appeler en externe:

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

Vous pouvez également importer subprocess et l'exécuter directement dans votre programme.

Le problème avec cette approche est une utilisation importante de la mémoire: subprocess crée une fourchette avec une empreinte mémoire identique à celle de votre processus et si la mémoire de votre processus est déjà lourde, elle est doublée pendant la durée de son exécution. La même chose avec os.system.

Voici une autre façon de faire cela, bien que je ne l’aie pas testé sur d’énormes fichiers, cela va être plus lent, mais soyez plus maigre en mémoire:

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

Voici un autre exemple utilisant 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)
0
radtek

J'avais l'obligation de fractionner les fichiers csv à importer dans Dynamics CRM, car la taille maximale des fichiers à importer est de 8 Mo et les fichiers que nous recevons sont beaucoup plus volumineux. Ce programme permet à l'utilisateur de saisir FileNames et LinesPerFile, puis divise les fichiers spécifiés en nombre de lignes demandé. Je ne peux pas croire à quelle vitesse cela fonctionne!

# 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()
0
Ron Smith

Ou bien, une version python de wc et split:

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

Ensuite, un code pour lire les premières lignes/3 dans un fichier, les lignes suivantes/3 dans un autre, etc.

0
Claudiu