【问题标题】:Reading data using regular expression使用正则表达式读取数据
【发布时间】:2020-03-12 19:59:26
【问题描述】:

对于我的项目,我需要读取文件并将其与我的常量匹配,一旦匹配,需要将它们存储在字典中。我将在下面展示我的数据样本以及到目前为止的数据。

我的数据:

TIMESTAMP: 1579051725 20100114-202845
.1.2.3.4.5.6.7.8.9 = 234567890
ifTb: name-nam-na
.1.3.4.1.2.1.1.1.1.1.1.128 = STRING: AA1
.1.3.4.1.2.1.1.1.1.1.1.129 = STRING: Eth1
.1.3.4.1.2.1.1.1.1.1.1.130 = STRING: Eth2

这些数据有 5 个我想收集的重要部分:

  1. 时间戳之后的日期:1579051725

  2. Num(数字的第一部分直到 128、129、130 等):.1.3.4.1.2.1.1.1.1.1.1

  3. Num2(第二部分):128 or 129 or 130 or others in my large data set

  4. Syntax:在这种情况下,它被命名为:STRING

  5. Counter:在这种情况下,它们是字符串; AA1Eth1Eth2

我还有(需要)常量Num作为程序中保存上述值和常量syntax的字典

我想通读数据文件,

  1. 如果Num 匹配我在程序中的常量,

  2. Num2

  3. 检查Syntax是否与程序中的常量syntax匹配

  4. Counter

当我说抓取时,我的意思是将数据放在相应的字典下。

简而言之,我想通读数据文件,在其中拆分5个变量,将2个变量与常量字典值匹配,并在字典下抓取并存储3个变量(包括时间)。

我现在无法拆分数据。我可以拆分除NumNum2 之外的所有内容。另外我不知道如何创建常量字典以及我应该如何将常量字典放在下面。

我很想使用正则表达式而不是使用 if 语句,但由于数据在单词中包含许多点,因此无法弄清楚要使用什么符号。

到目前为止,我有以下内容:

constant_dic1 = {[".1.3.4.1.2.1.1.1.1.1.1"]["STRING" ]}
data_cols = {'InterfaceNum':[],"IndexNum":[],"SyntaxName":[],"Counter":[],"TimeStamp":[]}
fileN = args.File_Name
with open (fileN, 'r') as f:

    for lines in f:
        if lines.startswith('.'):
            if ': ' in lines:
                lines=lines.split("=")
                first_part = lines[0].split()
                second_part = lines[1].split()
                for i in first_part:
                    f_f = i.split("{}.{}.{}.{}.{}.{}.{}.{}.{}.{}.{}.")
                print (f_f[0])

运行程序后,我收到“TypeError:列表索引必须是整数或切片,而不是 str”的错误。

当我注释掉字典部分时,输出是Num 以及Num2。它不会被拆分,也不会只打印Num 部分。

感谢任何帮助! 如果还有其他来源,请在下面告诉我。 请让我知道我是否需要对该问题的任何更新,而无需投票。 谢谢!

更新代码

import pandas as pd
import io
import matplotlib
matplotlib.use('TkAgg') # backend option for matplotlib #TkAgg #Qt4Agg #Qt5Agg
import matplotlib.pyplot as plt
import re # regular expression
import argparse # for optional arguments
parser = argparse.ArgumentParser()
parser.add_argument('File_Name', help="Enter the file name | At least one file is required to graph")
args=parser.parse_args()

data_cols = {'InterfaceNum':[],"IndexNum":[],"SyntaxName":[],"Counter":[],"TimeStamp":[]}
fileN = args.File_Name
input_data = fileN
expr = r"""
    TIMESTAMP:\s(\d+)           # date    - TimeStamp
    |                           # ** OR **
    ((?:\.\d+)+)                # num     - InterfaceNum
        \.(\d+)\s=\s            # num2    - IndexNum
            (\w+):\s            # syntax  - SyntaxName
                (\w+)           # counter - Counter
    """
