用Python分割大体积文本日志文件

用Python分割大体积文本日志文件

最近帮助客户进行运维,要分析一个应用产生的日志文本文件,但是在服务器上根本打不开,原因是体积太大编辑器无法处理,我看了一下文件大小有1.2G确实很大,Windows服务器上那个记事本软件根本没办法打开。后来想了一个办法,先将文件压缩,压缩后一百多M,再远程拷贝回我的电脑上,用从网上下载的文件分割软件分割成多个小文件后再打开,发现可行。但是又碰到了新问题,有的分割后文件用文本编辑器打开后是乱码,无法显示。这是什么原因?想了想,看了一下原日志文件的编码格式,恍然大悟。原日志文件是UTF8格式编码,这种编码一个字符的使用的字节数在1-4个之间不确定,当我们用分割软件分割原文件时候很有可能正好切在了最后UTF8字符的1-4个字节组的中间,造成了切割后的新文件第一个字符的编码字节错误,文本编辑器就无法识别。这样只好自己用Python编一个小工具来处理吧。

实现按字节数分割文件

先实现用字节数分割软件,可以分割二进制文件和ASCII等编码一个字符占一个字节的文件。这个实现起来比较简单,就是按设定的字节数读取原文件,然后再存入新文件直到读完 原文件。代码如下:

import os
import tqdm

def splitfilebysize(filename, splitfilesize):
    if not os.path.isfile(filename):
        return
    if splitfilesize < 1024:
        return
    step = os.path.getsize(filename) / splitfilesize
    noextfilename = os.path.splitext(filename)[0]
    extname = os.path.splitext(filename)[-1]
    pbar = tqdm.tqdm(total=step, desc="正在分割文件")  # 生成进度条对象
    with open(filename, "rb") as srcfile:
        count = 1
        iscomplete = False
        while not iscomplete:
            readbuffer = srcfile.read(splitfilesize)
            newfilename = f"{noextfilename}_{count}{extname}"
            if os.path.exists(newfilename):
                os.remove(newfilename)
            with open(newfilename, 'wb') as dstfile:
                dstfile.write(readbuffer)
            iscomplete = (len(readbuffer) != splitfilesize)
            if not iscomplete:
                count += 1
                pbar.update(1)
    pbar.close()
    return True

函数有两个参数filename是全路径的原文件的文件名,splitfilesize是分割的每个文件大小。tqdm是一个进度条模块,用来显示分割时进度。
函数按预定好的字节数进行分割,分割后的文件名是原文件名后加”_序号“。

实现按字符数分割文件

按前面思路,用字节个数分割文件对多字节编码的文件不适用,所以用字符个数分割文件就不会造成乱码的问题,首先在分割文件前判断一下原文件的字符编码格式,这里要用到chardet模块,用该模块的detect函数进行编码格式判断。在打开原文件时指定编码格式,写入新文件时也指定编码格式和原文件一致。实现代码如下:

import os
import tqdm
import chardet

def splittxtfilebysize(filename, charsnum):
    if not os.path.isfile(filename):
        return
    if charsnum < 1024:
        return
    # 判断文件的编码类型
    nowcoding = "ascii"
    with  open(filename, "rb") as f:
         msg = f.read(1024)     # 读取1K字节进行判断,不能将整个大文件读取
         code = chardet.detect(msg)
         nowcoding = code["encoding"]
    noextfilename = os.path.splitext(filename)[0]
    extname = os.path.splitext(filename)[-1]
    if nowcoding == "ascii":
        step = os.path.getsize(filename) / charsnum
    else:
        step = os.path.getsize(filename) / charsnum / 2

    pbar = tqdm.tqdm(total=step, desc="正在分割文件")  # 生成进度条对象
    with open(filename, "r", encoding=nowcoding) as srcfile:
        count = 1
        iscomplete = False
        while not iscomplete:
            readbuffer = srcfile.read(charsnum) # 这里是按编码读回的文本,read的参数不是字节数而是字符数
            newfilename = f"{noextfilename}_{count}{extname}"
            if os.path.exists(newfilename):
                os.remove(newfilename)
            with open(newfilename, 'w', encoding=nowcoding) as dstfile:
                dstfile.write(readbuffer)
            iscomplete = (len(readbuffer) != charsnum)
            if not iscomplete:
                count += 1
                pbar.update(1) # 进度条更新
    pbar.close()
    print(f"Source file's encoding is {nowcoding}!")
    return True

