Python日期實體提取

日期實體提取

1 需求

根據用戶的每句語音進行解析,識別出用戶的酒店預訂需求,如房間型號、入住時間等;用戶的語音在發送給後臺進行請求時已經轉換成中文文本,然而由於語音轉換工具的識別問題,許多日期類的數據並不是嚴格的數字,會出現諸如“六月12”“2016年八月”“20160812”“後天下午”等形式。

例如“我要今天住到明天”(假設今天爲2017年10月1號),那麼通過日期解析後,應該輸出爲“2017-10-01”和“2017-10-02”。

2 代碼

我們主要通過正則表達式和Jieba分詞來完成該任務,主要引入以下庫:

import re
from datetime import datetime, timedelta
from dateutil.parser import parse
import jieba.posseg as psg

check_time_valid函數,用來對提取的拼接日期串進行進一步處理,以進行有效性判斷的。

def check_time_valid(word):
    # 對提取的拼接日期串進行進一步處理,以進行有效性判斷
    m = re.match("\d+$", word)
    if m:
        if len(word) <= 6:
            return None
    word1 = re.sub('[號|日]\d+$', '日', word)
    if word1 != word:
        return check_time_valid(word1)
    else:
        return word1

parse_datetime在解析具體幾個維度時,用了year2dig和cn2dig方法。主要是通過預定義一些模板,將具體的文本轉換成相應的數字,以供parse_datetime進行封裝。

UTIL_CN_NUM = {'零': 0, '一': 1, '二': 2, '兩': 2, '三': 3, '四': 4,
'五': 5, '六': 6, '七': 7, '八': 8, '九': 9,
'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
'5': 5, '6': 6, '7': 7, '8': 8, '9': 9
}
UTIL_CN_UNIT = {'十': 10, '百': 100, '千': 1000, '萬': 10000}


def cn2dig(src):
    if src == "":
        return None
    m = re.match("\d+", src)
    if m:
        return int(m.group(0))
    rsl = 0
    unit = 1
    for item in src[::-1]:
        if item in UTIL_CN_UNIT.keys():
            unit = UTIL_CN_UNIT[item]
        elif item in UTIL_CN_NUM.keys():
            num = UTIL_CN_NUM[item]
            rsl += num * unit
        else:
            return None
    if rsl < unit:
        rsl += unit
    return rsl
def year2dig(year):
    res = ''
    for item in year:
        if item in UTIL_CN_NUM.keys():
            res = res + str(UTIL_CN_NUM[item])
        else:
            res = res + item
    m = re.match("\d+", res)
    if m:
        if len(m.group(0)) == 2:
            return int(datetime.datetime.today().year/100)*100 + int(m.group(0))
        else:
            return int(m.group(0))
    else:
        return None

parse_datetime函數,用以將每個提取到的文本日期串進行時間轉換。其主要通過正則表達式將日期串進行切割,分爲“年”“月”“日”“時”“分”“秒”等具體維度,然後針對每個子維度單獨再進行識別。

def parse_datetime(msg):
    # 將每個提取到的文本日期串進行時間轉換。
    # print("parse_datetime開始處理:",msg)
    if msg is None or len(msg) == 0:
        return None
    try:
        msg = re.sub("年"," ",msg)# parse不認識"年"字
        dt = parse(msg, yearfirst=True, fuzzy=True)
        # print(dt)
        return dt.strftime('%Y-%m-%d %H:%M:%S')
    except Exception as e:
        m = re.match(r"([0-9零一二兩三四五六七八九十]+年)?([0-9一二兩三四五六七八九十]+月)?([0-9一二兩三四五六七八九十]+[號日])?([上中下午晚早]+)?([0-9零一二兩三四五六七八九十百]+[點:\.時])?([0-9零一二三四五六七八九十百]+分?)?([0-9零一二三四五六七八九十百]+秒)?",
                    msg)
        if m.group(0) is not None:
            res = {
                "year": m.group(1),
                "month": m.group(2),
                "day": m.group(3),
                "hour": m.group(5) if m.group(5) is not None else '00',
                "minute": m.group(6) if m.group(6) is not None else '00',
                "second": m.group(7) if m.group(7) is not None else '00',
            }
            params = {}

            for name in res:
                if res[name] is not None and len(res[name]) != 0:
                    tmp = None
                    if name == 'year':
                        tmp = year2dig(res[name][:-1])
                    else:
                        tmp = cn2dig(res[name][:-1])
                    if tmp is not None:
                        params[name] = int(tmp)
            target_date = datetime.today().replace(**params)
            is_pm = m.group(4)
            if is_pm is not None:
                if is_pm == u'下午' or is_pm == u'晚上' or is_pm =='中午':
                    hour = target_date.time().hour
                    if hour < 12:
                        target_date = target_date.replace(hour=hour + 12)
            return target_date.strftime('%Y-%m-%d %H:%M:%S')
        else:
            return None

首先通過Jieba分詞將帶有時間信息的詞進行切分,然後記錄連續時間信息的詞。這裏面就用到Jieba詞性標註的功能,提取其中“m”(數字)“t”(時間)詞性的詞。

time_extract實現了這樣的規則約束:對句子進行解析,提取其中所有能表示日期時間的詞,並進行上下文拼接,如詞性標註完後出現“今天/t 住/v 到/v 明天/t 下午/t 3/m點/m”,那麼需要將“今天”和“明天下午3點”提取出來。代碼裏面定義了幾個關鍵日期——“今天”“明天”和“後天”,當解析遇到這些詞時進行日期格式轉換,以方便後面的解析。

def time_extract(text):
    time_res = []
    word = ''
    keyDate = {'今天': 0, '至今': 0, '明天':1, '後天': 2}
    for k, v in psg.cut(text):
        if k in keyDate:
            if word != '':
                time_res.append(word)
            word = (datetime.today() + timedelta(days=keyDate.get(k, 0))).strftime('%Y年%m月%d日')
        elif word != '':
            if v in ['m', 't']:
                word = word + k
            else:
                time_res.append(word)
                word = ''
        elif v in ['m', 't']:
            word = k
    if word != '':
        time_res.append(word)
    # print('22222222',time_res)
    result = list(filter(lambda x: x is not None, [check_time_valid(w) for w in time_res]))
    final_res = [parse_datetime(w) for w in result]
    return [x for x in final_res if x is not None]

識別輸入的文本是否直接滿足 最後的要求,滿足就返回true

def yanZhengRightData(string):
    m = re.match("(\d{4}|\d{2})-(\d{2}|\d{1})-(\d{2}|\d{1})", string)
    if m:
        return True
    else:
        return time_extract(string)e:

main函數:

if __name__ == '__main__':
    print(time_extract("從2016年3月5日至今"))
    print(time_extract("在20160824-20180529的全部交易。"))
    print(time_extract("2017.6.12-7.10交!"))
    print(yanZhengRightData("2017-12-21"))

將上面的代碼按順序拷貝,並粘貼上一下main函數,運行後結果顯示爲:

['2016-03-05 00:00:00', '2019-12-04 00:00:00']
['2016-08-24 00:00:00', '2018-05-29 00:00:00']
['2017-06-12 00:00:00', '2019-12-07 00:00:00']
True

3 參考:

Python自然語言處理實戰:日期實體提取:https://zhuanlan.zhihu.com/p/39088702

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章