【问题标题】:Is there a faster way to parse this text file?有没有更快的方法来解析这个文本文件?
【发布时间】:2020-11-24 06:46:44
【问题描述】:

我正在从一些与此类似的文本文件中解析日期/时间/测量信息:

[Sun Jul 15 09:05:56.724 2018] *000129.32347
[Sun Jul 15 09:05:57.722 2018] *000129.32352
[Sun Jul 15 09:05:58.721 2018] *000129.32342
[Sun Jul 15 09:05:59.719 2018] *000129.32338
[Sun Jul 15 09:06:00.733 2018] *000129.32338
[Sun Jul 15 09:06:01.732 2018] *000129.32352

结果进入如下输出文件:

07-15-2018 09:05:56.724, 29.32347
07-15-2018 09:05:57.722, 29.32352
07-15-2018 09:05:58.721, 29.32342
07-15-2018 09:05:59.719, 29.32338
07-15-2018 09:06:00.733, 29.32338
07-15-2018 09:06:01.732, 29.32352

我使用的代码如下所示:

import os
import datetime

with open('dq_barorun_20180715_calibtest.log', 'r') as fh, open('output.txt' , 'w') as fh2:
    for line in fh:
        line = line.split()
        monthalpha = line[1]
        month = datetime.datetime.strptime(monthalpha, '%b').strftime('%m')
        day = line[2]
        time = line[3]
        yearbracket = line[4]
        year = yearbracket[0:4]
        pressfull = line[5]
        press = pressfull[5:13]
        timestamp = month+"-"+day+"-"+year+" "+time
        fh2.write(timestamp + ", " + press + "\n")

这段代码运行良好,可以满足我的需求,但我正在尝试学习更有效的 Python 文件解析方法。处理一个 100MB 的文件大约需要 30 秒,我有几个大小为 1-2GB 的文件。有没有更快的方法解析这个文件?

【问题讨论】:

  • 检查将一堆输出行捆绑成一个更大的字符串然后偶尔将它们写入输出文件是否更快。
  • 这里的星期日是Sun,七月是Jul。其余数据是否也只需要前 3 个字母?
  • 为什么 000129.32338 会导致 29.32338 ? 1去哪儿了?
  • 我只需要三个字母的月份Jul(在这种情况下),这样我就可以将它转换为日期格式的07。我不需要Sun
  • 仪器的原始输出是*0001,读数是29.32338,所以我不需要1。

标签: python file optimization


【解决方案1】:

你可以声明months dict 不使用datetime 模块,这样应该会快一点。

months = {"Jan": "01", "Feb": "02", "Mar": "03", "Apr": "04", "May": "05", "Jun": "06",
          "Jul": "07", "Aug": "08", "Sep": "09", "Oct": "10", "Nov": "11", "Dec": "12"}

您还可以使用 unpacking 让您的代码更简单:

for line in fh:
    _, month, day, time, year, last = line.split()
    res = months[month] + "-" + day + "-" + year[:4] + " " + time + ", " + last[5:]
    fh2.write(res)

附: timeit 表明它的速度大约是 10 倍

