如何改进正则表达式以避免提取纯数字并正确提取复杂日期格式?
看起来你已经为提取复杂日期搭好了正则的架子,但碰到了纯数字被误抓的坑——我太懂这种调正则调得头大的感觉了!
先帮你分析下问题出在哪:你当前的正则里,所有日期相关的部分(日、月份、中间的日分隔)都是可选的,最后只剩下\d{2,4}这个必填项,所以像29505这种纯数字串就会被误匹配。下面给你两个可行的解决思路:
思路一:直接改进正则,从根源避免纯数字匹配
首先要修正正则里的一个小错误:你用了[th|st|nd|rd]这种方括号写法,这会把|当成普通字符匹配,正确的应该用小括号分组(?:th|st|nd|rd)来表示后缀的可选分支。
然后,核心要做的是:确保匹配的内容不只是纯数字,必须包含至少一个日期相关的非数字元素(比如月份名称、日期分隔符-/.、日期后缀st/nd/rd/th)。我们可以用正向预查来实现这一点,同时调整正则结构:
import re # 修正后的正则 pattern = r'(?<![A-Za-z0-9-\./])\b(?=.*(?:[-/\s,.]|th|st|nd|rd|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))(?:\d{1,2}(?:[-/\s.]|th|st|nd|rd)?)?(?:(?:Jan|January|Feb|February|Mar|March|Apr|April|May|Jun|June|Jul|July|August|Sep|September|Oct|October|Nov|November|Dec|December)[\s,.]*)?(?:\d{1,2}(?:[-/\s,.]|th|st|nd|rd)?)?(?:\d{2,4})\b(?![A-Za-z0-9-/])' dates = df.apply(lambda row: [match for match in re.findall(pattern, row)])
这个正则开头的(?=.*(?:[-/\s,.]|th|st|nd|rd|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))就是正向预查,确保匹配的内容里至少有一个日期相关的非数字元素,这样纯数字串就会被直接排除。
思路二:后处理过滤,简单又省心
如果觉得正则调起来太繁琐,后处理过滤其实是更直观的选择——毕竟正则要覆盖所有日期格式已经够复杂了,把纯数字的过滤放在提取之后做,逻辑更清晰,也更容易调试。
你只需要在列表推导里加一个判断,过滤掉纯数字的匹配结果:
import re # 用你原来的正则也可以,或者上面修正后的 pattern = r'(?<![A-Za-z0-9-\./])\b(?:\d{1,2}[-/th|st|nd|rd\s.])?(?:(?:Jan|January|Feb|February|Mar|March|Apr|April|May|Jun|June|Jul|July|August|Sep|September|Oct|October|Nov|November|Dec|December)[\s,.]*)?(?:(?:\d{1,2})[-/th|st|nd|rd\s,.]*)?(?:\d{2,4})\b(?![A-Za-z0-9-/])' dates = df.apply(lambda row: [match for match in re.findall(pattern, row) if not match.isdigit()])
这里的if not match.isdigit()会直接过滤掉所有纯数字的匹配项,比如29505就会被排除,而像Oct 2023、12-31-2022这种合法日期会被保留。
个人建议
如果你的日期格式特别复杂,正则已经很难再调整得完美,后处理过滤的方法会更省心——毕竟正则的复杂度越高,出现边缘情况的概率也越大,后处理的逻辑简单直接,出问题也更容易排查。
备注:内容来源于stack exchange,提问作者Daniel Moraes