expr = re.compile(expr, re.VERBOSE)
data = {}
keys = ['TimeStamp', 'InterfaceNum', 'IndexNum', 'SyntaxName', 'Counter']


with io.StringIO(input_data) as data_file:
    for line in data_file:
        try:
            find_data = expr.findall(line)[0]
            vals = [date, num, num2, syntax, counter] = list(find_data)
            if date:
                cur_date = date
                data[cur_date] = {k: [] for k in keys}
            elif num:
                vals[0] = cur_date
                for k, v in zip(keys, vals):
                    data[cur_date][k].append(v)
        except IndexError:
            # expr.findall(...)[0] indexes an empty list when there's no
            # match.
            pass

data_frames = [pd.DataFrame.from_dict(v) for v in data.values()]

print(data_frames[0])

我得到的错误

Traceback (most recent call last):
  File "v1.py", line 47, in <module>
    print(data_frames[0])
IndexError: list index out of range


新数据

TIMESTAMP: 1579051725 20100114-202845
.1.2.3.4.5.6.7.8.9 = 234567890
ifTb: name-nam-na
.1.3.4.1.2.1.1.1.1.1.1.128 = STRING: AA1
.1.3.4.1.2.1.1.1.1.1.1.129 = STRING: Eth1
.1.3.4.1.2.1.1.1.1.1.1.130 = STRING: Eth2
.1.2.3.4.5.6.7.8.9.10.11.131 = INT32: A

更新代码 (v2)

import pandas as pd
import io
import matplotlib
import re # regular expression

file = r"/home/rusif.eyvazli/Python_Projects/network-switch-packet-loss/s_data.txt"



def get_dev_data(file_path, timestamp=None, iface_num=None, idx_num=None, 
                 syntax=None, counter=None):

    timestamp = timestamp or r'\d+'
    iface_num = iface_num or r'(?:\.\d+)+'
    idx_num   = idx_num   or r'\d+'
    syntax    = syntax    or r'\w+'
    counter   = counter   or r'\w+'

#     expr = r"""
#         TIMESTAMP:\s({timestamp})   # date    - TimeStamp
#         |                           # ** OR **
#         ({iface_num})               # num     - InterfaceNum
#             \.({idx_num})\s=\s      # num2    - IndexNum
#                 ({syntax}):\s       # syntax  - SyntaxName
#                     ({counter})     # counter - Counter
#         """

    expr = r"TIMESTAMP:\s(\d+)|((?:\.\d+)+)\.(\d+)\s=\s(\w+):\s(\w+)"

#   expr = re.compile(expr, re.VERBOSE)

    expr = re.compile(expr)

    rows = []
    keys = ['TimeStamp', 'InterfaceNum', 'IndexNum', 'SyntaxName', 'Counter']
    cols = {k: [] for k in keys}

    with open(file_path, 'r') as data_file:
        for line in data_file:
            try:

                find_data = expr.findall(line)[0]
                vals = [tstamp, num, num2, sntx, ctr] = list(find_data)
                if tstamp:
                    cur_tstamp = tstamp
                elif num:
                    vals[0] = cur_tstamp
                    rows.append(vals)
                    for k, v in zip(keys, vals):
                         cols[k].append(v)
            except IndexError:
                # expr.findall(line)[0] indexes an empty list when no match.
                pass

    return rows, cols

const_num    = '.1.3.4.1.2.1.1.1.1.1.1'
const_syntax = 'STRING'

result_5 = get_dev_data(file)

# Use the results of the first dict retrieved to initialize the master
# dictionary.
master_dict = result_5[1]

df = pd.DataFrame.from_dict(master_dict)

df = df.loc[(df['InterfaceNum'] == '.1.2.3.4.5.6.7.8.9.10.11') & (df['SyntaxName'] == 'INT32' )] 

print(f"\n{df}")

输出

    TimeStamp              InterfaceNum IndexNum SyntaxName Counter
3  1579051725  .1.2.3.4.5.6.7.8.9.10.11      131      INT32       A

