使用Python解析基于块的程序输出
5 个回答
根据你的例子,你现在面对的是一堆不同的、嵌套的子格式。单独来看,这些格式都很容易解析,但问题在于格式的数量太多,而且它们可以以不同的方式嵌套在一起,这就让人觉得有些复杂。
在最基本的层面上,你会看到一行行用空格分开的值。这些行组合成块,而这些块是如何组合和嵌套在一起的,才是复杂的地方。这种输出格式是为了人类阅读设计的,并不是为了被“抓取”回机器可读的形式。
首先,我建议你联系软件的作者,看看是否有其他输出格式,比如XML或CSV。如果做得好(也就是说,不是简单地把打印格式包裹在笨重的XML里,或者用逗号替换空格),那么处理起来会容易得多。如果没有这样的选项,我会尝试列出一个格式的层级列表,看看它们是如何嵌套的。例如:
ESTIMATED SAMPLE STATISTICS
开始一个块- 在这个块内,
MEANS/INTERCEPTS/THRESHOLDS
开始一个嵌套块 - 接下来的两行是一些列标题
- 然后是一行(或多行?)数据,包含行标题和数据值
依此类推。如果你把每个问题单独处理,你会发现这虽然繁琐,但并不复杂。可以把上面的每一步看作是模块,测试输入是否匹配,如果匹配,就调用其他模块进一步测试块内可能出现的内容,如果遇到不匹配的情况,就回溯(这被称为“递归下降”)。
请注意,你无论如何都需要做类似的事情,以便构建一个内存中的数据版本(即“数据模型”),这样你才能进行操作。
我的建议是对这些数据进行一些简单的处理,让它们变得更有用。下面是我对你的数据做的一些实验:
from __future__ import print_function
from itertools import groupby
import string
counter = 0
statslist = [ statsblocks.split('\n')
for statsblocks in open('mlab.txt').read().split('\n\n')
]
print(len(statslist), 'blocks')
def blockcounter(line):
global counter
if not line[0]:
counter += 1
return counter
blocklist = [ [block, list(stats)] for block, stats in groupby(statslist, blockcounter)]
for blockno,block in enumerate(blocklist):
print(120 * '=')
for itemno,line in enumerate(block[1:][0]):
if len(line)<4 and any(line[-1].endswith(c) for c in string.letters) :
print('\n** DATA %i, HEADER (%r)**' % (blockno,line[-1]))
else:
print('\n** DATA %i, item %i, length %i **' % (blockno, itemno, len(line)))
for ind,subdata in enumerate(line):
if '___' in subdata:
print(' *** Numeric data starts: ***')
else:
if 6 < len(subdata)<16:
print( '** TYPE: %s **' % subdata)
print('%3i : %s' %( ind, subdata))
是的,解析这个东西确实挺麻烦的。不过,你其实不需要用很多复杂的正则表达式。普通的 split
方法就可能足够把这个文档分成更容易处理的字符串序列。
这里面有很多我称之为“头-体”结构的文本块。你会看到标题、一行“--”的分隔线,然后是数据。
你想做的就是把这种“头-体”结构压缩成一个生成器函数,这个函数会逐个输出字典。
def get_means_intecepts_thresholds( source_iter ):
"""Precondition: Current line is a "MEANS/INTERCEPTS/THRESHOLDS" line"""
head= source_iter.next().strip().split()
junk= source_iter.next().strip()
assert set( junk ) == set( [' ','-'] )
for line in source_iter:
if len(line.strip()) == 0: continue
if line.strip() == "SLOPES": break
raw_data= line.strip().split()
data = dict( zip( head, map( float, raw_data[1:] ) ) )
yield int(raw_data[0]), data
def get_slopes( source_iter ):
"""Precondition: Current line is a "SLOPES" line"""
head= source_iter.next().strip().split()
junk= source_iter.next().strip()
assert set( junk ) == set( [' ','-'] )
for line in source_iter:
if len(line.strip()) == 0: continue
if line.strip() == "SLOPES": break
raw_data= line.strip().split() )
data = dict( zip( head, map( float, raw_data[1:] ) ) )
yield raw_data[0], data
关键是用一套操作来处理头部和一些杂乱的内容。
然后用另一套操作来处理后面的数据行。
因为这些是生成器,你可以把它们和其他操作结合起来使用。
def get_estimated_sample_statistics( source_iter ):
"""Precondition: at the ESTIMATED SAMPLE STATISTICS line"""
for line in source_iter:
if len(line.strip()) == 0: continue
assert line.strip() == "MEANS/INTERCEPTS/THRESHOLDS"
for data in get_means_intercepts_thresholds( source_iter ):
yield data
while True:
if len(line.strip()) == 0: continue
if line.strip() != "SLOPES": break
for data in get_slopes( source_iter ):
yield data
这样做可能比用正则表达式要好。