如何解决dateutil.parser模糊匹配日期时的误识别问题?
问题描述
我想要从字符串中提取日期,为此编写了如下_is_date函数,使用dateutil.parser的parse方法并开启fuzzy参数:
def _is_date(string, fuzzy=False): try: return parse(string, fuzzy=fuzzy) except ValueError: return False
该函数在部分场景下能正常工作,例如:
>>> _is_date('delivered 22-jun-2022', fuzzy=True) 2022-06-22 00:00:00 >>> _is_date('04 sep, lets meet', fuzzy=True) 2022-09-04 00:00:00
但在处理不含日期的字符串时会返回错误结果,例如:
>>> _is_date('Ive 4 kids', fuzzy=True) 2022-09-04 00:00:00 >>> _is_date('samsung galaxy m32 (black,', fuzzy=True) 2022-09-23 00:00:32 >>> _is_date('4gb ram..', fuzzy=True) 2022-09-04 00:00:00
请问如何修复该问题?或者是否有其他方法可以解决这个日期提取需求?
问题原因
dateutil.parser.parse的fuzzy=True模式会忽略非日期相关字符,但当字符串中存在孤立数字时,它会自动结合当前系统日期补全年、月、日的缺失部分,导致误判。比如字符串里的"4"会被当成日期日份,结合当前月份和年份生成完整日期。
修复方案
方案1:先通过正则筛选潜在日期子串,再解析
先定义常见的日期格式正则表达式,从原字符串中匹配出可能的日期片段,再对这些片段进行解析,避免孤立数字被误判。
示例代码:
from dateutil.parser import parse import re # 匹配常见日期格式:dd-mon-yyyy、dd mmm yyyy、yyyy-mm-dd、dd/mm/yyyy等 DATE_PATTERNS = [ r'\b\d{1,2}-[a-zA-Z]{3,}-\d{4}\b', # 22-jun-2022 r'\b\d{1,2} [a-zA-Z]{3,} \d{4}\b', # 22 Jun 2022 r'\b\d{4}-\d{1,2}-\d{1,2}\b', # 2022-06-22 r'\b\d{1,2}/\d{1,2}/\d{4}\b', # 22/06/2022 r'\b\d{1,2} [a-zA-Z]{3,}\b', # 04 sep r'\b[a-zA-Z]{3,} \d{1,2}\b' # Jun 22 ] def extract_date(string): # 合并所有正则模式 combined_pattern = '|'.join(DATE_PATTERNS) # 查找所有匹配的子串 matches = re.findall(combined_pattern, string, flags=re.IGNORECASE) for match in matches: try: # 对匹配到的子串进行解析 return parse(match) except ValueError: continue # 没有匹配到有效日期返回False return False
测试效果:
>>> extract_date('delivered 22-jun-2022') 2022-06-22 00:00:00 >>> extract_date('04 sep, lets meet') 2022-09-04 00:00:00 >>> extract_date('Ive 4 kids') False >>> extract_date('samsung galaxy m32 (black,') False >>> extract_date('4gb ram..') False
方案2:检查解析结果的来源是否为原字符串中的有效日期成分
通过parse的fuzzy_with_tokens参数,获取解析时用到的日期相关token,判断这些token是否构成一个合理的日期片段(比如至少包含日+月份/年份,或者完整的年月日)。
示例代码:
from dateutil.parser import parse def _is_date(string): try: # fuzzy_with_tokens返回(解析后的日期, 忽略的token列表) parsed_date, ignored_tokens = parse(string, fuzzy_with_tokens=True) # 获取原字符串中被用作日期的部分:原字符串去掉忽略的token date_part = string for token in ignored_tokens: date_part = date_part.replace(token, '', 1).strip() # 检查日期部分是否包含至少两个日期相关元素 if len(date_part) < 4: return False has_digit = any(c.isdigit() for c in date_part) has_alpha = any(c.isalpha() for c in date_part) has_separator = any(c in '-/.' for c in date_part) if (has_digit and has_alpha) or (has_digit and has_separator): return parsed_date else: return False except ValueError: return False
测试效果:
>>> _is_date('delivered 22-jun-2022') 2022-06-22 00:00:00 >>> _is_date('04 sep, lets meet') 2022-09-04 00:00:00 >>> _is_date('Ive 4 kids') False >>> _is_date('samsung galaxy m32 (black,') False >>> _is_date('4gb ram..') False
方案3:使用更严格的日期解析工具
比如使用pandas.to_datetime,通过指定errors='coerce'和infer_datetime_format=True来减少误判:
import pandas as pd def extract_date_pandas(string): # 尝试解析,无法解析返回NaT parsed_date = pd.to_datetime(string, errors='coerce', infer_datetime_format=True) # 检查是否为有效日期,且原字符串包含月份相关字符或日期分隔符 if pd.notna(parsed_date): has_month_char = any(c.lower() in 'janfebmaraprmayjunjulaugsepoctnovdec' for c in string) has_separator = any(c in '-/.' for c in string) if has_month_char or has_separator: return parsed_date.to_pydatetime() return False
总结
推荐优先使用方案1,因为正则可以精准匹配你需要的日期格式,避免无意义的误判;如果需要兼容更多未知格式,可以结合方案2的token检查逻辑,过滤掉由孤立数字生成的无效日期。
内容的提问来源于stack exchange,提问作者Praful Bagai
相关产品推荐
相关产品推荐