【问题讨论】:

  • @rahlf23,我更新了。当我注释掉字典部分时,输出是 Num 和 Num2。它不会被拆分,也不会只打印 Num 部分。
  • 嗨@r_e,看看我发布的答案是否有什么可以使用的。
  • 嗨@Todd ,我将在一些时间检查一下。由于时区不同,我不能早点回复,我的不好
  • @r_e,你怎么会选择只说“请使用正则表达式”的答案??这个答案甚至没有详细说明。这不公平。你在上面使用我的代码,所以我假设你让它为你工作。
  • 啊……谢谢你解决这个问题。我知道这很有趣,但我很喜欢这个网站上的积分给我的验证=)谢谢@r_e 我很高兴你的代码能正常工作。随意删除不需要的部分。看起来您可以删除注释掉的详细表达式并只保留输入参数之一。

标签: python regex python-3.x pandas read-data


【解决方案1】:

使用正则表达式解析原始文件输入

下面的函数是如何使用正则表达式解析原始文件输入的示例。

循环正则表达式捕获组以构建记录。这是一种可重用的模式,可以在许多情况下应用。在“复合正则表达式中的分组”部分中有更多关于它如何工作的信息。

该函数将过滤与参数值匹配的记录。将它们保留为默认值,该函数返回所有数据行。

def get_dev_data(file_path, timestamp=None, iface_num=None, idx_num=None, 
                 syntax=None, counter=None):
    timestamp = timestamp or r'\d+'
    iface_num = iface_num or r'(?:\.\d+)+'
    idx_num   = idx_num   or r'\d+'
    syntax    = syntax    or r'\w+'
    counter   = counter   or r'\w+'
    expr = rf"""
        TIMESTAMP:\s({timestamp})   # date    - TimeStamp
        |                           # ** OR **
        ({iface_num})               # num     - InterfaceNum
            \.({idx_num})\s=\s      # num2    - IndexNum
                ({syntax}):\s       # syntax  - SyntaxName
                    ({counter})     # counter - Counter
        """
    expr = re.compile(expr, re.VERBOSE)
    rows = []
    keys = ['TimeStamp', 'InterfaceNum', 'IndexNum', 'SyntaxName', 'Counter']
    cols = {k: [] for k in keys}

    with open(file_path, 'r') as data_file:
        for line in data_file:
            try:
                find_data = expr.findall(line)[0]
                vals = [tstamp, num, num2, sntx, ctr] = list(find_data)
                if tstamp:
                    cur_tstamp = tstamp
                elif num:
                    vals[0] = cur_tstamp
                    rows.append(vals)
                    for k, v in zip(keys, vals):
                        cols[k].append(v)
            except IndexError:
                # expr.findall(line)[0] indexes an empty list when no match.
                pass
    return rows, cols

返回一个元组。第一项rows是简单格式的数据行列表;第二项,cols,是一个以列名作为键的字典,每个键都有一个行数据列表。两者都包含相同的数据,并且都可以被 Pandas 分别使用pd.DataFrame.from_records()pd.DataFrame.from_dict() 消化。

过滤示例

这显示了如何使用函数参数过滤记录。我认为最后一个 result_4 符合问题中的描述。假设iface_num 设置为您的const_numsyntax 设置为您的const_syntax 值。只会返回匹配的记录。

if __name__ == '__main__':

    file = r"/test/inputdata.txt"

    result_1 = get_dev_data(file)[0]
    result_2 = get_dev_data(file, counter='Eth2')[0]
    result_3 = get_dev_data(file, counter='Eth2|AA1')[0]
    result_4 = get_dev_data(file,
                           iface_num='.1.3.4.1.2.1.1.1.1.1.1', syntax='STRING')[0]

    for var_name, var_val in zip(['result_1', 'result_2', 'result_3', 'result_4'],
                                 [ result_1,   result_2,   result_3,   result_4]):

        print(f"{var_name} = {var_val}")

输出

