【问题标题】:How to extract date from string using Python 3.x如何使用 Python 3.x 从字符串中提取日期
【发布时间】:2017-07-13 12:11:59
【问题描述】:

我需要使用 Python 3.4 从 SMS 中找出电话账单到期日我使用过 dateutil.parser 和 datefinder,但根据我的用例没有成功。

示例:sms_text = "您的 2017 年 6 月 72.23 卢比的电话账单已发送至您的 regd 电子邮件 ID abc@xyz.com。请检查收件箱"

代码 1:

import datefinder
due_dates = datefinder.find_dates(sms_text)
for match in due_dates:
    print(match)

结果:2017-07-17 00:00:00

代码 2:

import dateutil.parser as dparser
due_date = dparser.parse(sms_text,fuzzy=True)
print(due_date)

结果:ValueError 可能是因为文本中有多个日期

如何从此类文本中选择截止日期?日期格式不固定,但文本中有 2 个日期:一个是生成账单的月份,另一个是到期日,顺序相同。即使我得到一个正则表达式来解析文本,那也很棒。

更多示例文本:

  1. 您好!您的电话未结帐款是 293.72 到期日是 7 月 3 日。
  2. 219 卢比的日期为 219 年 6 月 6 日的账单今天到期,您的电话号码为 1234567890
  3. 17 年 6 月 6 日 219 卢比的账单将于 7 月 5 日到期,您的电话号码为 1234567890
  4. 2017 年 6 月 27 日您的运营商固网/宽带 ID 1234567890 的账单已从 xyz@abc.com 发送至 abc@xyz.com。到期金额:3,764.53 卢比,到期日期:17 年 7 月 16 日。
  5. 2017 年 6 月 21 日电话号码账单的详细信息。 1234567890:到期总额:374.12 卢比,到期日:2017 年 7 月 9 日,账单交付日期:2017 年 6 月 25 日,
  6. 您好!您的手机账单 1234567890,2017 年 6 月 18 日,付款到期日 2017 年 7 月 6 日已发送至 abc@xyz.com
  7. 尊敬的客户,您的电话账单 191.24 卢比应于 2017 年 6 月 25 日到期
  8. 嗨!你的电话费卢比。 560.41 将于 2017 年 3 月 7 日到期。

【问题讨论】:

  • 如果你的字符串这么简单,你可以使用正则表达式。
  • @cᴏʟᴅsᴘᴇᴇᴅ 我很想先生...字符串很简单,但日期格式可能会有所不同。另外,我对正则表达式不是很好。如果结果是提取到期日期,那么正则表达式对我来说也是完美的。
  • 当你说我警惕的日期格式时,这会敲响一些警钟。您会遇到哪些可能的日期格式?有一个正则表达式适用于一种格式但不适用于其他所有格式是没有意义的。
  • 到期日可以是:YYYY-MM-DD、DD-MM-YYYY、MMMMD、DDMMM。账单月份可以是:MMM-YY、MMM'YY、MMM YYYY。这些是我遇到的几个例子。由于格式不固定,我试图使用可以检测不同日期格式的 Python3.x 实用程序来解决它
  • 我很抱歉。我不确定正则表达式能否处理这么多格式。

标签: python python-3.x date


【解决方案1】:

使用dateutil.parser的一个想法:

from dateutil.parser import parse

for s in sms_text.split():
    try:
        print(parse(s))
    except ValueError:
        pass

【讨论】:

  • 在解析金额等其他数值时会搞砸
【解决方案2】:

有两件事会阻止datefinder 正确解析您的样本:

  1. 账单金额:数字被解释为年份,因此如果它们有 3 位或 4 位数字,则会创建一个日期
  2. datefinder 定义为分隔符的字符可能会妨碍找到合适的日期格式(在本例中为 ':'

这个想法是首先通过删除阻止datefinder 识别所有日期的文本部分来清理文本。不幸的是,这是一个尝试和错误,因为这个包使用的正则表达式太大,我无法彻底分析。

def extract_duedate(text):
    # Sanitize the text for datefinder by replacing the tricky parts 
    # with a non delimiter character
    text = re.sub(':|Rs[\d,\. ]+', '|', text, flags=re.IGNORECASE)

    return list(datefinder.find_dates(text))[-1]

Rs[\d,\. ]+ 将删除帐单金额,因此它不会被误认为是日期的一部分。它将匹配'Rs[.][ ][12,]345[.67]' 形式的字符串(实际上还有更多变体,但这只是为了说明)。

显然,这是一个原始示例函数。 以下是我得到的结果:

1 : 2017-07-03 00:00:00
2 : 2017-06-06 00:00:00 # Wrong result: first date instead of today
3 : 2017-07-05 00:00:00
4 : 2017-07-16 00:00:00
5 : 2017-06-25 00:00:00
6 : 2017-07-06 00:00:00
7 : 2017-06-25 00:00:00
8 : 2017-03-07 00:00:00

示例 2 存在一个问题:datefinder 单独无法识别 'today'

例子:

>>> list(datefinder.find_dates('Rs 219 is due today'))
[datetime.datetime(219, 7, 13, 0, 0)]
>>> list(datefinder.find_dates('is due today'))
[]

因此,为了处理这种情况,我们可以简单地将标记 'today' 替换为当前日期作为第一步。这将提供以下功能:

def extract_duedate(text):
    if 'today' in text:
        text = text.replace('today', datetime.date.today().isoformat())

    # Sanitize the text for datefinder by replacing the tricky parts 
    # with a non delimiter character
    text = re.sub(':|Rs[\d,\. ]+', '|', text, flags=re.IGNORECASE)

    return list(datefinder.find_dates(text))[-1]

现在所有样本的结果都很好:

1 : 2017-07-03 00:00:00
2 : 2017-07-18 00:00:00 # Well, this is the date of my test
3 : 2017-07-05 00:00:00
4 : 2017-07-16 00:00:00
5 : 2017-06-25 00:00:00
6 : 2017-07-06 00:00:00
7 : 2017-06-25 00:00:00
8 : 2017-03-07 00:00:00

如果需要,可以让函数返回所有日期,它们都应该是正确的。

【讨论】:

  • 同意。我正在按照类似的思路工作,尝试拆分文本并检查单个单词的日期。在您的解决方案中,我认为正则表达式可能需要一些修改,因为 datetime.datetime(2017, 7, 17, 0, 0) 文本中没有日期。它仍然在某种程度上指的是别的东西
  • @DrunkKnight 实际上,我认为 dateutil 到今天为止已经填满了这一年。无论如何,我正在使用您添加的示例进行测试,它几乎适用于所有情况。
  • 正则表达式是否有效或者您正在使用 Python 实用程序?
  • @DrunkKnight 我正在使用与我的答案相同的方法来寻找更好的正则表达式。我们的想法是找到导致问题的部分,以便您可以先清理字符串。
  • @DrunkKnight 我更新了我的答案,这并不完美,但我希望这会有所帮助。
【解决方案3】:

为什么不直接使用regex?如果您的输入字符串始终包含此子字符串 due on ... has been 您可以这样做:

import re
from datetime import datetime

string = """Your phone bill for Jun'17 of Rs.72.23 due on 15-07-2017 has been
 sent to your regd email ID abc@xyz.com. Pls check Inbox"""

match_obj = re.search(r'due on (.*) has been', string)

if match_obj:
    date_str = match_obj.group(1)
else:
    print "No match!!"
try:
    # DD-MM-YYYY
    print datetime.strptime(date_str, "%d-%m-%Y")
except ValueError:
    # try another format
    try:
        print datetime.strptime(date_str, "%Y-%m-%d")
    except ValueError:
        try:
            print datetime.strptime(date_str, "%m-%d")
        except ValueError:
            ...

【讨论】:

  • 使用dateutil.parser 比在最后尝试所有可能的格式更容易。
  • @Alexey 它可能到期,到期日期是,到期日期为 。文本或日期的格式不固定。
  • @Gall 我正在尝试使用它,但我无法理解使用它的结果。
【解决方案4】:

以您提供的短信为例:

sms_text = "Your phone bill for Jun'17 of Rs.72.23 due on 15-07-2017 has been sent to your regd email ID abc@xyz.com. Pls check Inbox"

可以使用 pythons build in regex 模块来匹配字符串的“due on”和“has be”部分。

import re

sms_text = "Your phone bill for Jun'17 of Rs.72.23 due on 15-07-2017 has been sent to your regd email ID abc@xyz.com. Pls check Inbox"

due_date = re.split('due on', re.split('has been', sms_text)[0])[1]

print(due_date)

结果:2017 年 7 月 15 日

在此示例中,日期格式无关紧要,但重要的是要分割字符串的单词保持一致。

【讨论】:

  • 与 Alexey 发布的内容类似,但文字不一致,问题出在哪里。
  • 您能否在问题中添加限制?因为它对于试图帮助你的人来说是很有价值的信息。
  • 我真诚地希望有人能提供一些不受限制的解决方案,因为它本质上是日期提取,Python 提供的解决方案。添加限制对我没有帮助,我们也不会得到一个万无一失的解决方案
猜你喜欢
  • 1970-01-01
  • 2014-09-23
  • 2016-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-01
  • 1970-01-01
相关资源
最近更新 更多