如何在Python中从不规则格式的数据文件中提取数据

1 投票
2 回答
874 浏览
提问于 2025-04-16 00:15

我需要从一个文件中提取特定的数据,但这个文件是为人类阅读而格式化的,所以它的格式比较不规则。首先,在数据开始之前,有一大堆文本:

   DL_POLY Version 2.20

                        Running on   10 nodes



*************** DLPOLY: LiNbO3 >***************




模拟控制参数

模拟温度 1.4500E+03

模拟压力 (katm) 0.0000E+00

选择的时间步数 8000

平衡期 500

数据打印间隔 80

统计文件间隔 80

模拟时间步长 5.0000E-04

Nose-Hoover (Melchionna) 各向同性 N-P-T 温控器放松时间 1.0000E-01 压控器放松时间 5.0000E-01

轨迹文件选项开启
轨迹文件起始 1 轨迹文件间隔 80 轨迹文件信息键 2 ...

然后过了一段时间,实际数据出现了,但它的格式有点奇怪:


步骤 总能量 总温度 配置能量 劣势能量 互作用能量 键能量 > 角能量 二面角能量 四面角能量 时间(ps) 动量能量 旋转温度 配置虚能量 劣势虚能量 互作用虚能量 键虚能量 >虚角能量 虚约束 虚四面角 CPU (秒) 体积 剪切温度 剪切能量 虚剪切 alpha beta >gamma 虚自由能 压力


1 -1.1289E+05 1.4750E+03 -1.1386E+05 1.7276E+04 -1.3114E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 0.0 -1.1545E+05 0.0000E+00 9.6539E+03 -1.2118E+05 1.3083E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 0.8 5.3733E+04 1.2367E+02 0.0000E+00 0.0000E+00 5.6396E+01 5.6396E+01 >5.6396E+01 0.0000E+00 -7.5549E+01

滚动 -1.1289E+05 1.4750E+03 -1.1386E+05 1.7276E+04 -1.3114E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 平均值 -1.1545E+05 0.0000E+00 9.6539E+03 -1.2118E+05 1.3083E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 5.3733E+04 1.2367E+02 0.0000E+00 0.0000E+00 5.6396E+01 5.6396E+01 >5.6396E+01 0.0000E+00 -7.5549E+01


80 -1.1290E+05 1.5021E+03 -1.1392E+05 2.1894E+04 -1.3726E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 0.0 -1.1256E+05 0.0000E+00 8.6671E+02 -1.3974E+05 1.3707E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 10.6 5.3149E+04 1.1377E+03 1.4419E+03 3.5382E+03 5.6396E+01 5.6396E+01 >5.6396E+01 0.0000E+00 1.1119E+01

滚动 -1.1290E+05 1.6145E+03 -1.1398E+05 2.0750E+04 -1.3588E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 平均值 -1.1333E+05 0.0000E+00 3.3694E+03 -1.3512E+05 1.3565E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 5.3481E+04 1.0997E+03 1.1430E+03 2.8391E+03 5.6396E+01 5.6396E+01 >5.6396E+01 0.0000E+00 -1.2096E+01


160 -1.1287E+05 1.2629E+03 -1.1376E+05 2.1450E+04 -1.3633E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 0.1 -1.1249E+05 0.0000E+00 3.8761E+02 -1.3824E+05 1.3612E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 20.5 5.3375E+04 4.9015E+02 1.1243E+03 2.5052E+03 5.6396E+01 5.6396E+01 >5.6396E+01 0.0000E+00 1.2676E+01

滚动 -1.1288E+05 1.4677E+03 -1.1389E+05 2.1589E+04 -1.3663E+05 0.0000E+00 0.0000E+00 0.0000E+00 0.0000E+00 平均值 -1.1235E+05 0.0000E+00 2.1147E+02 -1.3884E+05 1.3643E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 5.3152E+04 7.4818E+02 1.1440E+03 2.6211E+03 5.6396E+01 5.6396E+01 >5.6396E+01 0.0000E+00 1.7174E+01


在第9个数据间隔时,有一个小异常:


在步骤500时关闭温度缩放


 560 -1.1287E+05  1.4709E+03 -1.1390E+05  2.1600E+04 -1.3678E+05  0.0000E+00  >0.0000E+00  0.0000E+00  0.0000E+00
 0.3 -1.1292E+05  0.0000E+00  1.9253E+03 -1.3743E+05  1.3656E+05  0.0000E+00  >0.0000E+00  0.0000E+00  0.0000E+00
68.4  5.4300E+04  1.5043E+02  1.2775E+03  2.7947E+03  5.6396E+01  5.6396E+01  >5.6396E+01  0.0000E+00  2.0576E-01

