Как анализировать несколько дат из блока текста в Python (или на другом языке)
У меня есть строка, в которой есть несколько значений даты, и я хочу их разобрать. Строка - естественный язык, поэтому лучшее, что я нашел до сих пор, - 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 - Я предполагаю, что я мог бы рекурсивно разделить входной файл посередине и попробовать, попробуйте еще раз, пока это не сработает, но это адский хак.
Ответы
Ответ 1
Глядя на это, наименее опасным способом было бы изменить dateutil parser, чтобы иметь вариант с нечетким числом.
parser._parse
берет вашу строку, ее токенизирует с помощью _timelex
, а затем сравнивает токены с данными, определенными в parserinfo
.
Здесь, если токен не соответствует чему-либо в parserinfo
, синтаксический анализ завершится с ошибкой, если fuzzy
не является True.
Я предлагаю вам разрешить не-совпадения, пока у вас нет обработанных токенов времени, а затем, когда вы нажмете на несоответствие, обработайте проанализированные данные в этой точке и снова начните искать токены времени.
Не следует предпринимать слишком много усилий.
Обновление
Пока вы ожидаете, что ваш патч будет запущен...
Это немного взломанный, использует в публике не публичные функции, но не требует изменения библиотеки и не является пробной и ошибочной. У вас могут быть ложные срабатывания, если у вас есть одиночные жетоны, которые можно превратить в поплавки. Возможно, вам придется отфильтровать результаты еще.
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)
Урожайность:
Found: 2011 04 23
Parsed: 2011-04-23 00:00:00
Found: 29 July 1928
Parsed: 1928-07-29 00:00:00
Обновление для Дитера
Dateutil 2.1, по-видимому, написан для совместимости с python3 и использует библиотеку "совместимости" под названием six
. Что-то не так с этим, и оно не рассматривает объекты str
как текст.
Это решение работает с dateutil 2.1, если вы передаете строки как unicode или как файлы типа:
from cStringIO import StringIO
for item in timesplit(StringIO(a)):
print "Found:", item
print "Parsed:", p.parse(StringIO(item))
Если вы хотите установить параметр на parserinfo, создайте экземпляр parserinfo и передайте его в объект парсера. Например:
from dateutil.parser import _timelex, parser, parserinfo
info = parserinfo(dayfirst=True)
p = parser(info)
Ответ 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
возвращает пустой список, который, IMHO, является тем, что можно было бы ожидать.
>>> from dateutil import parser
>>> parser.parse("")
datetime.datetime(2011, 8, 12, 0, 0)
>>> parse_multiple("")
[]
P.S. Просто заметили MattH обновленный ответ, который делает что-то очень похожее.
Ответ 3
Я думаю, что если вы поместите "слова" в массив, он должен сделать трюк. С этим вы можете проверить, является ли это датой или нет, и вставить переменную.
Как только у вас будет дата, вы должны использовать библиотеку библиотека datetime.
Ответ 4
Почему бы не написать шаблон регулярного выражения, охватывающий все возможные формы, в которых может отображаться дата, а затем запустить регулярное выражение для изучения текста? Я предполагаю, что десятки десятков способов выразить дату в строке.
Единственная проблема - собрать максимум выражений даты