Найти всё. Text Mining

от автора

Привет, Хабр! Хочу поделиться опытом анализа текста. Возьму рабочий пример документов в отношении граждан, проходящих процедуру банкротства. Задача заключается в автоматизированном сборе информации из текста 300 тыс. документов такой как: номер счета, с которого можно снять средства, разрешенная сумма, период действия. Пример интересующей меня части документа:

Немного скучной теории: формулировка заключений финансовых управляющих не регламентируется специальными правилами и потому может выглядеть по-разному и может находится в разных частях документа. Но в подобных заключениях (если они имеются) всегда будут упоминаться номер специального счета (для физ. лица он начинается с 4), сумма и фразы, подразумевающие разрешение на снятие денежных средств. С помощью данных эвристик можно локализовать нужный нам кусок текста!

Воспользуюсь python и библиотеками regex и Natasha. С помощью регулярных выражений локализирую нужные предложения, а разбивать текст на предложения и вытаскивать необходимые поля буду с Natasha. Многие знакомы с отличными способностями библиотеки Natasha к распознаванию именованных сущностей (имена, города, названия компаний и т.д.). Но, помимо этого, она умеет находить даты, суммы денег и даже адреса!

Импортирую нужные библиотеки:

import os import pandas as pd from natasha import (Doc, Segmenter, NewsEmbedding, NewsMorphTagger,                      NewsSyntaxParser, MorphVocab, NewsNERTagger,                      DatesExtractor, MoneyExtractor) import re from collections import Counter import openpyxl import datetime pd.options.display.max_columns = 100 pd.options.display.max_rows = 100 import warnings  warnings.filterwarnings('ignore')

Напишу функцию get_info, которая на вход будет принимать текст и предложения, а на выходе возвращает номер счета, сумму и период. Сначала с помощью регулярных выражений нахожу центральное предложение и беру соседние 2 предложения.

def get_info(text, sents):   try:     match = re.finditer('4\d{19}', text)      marks = [m.start() for m in match]     bill = None     money = None     date1 = None     date2 = None      for ind, i in enumerate(sents):       for mark in marks:    if i.start <= mark and i.stop > mark:        mini_sents = [sents[ind-1], i ,sents[ind+1]] # берем 3 предложения       bill_text = ' '.join([x.text for x in mini_sents]) 

Далее проверяю, если там идет речь о разрешении на снятия или о денежных лимитах, то забираем нужные поля.

patterns = ['не более', 'в пределах', 'разблокир\w+', 'не может превышать', 'превыша\w+', 'самостоятельно', 'имеет право', 'распоряж\w+', '[Дд]еньги снимаются']                     matches = []       [matches.extend(re.findall(pattern, bill_text)) for pattern in patterns]      if matches:        matches = money_extractor(bill_text) # распознавание денег         facts = [i.fact.as_json for i in matches]        facts = [f.get('amount') for f in facts]        money = facts        bill = re.search('4\d{19}',bill_text) # забираем счет    try:     start = [m.start() for m in  re.finditer('\s+с\s+\d{2}', bill_text)][0]    # находим упоминание периода          dates = dates_extractor(bill_text[start:]) # распознавание дат           dates = [datetime.date(d.fact.as_json.get('year'),                                  d.fact.as_json.get('month'),                                  d.fact.as_json.get('day'))                   for d in dates]          date1 = dates[0]          date2 = dates[1]                        except:          pass                                     if money:          money = [0]                     return bill, money, date1, date2     except:        return None, None, None, None

Создаю датафрейм:

data = pd.DataFrame({'ЗНО':[], 'Номер счета' : [], 'Дата 1' : [],                        'Дата 2' : [], 'Сумма' : []})

В цикле открываю и считаю информацию из файлов, убираю символы переноса и табуляции. Затем передаю текст в Natasha для токенизации и распознавания сущностей. Вызываю написанную мной функцию get_info и дописываю в датафрейм найденную информацию.

segmenter = Segmenter() emb = NewsEmbedding() morph_tagger = NewsMorphTagger(emb) syntax_parser = NewsSyntaxParser(emb) morph_vocab = MorphVocab() ner_tagger = NewsNERTagger(emb)  path = 'docs/' filenames = os.listdir(path)  for filename in filenames:   with open(path + filename, 'r') as file:     text += file.read()   text = text.replace('\n', ' ')   text = text.replace('\t', ' ')   doc = Doc(text)   dates_extractor = DatesExtractor(morph_vocab)   money_extractor = MoneyExtractor(morph_vocab)   doc.segment(segmenter)   doc.tag_morph(morph_tagger)   doc.parse_syntax(syntax_parser)   doc.tag_ner(ner_tagger)     num, money, date1, date2 = get_info(doc.text, doc.sents)   data = pd.concat([pd.DataFrame({     'ЗНО':[filename.split('.')[0].split('_')[0]],  # ЗНО в нашем случае являлось название документа                'Номер счета' : [num], 'Дата 1' : [date1],                 'Дата 2' : [date2], 'Сумма' : [money]}), data])

На выходе получаю желаемые данные в виде таблицы:

Таким образом, с помощью специальных эвристик и уже существующих инструментов мне удалось автоматизировано и качественно получить желаемую информацию из нескольких сотен тысяч документов и, при этом, не изобретать колесо. На этом всё, надеюсь была полезна, всем успехов!


ссылка на оригинал статьи https://habr.com/ru/post/673726/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *