【问题标题】:How to parse multiple dates from a block of text in Python (or another language)如何从 Python(或其他语言)中的文本块中解析多个日期
【发布时间】:2011-08-11 15:33:48
【问题描述】:

我有一个字符串,其中包含多个日期值,我想将它们全部解析出来。该字符串是自然语言,所以到目前为止我发现的最好的东西是dateutil

不幸的是,如果一个字符串中有多个日期值,dateutil 会抛出一个错误:

>>> s = "I like peas on 2011-04-23, and I also like them on easter and my birthday, the 29th of July, 1928"
>>> parse(s, fuzzy=True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/pymodules/python2.7/dateutil/parser.py", line 697, in parse
    return DEFAULTPARSER.parse(timestr, **kwargs)
  File "/usr/lib/pymodules/python2.7/dateutil/parser.py", line 303, in parse
    raise ValueError, "unknown string format"
ValueError: unknown string format

关于如何解析长字符串中的所有日期有什么想法吗?理想情况下,会创建一个列表,但如果需要,我可以自己处理。

我正在使用 Python,但在这一点上,其他语言可能还可以,如果他们能完成工作的话。

PS - 我想我可以在中间递归地拆分输入文件并尝试,再试一次,直到它工作,但它是一个地狱。

【问题讨论】:

  • 在您的示例字符串中,您是否认为“复活节”是您要解析的日期?
  • 不。正在测试它是否有效,但无论哪种方式我都不太在意。
  • 使用 DateUtil 1.5 它当然可以工作,我的错。但我仍然想以一种比 MattH Shawn Chin 更清洁/更快的方法来奖励它……

标签: python parsing python-dateutil


【解决方案1】:

看起来,最简单的方法是将 dateutil parser 修改为具有模糊多重选项。

parser._parse 获取您的字符串,使用_timelex 对其进行标记,然后将标记与parserinfo 中定义的数据进行比较。

Here,如果某个标记与parserinfo 中的任何内容都不匹配,除非fuzzy 为True,否则解析将失败。

我建议您在没有任何已处理的时间标记时允许不匹配,然后当您遇到不匹配时,在该点处理已解析的数据并再次开始查找时间标记。

不应该花太多力气。


更新

当你在等待你的补丁推出时......

这有点hacky,使用库中的非公共函数,但不需要修改库,也不是反复试验。如果您有任何可以变成浮点数的单独令牌,您可能会误报。您可能需要对结果进行更多过滤。

from dateutil.parser import _timelex, parser

a = "I like peas on 2011-04-23, and I also like them on easter and my birthday, the 29th of July, 1928"

p = parser()
info = p.info

def timetoken(token):
  try:
    float(token)
    return True
  except ValueError:
    pass
  return any(f(token) for f in (info.jump,info.weekday,info.month,info.hms,info.ampm,info.pertain,info.utczone,info.tzoffset))

def timesplit(input_string):
  batch = []
  for token in _timelex(input_string):
    if timetoken(token):
      if info.jump(token):
        continue
      batch.append(token)
    else:
      if batch:
        yield " ".join(batch)
        batch = []
  if batch:
    yield " ".join(batch)

for item in timesplit(a):
  print "Found:", item
  print "Parsed:", p.parse(item)

产量:

发现:2011 04 23
解析:2011-04-23 00:00:00
发现时间:1928 年 7 月 29 日
解析:1928-07-29 00:00:00

Dieter 更新

Dateutil 2.1 似乎是为了与 python3 兼容而编写的,并使用名为 six 的“兼容性”库。它有问题,它没有将 str 对象视为文本。

如果您将字符串作为 unicode 或类似文件的对象传递,则此解决方案适用于 dateutil 2.1:

from cStringIO import StringIO
for item in timesplit(StringIO(a)):
  print "Found:", item
  print "Parsed:", p.parse(StringIO(item))

如果要在 parserinfo 上设置选项,请实例化 parserinfo 并将其传递给 parser 对象。例如:

from dateutil.parser import _timelex, parser, parserinfo
info = parserinfo(dayfirst=True)
p = parser(info)

【讨论】:

  • 可能是最有效的解决方案。 +1。当然,修改库本身会使部署/维护更加困难,除非这些更改被吸收到官方源中。
  • 这是一个了不起的答案 - 迄今为止我在 SO 上得到的最好的答案。将对其进行测试,并让您知道情况如何。谢谢!
  • 这很好用。它有我需要捕获的 TypeErrors 和 ValueErrors,以及许多误报。错误很容易发现,我通过对当年的任何内容进行核对来消除误报(我的语料库只有旧日期)。再次感谢。
  • 新闻下this page 有一条评论说你应该为 Python 2.x 使用 DateUtil 1.x。
  • @Praveen:这是因为and是一个info.jump的token,用于组合时间token,并不一定表示一系列时间token已经结束。我建议您修改timesplit 的行为,将info.jump 视为休息。 IE。 if timetoken(token) and not info.jump(token): batch.append(token)
【解决方案2】:

当我离线时,我对昨天在这里发布的答案感到困扰。是的,它完成了这项工作,但它不必要地复杂且效率极低。

这是封底版本,应该会做得更好!

import itertools
from dateutil import parser

jumpwords = set(parser.parserinfo.JUMP)
keywords = set(kw.lower() for kw in itertools.chain(
    parser.parserinfo.UTCZONE,
    parser.parserinfo.PERTAIN,
    (x for s in parser.parserinfo.WEEKDAYS for x in s),
    (x for s in parser.parserinfo.MONTHS for x in s),
    (x for s in parser.parserinfo.HMS for x in s),
    (x for s in parser.parserinfo.AMPM for x in s),
))

def parse_multiple(s):
    def is_valid_kw(s):
        try:  # is it a number?
            float(s)
            return True
        except ValueError:
            return s.lower() in keywords

    def _split(s):
        kw_found = False
        tokens = parser._timelex.split(s)
        for i in xrange(len(tokens)):
            if tokens[i] in jumpwords:
                continue 
            if not kw_found and is_valid_kw(tokens[i]):
                kw_found = True
                start = i
            elif kw_found and not is_valid_kw(tokens[i]):
                kw_found = False
                yield "".join(tokens[start:i])
        # handle date at end of input str
        if kw_found:
            yield "".join(tokens[start:])

    return [parser.parse(x) for x in _split(s)]

示例用法:

>>> parse_multiple("I like peas on 2011-04-23, and I also like them on easter and my birthday, the 29th of July, 1928")
[datetime.datetime(2011, 4, 23, 0, 0), datetime.datetime(1928, 7, 29, 0, 0)]

可能值得注意的是,在处理空/未知字符串时,它的行为与dateutil.parser.parse 略有不同。 Dateutil 将返回当前日期,而 parse_multiple 返回一个空列表,恕我直言,这是人们所期望的。

>>> from dateutil import parser
>>> parser.parse("")
datetime.datetime(2011, 8, 12, 0, 0)
>>> parse_multiple("")
[]

附注刚刚发现 MattH's updated answer 做了非常相似的事情。

【讨论】:

  • 这似乎比 MattH 的建议更可靠,但在更大的测试中表现很糟糕(不足为奇)。不过感谢您的帮助!
  • @mlissner 不客气。这是一个有趣的问题。以至于我昨晚仔细考虑了一下,想出了我认为更好的解决方案。查看更新的答案。
【解决方案3】:

我认为如果你把“单词”放在一个数组中,它应该可以解决问题。这样你就可以验证它是否是一个日期,并放入一个变量。

一旦你有了日期,你应该使用datetime library library。

【讨论】:

    【解决方案4】:

    为什么不编写一个涵盖日期可能出现的所有可能形式的正则表达式模式,然后启动正则表达式来探索文本?我想用字符串表达一个日期的方式没有几十种。

    唯一的问题是收集日期表达式的最大值

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-15
      • 1970-01-01
      • 1970-01-01
      • 2020-03-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多