有没有更快的方法来解析这个文本文件?

3

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

[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大小的文件。是否有一种更快的方式来解析这个文件?


检查一下是否将一堆输出行捆绑成一个较大的字符串,然后每隔一段时间才将它们写入输出文件会更快。 - ctenar
这里的星期天是“Sun”,七月是“Jul”。您的其余数据是否也只需要前三个字母? - Countour-Integral
为什么 000129.32338 的结果是 29.32338?1去哪了? - Patrick Artner
我只需要三个字母的月份“Jul”(在这种情况下),以便我可以将其转换为日期格式中的“07”。我不需要“Sun”。 - Jason Dunn
仪器的原始输出为*0001,读数为29.32338,因此我不需要1。 - Jason Dunn
6个回答

3
您可以声明一个名为 months 的字典,不使用 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"}

你还可以使用解包,让代码更加简单:

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

P.S. timeit 显示它大约 快了10倍


0
我可能有一个完全不同的想法,但它本身并不能加快解析速度。 如果这不是您想要的,因为您要求更快的解析,请留下评论,我会删除这个答案。
您应该将大型输入文件拆分成较小的段落。 例如,您可以尝试获取行数,并将其除以一个合适的数字。 假设有40,000行,那么您可以将其分成4个片段,每个片段处理10,000行。记录一个起始索引和您想处理的行数。(请注意,最后一个偏移量可能小于10,000。)
然后,将输入文件传递给几个线程,这些线程仅从输入文件的“起始索引”到“偏移量”的给定部分进行读取和解析。 各个线程将解析的部分写入共享文件夹,但使用有索引的文件名。 每个线程完成后,您可以将共享文件夹中的所有文件合并成一个output.txt。

以下是一些相关链接,可以帮助您实现这些功能:


0

你应该使用pandas数据框,以便快速加载和操作大量数据:

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)

3
不应该使用 apply,因为它非常慢。我认为它不会比 OP 更快。请参考 此链接 - Inyoung Kim 김인영
我之前多次使用它处理大数据集(超过5GB的数据),没有看到明显的时间延迟。 - cristelaru vitian

0

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

#! /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)

0

这里有另一种使用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)

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


-1
为了避免打开文件过大导致内存使用过度,此代码采用 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默认情况下所做的完全相同。 - Olvin Roght

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接