这个函数有几个地方要注意,首先在判断原文件编码格式的时候,从原文件读取不能用read()全部读取,本来原文件就是大体积文件这样读就卡死在这里了,我是读了1K字节的内容进行判断。detect函数返回一个字典类型对象,"encoding"键值对存放的是编码名。还要注意打开文件时要指定编码格式,调用读文件函数read时是按字符个数不是字节个数进行读取。最后要注意的是由于无法在分割开始前得到原文件中的字符个数总数,所以进度条不太准确,这里可以改进一下。
完整代码如下:

# coding=utf-8
import os
import time
import tqdm
import chardet


def splitfilebysize(filename, splitfilesize):
    if not os.path.isfile(filename):
        return
    if splitfilesize < 1024:
        return
    step = os.path.getsize(filename) / splitfilesize
    noextfilename = os.path.splitext(filename)[0]
    extname = os.path.splitext(filename)[-1]
    pbar = tqdm.tqdm(total=step, desc="正在分割文件")  # 生成进度条对象
    with open(filename, "rb") as srcfile:
        count = 1
        iscomplete = False
        while not iscomplete:
            readbuffer = srcfile.read(splitfilesize)
            newfilename = f"{noextfilename}_{count}{extname}"
            if os.path.exists(newfilename):
                os.remove(newfilename)
            with open(newfilename, 'wb') as dstfile:
                dstfile.write(readbuffer)
            iscomplete = (len(readbuffer) != splitfilesize)
            if not iscomplete:
                count += 1
                pbar.update(1)
    pbar.close()
    return True
    
def splittxtfilebysize(filename, charsnum):
    if not os.path.isfile(filename):
        return
    if charsnum < 1024:
        return
    # 判断文件的编码类型
    nowcoding = "ascii"
    with  open(filename, "rb") as f:
         msg = f.read(1024)     # 读取1K字节进行判断,不能将整个大文件读取
         code = chardet.detect(msg)
         nowcoding = code["encoding"]
    noextfilename = os.path.splitext(filename)[0]
    extname = os.path.splitext(filename)[-1]
    if nowcoding == "ascii":
        step = os.path.getsize(filename) / charsnum
    else:
        step = os.path.getsize(filename) / charsnum / 2

    pbar = tqdm.tqdm(total=step, desc="正在分割文件")  # 生成进度条对象
    with open(filename, "r", encoding=nowcoding) as srcfile:
        count = 1
        iscomplete = False
        while not iscomplete:
            readbuffer = srcfile.read(charsnum) # 这里是按编码读回的unicode文本,read的参数不是字节数而是字符数
            newfilename = f"{noextfilename}_{count}{extname}"
            if os.path.exists(newfilename):
                os.remove(newfilename)
            with open(newfilename, 'w', encoding=nowcoding) as dstfile:
                dstfile.write(readbuffer)
            iscomplete = (len(readbuffer) != charsnum)
            if not iscomplete:
                count += 1
                pbar.update(1)
    pbar.close()
    print(f"Source file's encoding is {nowcoding}!")
    return True

if __name__ == '__main__':
    filename = r"D:\test\test.log"
    # splitfilebysize(filename, 1024*1024)
    splittxtfilebysize(filename, 1*1024*1024)    

大家可以测试下,使用按字符分割后就没有乱码了,用一般文本编辑器都可以打开。这里推荐免费开源的Notepad3,比Windows自带的好用多了, Windows自带的那个编辑器的BUG大家都知道,会在保存UTF8文本时在文件前加上0xefbbbf。用VsCode也不错。

码字不易,如果本文对您有用请随手点个赞,谢谢!^_^

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值