result_1 = [['1579051725', '.1.3.4.1.2.1.1.1.1.1.1', '128', 'STRING', 'AA1'], ['1579051725', '.1.3.4.1.2.1.1.1.1.1.1', '129', 'STRING', 'Eth1'], ['1579051725', '.1.3.4.1.2.1.1.1.1.1.1', '130', 'STRING', 'Eth2']]
result_2 = [['1579051725', '.1.3.4.1.2.1.1.1.1.1.1', '130', 'STRING', 'Eth2']]
result_3 = [['1579051725', '.1.3.4.1.2.1.1.1.1.1.1', '128', 'STRING', 'AA1'], ['1579051725', '.1.3.4.1.2.1.1.1.1.1.1', '130', 'STRING', 'Eth2']]
result_4 = [['1579051725', '.1.3.4.1.2.1.1.1.1.1.1', '128', 'STRING', 'AA1'], ['1579051725', '.1.3.4.1.2.1.1.1.1.1.1', '129', 'STRING', 'Eth1'], ['1579051725', '.1.3.4.1.2.1.1.1.1.1.1', '130', 'STRING', 'Eth2']]

使用第一个返回的元组项,可以使用它们的偏移量从返回的记录中访问列数据。例如TimeStamp 会像first_item[0][0] 一样被访问——第一行,第一列。或者,可以将行转换为数据框并以这种方式访问​​。

输入文件/test/inputdata.txt

TIMESTAMP: 1579051725 20100114-202845
.1.2.3.4.5.6.7.8.9 = 234567890
ifTb: name-nam-na
.1.3.4.1.2.1.1.1.1.1.1.128 = STRING: AA1
.1.3.4.1.2.1.1.1.1.1.1.129 = STRING: Eth1
.1.3.4.1.2.1.1.1.1.1.1.130 = STRING: Eth2

将行数据转换为 Pandas 数据框

函数输出中的第一个元组项将是与我们定义的列相对应的数据行。可以使用pd.DataFrame.from_records() 将这种格式转换为 Pandas 数据帧:

>>> row_data = [['1579051725', '.1.3.4.1.2.1.1.1.1.1.1', '128', 'STRING', 'AA1']]]
>>>
>>> column_names = ['TimeStamp', 'InterfaceNum', 'IndexNum', 
...                 'SyntaxName', 'Counter']
>>>
>>> pd.DataFrame.from_records(row_data, columns=column_names)
    TimeStamp            InterfaceNum IndexNum SyntaxName Counter
0  1579051725  .1.3.4.1.2.1.1.1.1.1.1      128     STRING     AA1
>>> 

将列数据转换为 Pandas 数据框

该函数还生成一个字典作为返回元组的第二项,其中包含相同的数据,它也可以使用pd.DataFrame.from_dict() 生成相同的数据帧。

>>> col_data = {'TimeStamp': ['1579051725'], 
...             'InterfaceNum': ['.1.3.4.1.2.1.1.1.1.1.1'], 
...             'IndexNum': ['128'], 'SyntaxName': ['STRING'], 
...             'Counter': ['AA1']}
>>> 
>>> pd.DataFrame.from_dict(col_data)
    TimeStamp            InterfaceNum IndexNum SyntaxName Counter
0  1579051725  .1.3.4.1.2.1.1.1.1.1.1      128     STRING     AA1
>>> 

字典示例

以下是过滤文件数据、初始化持久字典的几个示例。然后过滤更多数据并将其添加到持久字典中。我认为这也接近问题中描述的内容。

const_num    = '.1.3.4.1.2.1.1.1.1.1.1'
const_syntax = 'STRING'

result_5 = get_dev_data(file, iface_num=const_num, syntax=const_syntax)

# Use the results of the first dict retrieved to initialize the master
# dictionary.
master_dict = result_5[1]

print(f"master_dict = {master_dict}")

result_6 = get_dev_data(file, counter='Eth2|AA1')

# Add more records to the master dictionary.
for k, v in result_6[1].items():
    master_dict[k].extend(v)

print(f"master_dict = {master_dict}")

df = pandas.DataFrame.from_dict(master_dict)

print(f"\n{df}")

输出