滚动 -1.1286E+05 1.4784E+03 -1.1390E+05 2.1546E+04 -1.3673E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 平均值 -1.1298E+05 0.0000E+00 2.1361E+03 -1.3717E+05 1.3651E+05 0.0000E+00 >0.0000E+00 0.0000E+00 0.0000E+00 5.4303E+04 2.2261E+02 1.2785E+03 2.8027E+03 5.6396E+01 5.6396E+01 >5.6396E+01 0.0000E+00 -1.7421E+00



可以看到,有一对'----'的行,这可能会干扰数据的正确解析。

假设我只想从这个文件中获取'eng_tot'的数据(加粗的数字),我该如何在Python中做到这一点?这个数字总是在文件中的同一个位置(第二个数量,第二组'----'之后的第一行)。

顺便提一下,包含所有定义的标题部分每8步就会重复一次,除了第一步有9行。我想忽略第一步。现在假设我想从第295行开始,包括这一行。告诉你,我对Python和编程都很陌生,所以你能提供的任何帮助我都非常感激。

这是我尝试过的代码,但Eng_Total仍然是一个空集合:

import re
import inspect

def lineno():
    """Returns the current line number"""
    linenum = inspect.currentframe().f_back.f_lineno
infile =  open('FilePath/OUTPUT.01').read()
Eng_Total = []
for line in infile:
#    if 'eng_tot' in line.split(): 
     if re.match("\s+-+\s+", line):
    lineno(line)
        line = linenum+1
        sanitized_line = line[8:]
        eng_total = line.split()[0]
        Eng_Total.append(eng_total)
print Eng_Total

2 个回答

0

你需要明确地定义文件格式,然后就可以轻松解析它。

第一步是找出你需要的数据在哪里定义。然后把之前的内容都丢掉,从那个地方开始读取。

如果eng_tot的位置可能会变动,你需要弄清楚它在有用数据块中的位置。所以,先读一行,entries = line.split(); location = entries.index('eng_tot'),然后从输出数据的对应行中读取那个位置的内容。

关键是你需要把问题分解成你能做到的步骤。当面对新事物时,很容易感到不知所措。如果你能先开始做一些事情,你会发现其实可以比较顺利地找到解决方案。

1

我可能会这样做:

  • 遍历输出中的每一行
  • 寻找包含 eng_tot 的行:
    • if 'eng_tot' in line.split(): process_blocks
  • 一直读取行,直到找到一行全是破折号的行(两边可以有空格)
    • if re.match("\s+-+\s+", line): proccess_metrics_block
  • 处理指标的第一行:
    • 把这一行的第一列去掉(因为它可能不在,所以会让解析变得复杂)
      • sanitized_line = line[8:]
      • eng_total = line.split()[0],现在第一列就是 eng_total
  • 跳过行,直到再遇到一行破折号,然后重新开始

在看到你的修改后:

  • 你需要在文件顶部导入 re(正则表达式)模块:import re
  • process_blocksprocess_metrics_block 是伪代码。除非你自己定义它们,否则它们是不存在的。:) 你不一定需要这些函数,可以用基本的循环(while)和条件(if)语句来替代。
  • 你得确保自己明白在做什么,而不是光复制 Stack Overflow 上的内容! :)

看起来你想做的事情大概是这样的。虽然似乎能工作,但我相信只要稍微努力,你可以想出更好的方法:

import re

def find_header(lines):
  for (i, line) in enumerate(lines):
    if 'eng_tot' in line.split():
      return i
  return None

def find_next_separator(lines, start):
  for (i, line) in enumerate(lines[start+1:]):
    if re.match("\s*-+\s*", line):
      return i + start + 1
  return None

if __name__ == '__main__':
  totals = []
  lines = open('so.txt').readlines()

  header = find_header(lines)
  start = find_next_separator(lines, header+1)

  while True:
    end = find_next_separator(lines, start+1)
    if end is None: break

    # Pull out block, after line of dashes.
    metrics_block = lines[start+1:end]

    # Pull out 2nd column from 1st line of metrics.
    eng_total = metrics_block[0].split()[1]
    totals.append(eng_total)

    start = end

  print totals

你可以使用生成器,让代码更符合 Python 的风格:

def metric_block_iter(lines):
  start = find_next_separator(lines, find_header(lines)+1)
  while True:
    end = find_next_separator(lines, start+1)
    if end is None: break
    yield (start, end)
    start = end


if __name__ == '__main__':
  totals = []
  lines = open('so.txt').readlines()

  for (start, end) in metric_block_iter(lines):
    # Pull out block, after line of dashes.
    metrics_block = lines[start+1:end]

    # Pull out 2nd column from 1st line of metrics.
    eng_total = metrics_block[0].split()[1]
    totals.append(eng_total)

  print totals

撰写回答