如何在python中拆分一个巨大的文本文件

Posted

技术标签:

【中文标题】如何在python中拆分一个巨大的文本文件【英文标题】:How do I split a huge text file in python 【发布时间】:2008-11-14 23:12:14 【问题描述】:

我有一个巨大的文本文件 (~1GB),遗憾的是我使用的文本编辑器无法读取这么大的文件。但是,如果我可以把它分成两三个部分,我会很好,所以,作为一个练习,我想用 python 编写一个程序来完成它。

我想我想让程序做的是找到一个文件的大小,将这个数字分成几部分,然后对于每个部分,以块的形式读取到该点,写入一个文件名.nnn 输出文件,然后读取到下一个换行符并写入,然后关闭输出文件等。显然最后一个输出文件只是复制到输入文件的末尾。

您能帮我解决与文件系统相关的关键部分:文件大小、分块读取和写入以及读取换行符吗?

我会先写这个代码测试,所以没有必要给我一个完整的答案,除非它是单行的;-)

【问题讨论】:

不受欢迎的建议:获得更好的文本编辑器。 :-) 如果您使用的是 Windows,EmEditor 是我所知道的,它可以无缝地编辑文件,而无需将它们完全加载到内存中。 【参考方案1】:

linux有分割命令

拆分 -l 100000 文件.txt

将拆分为等长 100,000 行大小的文件

【讨论】:

如果您的基本操作系统是 Windows,您可以获取 Cygwin 访问基本上所有很酷的命令行实用程序。 Unixtools for windows也有拆分工具:split.exe 我有一个 120 GB 的文件。使用此命令时,它在一些 1928613 行之后卡住了。它不再继续。我试图做 ***.com/a/291759/6143004 中所说的,但同样的问题正在发生。【参考方案2】:

查看os.stat() 了解文件大小和file.readlines([sizehint])。这两个功能应该是您阅读部分所需的全部,希望您知道如何进行写作:)

【讨论】:

感谢您的回答 - 到目前为止,您的建议对于阅读文件来说效果很好。完成后,我还将尝试一次不读取一行的二进制版本。【参考方案3】:

作为替代方法,使用日志库:

>>> 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(文件结尾) 文件名.txt.1 文件名.txt.2 ... 文件名.txt.10(文件开头)

这是使大型日志文件与您的RotatingFileHandler 实现相匹配的一种快速简便的方法。

【讨论】:

既然是逐行分割,那如何更快呢?【参考方案4】:

现在,有一个可用的 pypi 模块,您可以使用它来将任意大小的文件拆分成块。看看这个

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

【讨论】:

这个包支持按行数分割吗?我看到它确实按给定大小拆分。【参考方案5】:

不要忘记seek() 和mmap() 用于随机访问文件。

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

【讨论】:

【参考方案6】:

这种生成器方法是一种(缓慢的)方法,可以在不占用内存的情况下获取一行行。

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)

【讨论】:

【参考方案7】:

虽然Ryan Ginstrom's answer 是正确的,但它确实需要更长的时间(正如他已经指出的那样)。这是一种通过连续迭代打开的文件描述符来规避对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

【讨论】:

【参考方案8】:

您可以使用wcsplit(参见各自的手册页)来获得所需的效果。在bash

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

产生相同行数的 3 个部分(当然最后一个有舍入错误),命名为 filename-chunk.00filename-chunk.02

【讨论】:

是的,它不是Python,但是为什么要用螺丝刀来钉钉子呢? 嗯,这并不是真正的螺丝刀与钉子... python 通常是完成诸如此类的简单任务的好方法。而且我不想抨击 bash(双关语),但这并不是真的......可读:) @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. 加上 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。【参考方案9】:

我已经编写了程序,它似乎工作正常。感谢 Kamil Kisiel 让我开始。 (请注意,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

【讨论】:

【参考方案10】:

用法 - 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 你能否让这段代码逐行提取而不是逐字符提取。有没有办法获取下一行的字符数?【参考方案11】:

这是一个 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 相同。

这是另一种纯 python 方法,虽然我没有在大文件上测试过,但它会更慢但更节省内存:

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命令,我没明白...第二个,readlines会读取整个文件,这会消耗很多内存,除了为什么我们需要另一个块来做这个??? 在许多情况下使用 linux split 命令更快,使用子进程后使用更多内存.. 答案都解释了。 readlines 示例演示了如何对数据进行分块以将块传递给需要块的函数。【参考方案12】:

这对我有用

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

【讨论】:

【参考方案13】:

您可以实现将任何文件拆分为如下所示的块,这里 CHUNK_SIZE 为 500000 字节(500kb),内容可以是任何文件:

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]

【讨论】:

【参考方案14】:

我需要拆分 csv 文件以导入 Dynamics CRM,因为导入的文件大小限制为 8MB,而我们收到的文件要大得多。该程序允许用户输入 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()

【讨论】:

【参考方案15】:

或者,python 版本的 wc 和 split:

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

然后一些代码将第一行/3 读入一个文件,将下一行/3 读入另一个文件,等等。

【讨论】:

无需手动保持计数,使用 enumerate: for l, line in enumerate(open(filename)):...

以上是关于如何在python中拆分一个巨大的文本文件的主要内容,如果未能解决你的问题,请参考以下文章

索引巨大的文本文件

如何根据日期列在不同的文本/csv文件中转储一个巨大的mysql表?

如何通过 javascript 或 jquery 读取巨大的文本文件?

如何在 Python 中使用管道分隔符拆分文本文件,然后使列数等于属性值的数量?

如何使用 Python 使用管道分隔符拆分文本文件,然后根据条件选择列?

我如何在 Python (2.72) 上打开一个文本文件中的行