master_dict = {'TimeStamp': ['1579051725', '1579051725', '1579051725'], 'InterfaceNum': ['.1.3.4.1.2.1.1.1.1.1.1', '.1.3.4.1.2.1.1.1.1.1.1', '.1.3.4.1.2.1.1.1.1.1.1'], 'IndexNum': ['128', '129', '130'], 'SyntaxName': ['STRING', 'STRING', 'STRING'], 'Counter': ['AA1', 'Eth1', 'Eth2']}
master_dict = {'TimeStamp': ['1579051725', '1579051725', '1579051725', '1579051725', '1579051725'], 'InterfaceNum': ['.1.3.4.1.2.1.1.1.1.1.1', '.1.3.4.1.2.1.1.1.1.1.1', '.1.3.4.1.2.1.1.1.1.1.1', '.1.3.4.1.2.1.1.1.1.1.1', '.1.3.4.1.2.1.1.1.1.1.1'], 'IndexNum': ['128', '129', '130', '128', '130'], 'SyntaxName': ['STRING', 'STRING', 'STRING', 'STRING', 'STRING'], 'Counter': ['AA1', 'Eth1', 'Eth2', 'AA1', 'Eth2']}

    TimeStamp            InterfaceNum IndexNum SyntaxName Counter
0  1579051725  .1.3.4.1.2.1.1.1.1.1.1      128     STRING     AA1
1  1579051725  .1.3.4.1.2.1.1.1.1.1.1      129     STRING    Eth1
2  1579051725  .1.3.4.1.2.1.1.1.1.1.1      130     STRING    Eth2
3  1579051725  .1.3.4.1.2.1.1.1.1.1.1      128     STRING     AA1
4  1579051725  .1.3.4.1.2.1.1.1.1.1.1      130     STRING    Eth2

如果不需要字典数据的所有列,可以使用&lt;dict&gt;.pop(&lt;key&gt;) 省去其中的键。或者,您可以从根据数据创建的任何数据框中删除列。


复合正则表达式中的分组

此表达式显示在函数的所有参数保留为默认值时在函数中计算的表达式。

expr = r"""
    TIMESTAMP:\s(\d+)           # date    - TimeStamp
    |                           # ** OR **
    ((?:\.\d+)+)                # num     - InterfaceNum
        \.(\d+)\s=\s            # num2    - IndexNum
            (\w+):\s            # syntax  - SyntaxName
                (\w+)           # counter - Counter
    """

在上面的正则表达式中,有两个由 OR 分隔的替代语句,| 运算符。这些备选方案匹配一行时间戳数据或设备数据。在这些子表达式中是用于捕获特定字符串数据的分组。匹配组是通过将括号 (...) 放在子表达式周围来创建的。非分组括号的语法是(?:...)

无论哪个替代子表达式匹配,每次成功调用 re.findall() 时,仍然会返回相同数量的匹配组。 可能有点违反直觉,但这就是它的工作原理。

但是,此功能确实可以轻松编写代码来提取您已捕获的匹配字段,因为您知道无论匹配的子表达式如何,组应该位于的位置:

     [<tstamp>, <num>, <num2>, <syntax>, <counter>]
     # ^expr1^  ^.............expr2..............^

而且由于无论哪个子表达式匹配,我们都有可预测数量的匹配组,因此它启用了一种可以在许多场景中应用的循环模式。通过测试单个匹配组是否为空,我们可以知道循环中的哪个分支来处理命中的子表达式的数据。

        if tstamp:
            # First expression hit.
        elif num:
            # Second alt expression hit.

当表达式与具有时间戳的文本行匹配时,第一个子表达式命中,其组将被填充。

>>> re.findall(expr, "TIMESTAMP: 1579051725 20100114-202845", re.VERBOSE)
[('1579051725', '', '', '', '')]

在这里,表达式中的第一个分组被填写,其他组为空白。其他分组属于其他子表达式。

现在,当表达式与设备数据的第一行匹配时,第二个子表达式会命中,并填充其组。时间戳组为空白。

>>> re.findall(expr, ".1.3.4.1.2.1.1.1.1.1.1.128 = STRING: AA1", re.VERBOSE)
[('', '.1.3.4.1.2.1.1.1.1.1.1', '128', 'STRING', 'AA1')]

