【问题标题】:Extract multiple data fields from a non-uniformly formatted string从非统一格式的字符串中提取多个数据字段
【发布时间】:2014-01-04 23:18:52
【问题描述】:

无聊的背景故事:

我的银行报告有 .pdf 和 .csv 格式,但旧的报告只能以 pdf 格式提供给我。我想以与新数据相同的格式保存数据,以便更容易搜索,但 pdf 受到保护,在使用解锁器和 pdf 到文本转换器后,我最终得到了格式非常糟糕的文件。

我有文件,其中每一行都是一个事务(例如:23.12 22.12.09 Verfügung Geldautomat\t63050000 / 9000481400\tGA NR00002317 BLZ63050000 0\t22.12/14.17UHR ESELSBERGW EUR 50,00\t-50,00

将数据字段替换为数字会产生以下顺序和分隔符:

1 2 3\t7 / 6\t5\t4\t8

但我想要这种格式(原始数据中不存在的字段 0、9 和 10 是静态的)

"0";"1";"3";"4";"5";"6";"7";"8";"9";"10"

这是我目前的方法(没有 I/O 部分)

def readtrans(line):
    d1, d2, rest = line.split(' ', 2)
    d3, rest, d5, d4, d8 = rest.split('\t')
    d7, d6 = rest.split(' / ')
    return [d1, d2, d3, d4, d5, d6, d7, d8]

不幸的是,它在第​​一个文件的第 3 行崩溃了,因为字段 3 的某些值的字段 5 和 6 是空的。在添加 if 子句来解决这个问题后,脚本前进到第 5 行只是再次崩溃,因为字段 4 也可能包含选项卡。我也可以解决这个问题,但我将其作为寻找更灵活解决方案的线索。

大多数时候,当我需要从文本中提取数据时,我会相应地查看分隔符和split()。它可能效率不高,但比查找我很少使用并反复忘记的正则表达式语法要快。在这种情况下这是一种可行的方法还是正则表达式更适合?正则表达式甚至可以处理此任务,如果可以,它仍然可读吗?你会怎么解决呢?

编辑: 这是真的,我永远不会再使用这个代码,(顺便说一句,这是我的解决方案)但这是一个非常常见的问题

def readtrans(line):
    d1, d2, rest = line.split(' ', 2)
    if rest[0] == 'A':
        d3, d7, d4, d8 = rest.split('\t')
        d5 = ''
        d6 = ''
    else:  
        d3, d7d6, d5, d4d8 = rest.split('\t', 3)
        d7, d6 = d7d6.split(' / ')
        rest = d4d8.split('\t')
        d8 = rest[-1]
        d4 = ' '.join(rest[:-1])
    return [d1, d2, d3, d4, d5, d6, d7, d8]

在思考如何改写我的问题后,我意识到它基本上是这个Convert string to variables (like format(), but in reverse)的复制品

有了新知识,我制作了这个正确解析我的示例的简短模式

import re
example = '23.12 22.12.09 Verfügung Geldautomat\t63050000 / 9000481400\tGA NR00002317 BLZ63050000 0\t22.12/14.17UHR ESELSBERGW EUR 50,00\t-50,00'
x = re.search(r'(\S+) (\S+) ([\S| ]+)\t(\S+) / (\S+)\t([\S| ]+)\t([\S| ]+)\t([\S| ]+)', example)
print x.groups()
>>>('23.12',
'22.12.09',
'Verf\xc3\xbcgung Geldautomat',
'63050000',
'9000481400',
'GA NR00002317 BLZ63050000 0',
'22.12/14.17UHR ESELSBERGW EUR 50,00',
'-50,00')

关键是使用 re.groups()

【问题讨论】:

  • 您能否用一些导致代码崩溃的行示例更新您的问题?此外,StackOverflow 可能正在吞噬你的 \t 字符,所以如果你能明确指出这些字符的去向,那就太好了。
  • 不要忘记 try/except 有利于处理预期的错误。
  • @senshin:错误是由于拆分产生的元素比预期的多或少,正如我所说,我可以解决这些问题,但我对提取具有不同模式的多个子字符串的一般方法更感兴趣,而不是这个特定的问题。

标签: python regex string-parsing


【解决方案1】:

我将做几个假设: 1)您可能永远不会再使用此代码 2) 只有几种可能的格式

我不会为此费心制定 RE,因为它不需要那么健壮。 (参见假设 1)。

我可能会尝试找出某种方法来确定我正在阅读的特定行使用的是哪种格式。然后使用一些 if 语句,通过适当的分隔步骤将其发送到您想要的顺序中的字段。 (参见假设 2)。

我很快想出了一个示例,您显然需要进行大量更改才能使其适用于您的情况,但您明白了。最难的部分可能是找出一种方法来确定要使用哪个解码器……在我的例子中,我使用了“选项卡的位置”。

def decoder1(line):
    parts = line.split("\t")
    d1, d2 = parts[0].split(",")
    d3, d4, d5, d6, d7, d8, d9 = parts[1].split(",")
    return [d1, d2, d3, d4, d5, d6, d7, d8, d9]


def decoder2(line):
    parts = line.split("\t")
    d1 = parts[0]
    d2, d3, d4, d5, d6, d7, d8, d9 = parts[1].split(",")
    return [d1, d2, d3, d4, d5, d6, d7, d8, d9]


def decoder3(line):
    parts = line.split("\t")
    d1, d2, d3, d4, d5, d6, d7 = parts[0].split(",")
    d8, d9 = parts[1].split(",")

    return [d1, d2, d3, d4, d5, d6, d7, d8, d9]


if __name__ =="__main__":
    lines = [
            "1,2\t3,4,5,6,7,8,9",
            "1\t2,3,4,5,6,7,8,9",
            "1,2,3,4,5,6,7\t8,9"
            ]

    for line in lines:
        tablocation = len((line.split("\t")[0]).split(","))
        if tablocation == 2:
            res = decoder1(line)
        elif tablocation == 1:
            res = decoder2(line)
        elif tablocation == 7:
            res = decoder3(line)
        else:
            print "Must be a new format for %s" %line
            res = "NA"
        print res

如果您有多个“解码器选项”,那么花时间开发一些 RE 可能是值得的。但如果不了解所有可能的变化,就很难提供比我在上述方法中展示的更多的帮助。

【讨论】:

    【解决方案2】:

    您的问题有些杂音,但我认为您要问的是:

    如何指定多个分隔符进行拆分,其中一些可能是 超过一个字符?

    答案是使用re.split():

    s = '1 2 3\t7 / 6\t5\t4\t8'
    
    import re
    
    re.split(r'\s/\s|\s|\t',s)
    Out[13]: ['1', '2', '3', '7', '6', '5', '4', '8']
    

    您可以根据需要将顺序重新排列到最终输出中。

    注意: 通常在这些多分隔符问题中,您可以任意指定要拆分的标记的顺序。这里不是这样。

    re.split(r'\s|\t|\s/\s',s)
    Out[14]: ['1', '2', '3', '7', '/', '6', '5', '4', '8']
    

    你需要寻找\s/\s之前你只寻找\s,因为后者是前者的子字符串。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-02
      • 2016-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多