【讨论】:

    【解决方案2】:

    您应该使用 pandas DataFrames,以便快速加载和操作大数据:

    import pandas as pd, datetime
    
    df = pd.read_csv('dq_barorun_20180715_calibtest.log',header=None,sep=' ')
    df[0] = df.apply(lambda x: x[0][1:],axis=1)
    df[1] = df.apply(lambda x: datetime.datetime.strptime(x[1], '%b').strftime('%m'), axis=1)
    df[4] = df.apply(lambda x: x[4][:-1],axis=1)
    df[5] = df.apply(lambda x: ' ' + x[5][5:],axis=1)
    df['timestamp'] = df.apply(lambda x: x[1]+"-"+str(x[2])+"-"+x[4]+" "+x[3], axis = 1)
    df.to_csv('output.txt',columns=['timestamp',5],header=False, index=False)
    

    【讨论】:

    • 你不应该使用apply。它非常慢。我认为它不会比 OP 更快。检查this link
    • 我在大型数据集(超过 5GB 的数据)之前多次使用它,没有明显的时间延迟。
    【解决方案3】:

    由于您有固定的职位和从月份名称到数字的简单转换,这应该可以工作

    #! /usr/bin/env python3
    
    m = {'Jan':'01', 'Feb':'02', 'Mar':'03', 'Apr':'04', 'May':'05', 'Jun':'06', 'Jul':'07', 'Aug':'08', 'Sep':'09', 'Oct':'10', 'Nov':'11', 'Dec':'12'}
    with open('dq_barorun_20180715_calibtest.log', 'r') as fh, open('output.txt' , 'w') as fh2:
        for line in fh:
            day = line[9:11]
            month = m[line[5:8]]
            year = line[25:29]
            time = line[12:24]
            val = line[36:44]
            print('{}-{}-{} {}, {}'.format(month, day, year, time, val), file=fh2)
    

    【讨论】:

      【解决方案4】:

      这是另一种使用 pandas read_csv 的方法,如果你有很多大文件,你可以使用 dask 它也支持这个。

      import pandas as pd
      import datetime
      
      df = pd.read_csv('D:\\test.txt',sep='\*0001')
      
      df.columns = ['dates','val']
      df.dates = pd.to_datetime(df.dates.str[1:-2])
      df.to_csv("output.csv",header=None,index=None)
      

      然后您可以使用不同的方法将日期转换为您想要的格式。

      【讨论】:

        【解决方案5】:

        我可能有一个完全不同的想法,但它本身并不能加快解析速度。
        如果这不是您想要的,因为您要求更快的解析,请发表评论,我会删除这个答案。

        您应该将大型输入文件拆分为较小的段。 例如,您可以尝试获取行数并将其除以合适的数字。 假设有 40,000 行,那么您可以将其分成 4 段,每段接近 10,000 行。记下起始索引以及要处理的行数。 (请注意,最后一个偏移量可能小于 10,000。

        然后将输入文件传递给几个线程,这些线程只读取输入文件从start-indexoffset 的给定部分并解析这部分。 各个线程将解析后的部分写入共享文件夹,但使用 indexed 文件名。 每个线程结束后,您可以将共享文件夹中的所有文件合并为一个output.txt


        以下是一些有关如何实现其中一些目标的链接资源:

        【讨论】:

          【解决方案6】:

          为了避免打开的文件过大,导致内存占用过多,本代码使用了Python的yield技术,使用了正则的内容。具体的实际性能,可以和自己写的代码进行性能对比。

          以下代码已在本地运行!

          import datetime
          import re
          
          # File path to be cleaned
          CONTENT_PATH = './test03.txt'
          # File path of cleaning results
          RESULT_PATH = './test03.log'
          
          
          def read(file):
              with open(file) as obj:
                  while True:
                      line = obj.readline()
                      if line:
                          yield line
                      else:
                          return
          
          
          def tsplit(line):
              _m, _d, _y = line[5:8], line[9:11], line[25:29]
              _m = datetime.datetime.strptime(_m, '%b').strftime('%m')
              rt = "%s-%s-%s" % (_m, _d, _y)
              return rt
          
          
          hour_min_sen = re.compile(r'(\d{2}:\d{2}:\d{2}.\d{2})')
          end = re.compile(r'(\d{2}\.\d{5})')
          
          
          with open(RESULT_PATH, 'a+') as obj:
              for line in read(CONTENT_PATH):
                  line = line.strip()
                  """
                  [Sun Jul 15 09:06:01.732 2018] *000129.32352
                  """
                  group = hour_min_sen.findall(line)
                  end_group = end.findall(line)
                  """
                  07-15-2018 09:05:56.724, 29.32347
                  """
                  obj.write("%s %s, %s\n" % (tsplit(line), group[0], end_group[0]))
          

          【讨论】:

          • Python 的 open 不读取文件,它只是以提供的访问模式打开文件句柄。如果您使用 for 循环遍历文件,python 不会将整个文件读取到内存中,它会让您逐行使用文件。因此,您的生成器函数与 python 默认所做的完全相同。
          猜你喜欢
          • 1970-01-01
          • 2013-03-10
          • 1970-01-01
          • 2015-02-10
          • 1970-01-01
          • 1970-01-01
          • 2020-12-22
          • 2011-03-24
          • 1970-01-01
          相关资源
          最近更新 更多