最后,当两个子表达式都不匹配时,整个表达式就不会被命中。在这种情况下,我们得到一个空列表。

>>> re.findall(expr, "ifTb: name-nam-na", re.VERBOSE)
[]
>>> 

相比之下,这里的表达式没有冗长的语法和文档:

expr = r"TIMESTAMP:\s(\d+)|((?:\.\d+)+)\.(\d+)\s=\s(\w+):\s(\w+)"

【讨论】:

  • 嗨@Todd,感谢您的精彩解释!我很感激!为了实现它从文件中读取数据,我将 input_data 分配给了 fileN。但它给了我 IndexError “列表索引超出范围” 我已经用更新的代码更新了我的问题。你能检查一下吗?我该如何解决这个问题?
  • @r_e 您将不得不用您要读取的文件替换语句 io.StringIO(input_data)with open(file_path, 'r') as data_file: io.StringIO 只是为了使用您显示的数据演示算法在你的问题中作为一个字符串。
  • 感谢您提供的信息。最后一件事,你能解释一下if语句吗?我不确定我是否理解 data[cur_date] = {k: [] for k in keys}part 以及 else if 部分。欣赏!
  • 另外,如果我想打印出一组特定的nums 或syntaxnames,我只需要添加 if 语句为if num = ".1.3.4.2.1.1.1.1.1.1" print (data_frames[0]) 吗?
  • 我稍微简化了代码@r_e。希望它更容易遵循。顺便说一句,你说的代码不起作用,不应该起作用。它似乎正在尝试为数据框列分配一个值。无论如何,新代码可能会更清晰,您可以更轻松地确定如何访问数据。
【解决方案2】:

请使用python“re”包在python中使用正则表达式。这个包使得在 python 中使用正则表达式变得非常容易,你可以使用这个包中的各种函数来实现你所需要的。 https://docs.python.org/3/library/re.html#module-contents 使用此链接阅读文档。

有一个叫做 re.Pattern.match() 的函数可以用来匹配模式,你需要试试这个。

【讨论】:

  • 感谢您的链接!我将再次尝试使用 RE。同时,如果可以的话,您能否告诉我如何使用问题中的 RE 将 .1.3.4.1.2.1.1.1.1.1.1.128 = STRING: AA1 拆分为 4 件?我试过了,但是符号很混乱,因为有重复。
  • 您想如何拆分 .1.3.4.1.2.1.1.1.1.1.1.128 = STRING: AA1。给我一个例子,以便我可以帮助你。
  • 当然。我想拆分 .1.3.4.1.2.1.1.1.1.1.1 然后 128 然后 STRING 然后 AA1。拆分后,我会将我的常量字典(修复字典后)与 .1.3.4.1.2.1.1.1.1.1.1 匹配,如果匹配,将在该字典中存储 128。然后将 STRING 与常量字典匹配。如果 STRING 匹配,则将 AA1 存储在该字典中。但同样,我想拆分 .1.3.4.1.2.1.1.1.1.1.1 然后 128 然后 STRING 然后 AA1。非常感谢!
  • a = ".1.3.4.1.2.1.1.1.1.1.1.128 = STRING: AA1" charcaters_splitted = a.split() >>> charcaters_splitted ['.1.3.4.1.2.1.1.1. 1.1.1.128', '=', 'STRING:', 'AA1'] >>> number_splitted = charcaters_splitted[0].split('.') >>> number_splitted ['', '1', '3', '4', '1', '2', '1', '1', '1', '1', '1', '1', '128'] >>> number_len_one = [i for i in number_splitted if len(i) >> number_len_one ['', '1', '3', '4', '1', '2', '1', '1', '1', ' 1', '1', '1'] joined = ".".join(number_len_one) >>> joined '.1.3.4.1.2.1.1.1.1.1.1' 这就是我拆分数字的方式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-09
  • 2013-04-04
  • 2015-02-24
  • 2017-02-28
  • 1970-01-01
相关资源
最近更新 更多