Введение
В статье рассматриваться нахождения физического лица в черном списке, когда его ФИО трансформируется в несколько вариантов для более точного поиска в списках.
Так как статья является продолжением статьи Парсинг (скрапинг) перечня террористов физических лиц с помощью Python, где мы в черный список сохраняли связку Имя+Фамилия, то текущая программа будет принимать на вход только 2 параметра:
- Имя
- Фамилия
На схеме ниже изображено, как программа будет преобразовывать клиента:
Программа делает перестановку слов Имя+Фамилия.
Для каждой перестановки делает второй вариант с замененными "Ё" и "Й" на "Е" и "И".
И для каждого варианта ФИО сделает еще по одному преобразованию с кириллицы на латиницу. Итого получится 8 ФИО вместо одного изначального.
Не существует одного единственного верного преобразования кириллицы в латиницу, поэтому данную схему можно расширять, но текущая программа носит ознакомительный характер, поэтому остановимся на одном варианте преобразования.
Дальше, согласно схеме, каждое из 8-ми ФИО будет проходить по 5 проверок:
- намеренная ошибка, когда латинскую букву вставляют в ФИО
например: Aлександр <- тут первая буква латинская
- нахождение одной подстроки в другой (x2)
- полное совпадение названий
- нахождение расстояния Левенштейна
Количество проверок может быть и больше, но мы остановимся на 5х
Всего получается, что мы вместо 1 варианта поиска создали 40 различных проверок. И это только для связки Имя+Фамилия. В более полном виде можно достичь и 200+, и 500+, и 1000+ проверок:
Плюс в реальных программах можно и нужно делать проверки на дату рождения, регион и прочие параметры.
Результатом программы будет имитация REST ответа:
На примере выше видно, что сработало несколько хитов:
- полное совпадение (DIST0)
- три частичных совпадения (DIST2)
- включение одной подстроки в другую (INC)
За пределами статьи:
- заранее были подготовлены файлы для работы с виртуальной средой. О том, как их подготавливать, описываю в отдельной статье: https://dzen.ru/a/aEKVHTM_GULyazKa
- стартовые исходники для статьи лежат в GitLab. В рамках статьи не объясняется, как устанавливать и настраивать git. Всё это описываю в отдельной статье: https://dzen.ru/a/aGgSbU4TnF-_7wL_
- должен быть установлен Excel от версии 2007 и старше
В конце статьи есть ссылка в GitLab на финальную версию программы.
Используемые технологии
ОС: Windows 10
Язык: Python 3.12
Основные библиотеки: re, pyxlsb, pydantic, numpy
Прочее: Microsoft Office 2013
План работы
- Клонируем репозиторий с GitLab
- Настраиваем виртуальную среду
- Изучаем архитектуру программы
- Создаём программу
- Запускаем программу
Клонируем репозиторий с GitLab
Создадим папку "searcher_fl", куда будем клонировать репозиторий
mkdir searcher_fl
Клонируем репозиторий
git clone https://gitlab.com/pytechnotes1/searcher_fl searcher_fl
Заходим в папку
cd searcher_fl/
Делаем чекаут версии коммита, который я сделал специально для статьи
git checkout 902c62da760e4e54c57d269781ed0e517f41dac9
Настраиваем виртуальную среду
Открываем в cmd наш проект searcher_fl и создать виртуальную среду
py -3.12 -m venv venv
Активируем среду
venv\Scripts\activate
Производим установку библиотек из файла, который я подготовил заранее
pip install -r requirements.txt
Выходим из виртуальной среды
deactivate
Изучаем архитектуру программы
Заранее уже готова определенная структура программы.
- app.py - скрипт, который будет запускать нашу программу.
- black_list.py - скрипт с нашей программой, которую мы будем реализовывать.
- datter.py - скрипт, в задачу которого входит извлечь черный список из заранее подготовленного эксель файла
- response_model.py - скрипт для формирования итогового JSON
- settings.py - скрипт с конфигами, которые править нет необходимости.
- data - папка, где лежит заранее подготовленный эксель файл с черным списком
Создаём программу
Начнем реализацию файла response_model.py
Для модели ответа будем использовать внешнюю библиотеку pydantic:
from pydantic import BaseModel
Создадим 3 класса:
- Response - класс с финальной структурой ответа
- BLModel - класс, который будет содержать основные поля, такие как результат проверки, кого проверяли и что нашли.
- PersonModel - класс с подробным описанием, по кому был поиск
По итогу у нас получится структура из вложенных друг в друга классов, унаследованных от базового класса BaseModel, это нам поможет одной командой превратить всю структуру ответа в JSON.
Когда мы заполним модель данными, у ответа получится примерно такой вид:
или такой:
Теперь реализуем файл datter.py
Напишем генератор, который будет итеративно, по одной строке подавать информацию из XLSB файла.
Укажем используемые библиотеки:
from typing import Generator
from pyxlsb import open_workbook
Начнем реализацию файла black_list.py
Реализуем конструктор __init__:
Сразу подключим необходимые импорты
import re
import numpy as np
import unicodedata
from settings import Settings
from datter import BlackListData as BLD
from response_model import BLModel, Response as RM, PersonModel as PM
Приступим к реализации метода search
Метод search проверяет клиентское ФИО на ошибку, если ошибки нет, то формирует list связок: Имя+Фамилия. После итерируется по черному списку в поисках совпадений и выводит ответ.
Реализуем метод _print_response
Реализуем метод _set_fio
Для проверки ФИО на ошибку метод _set_fio вызывает метод _check_fio_mistake, реализуем его тоже.
Если _check_fio_mistake находит ошибку. В нашем случае, ошибкой считается, когда в ФИО находим латинские символы. Такую вещь встречал на практике, когда недобросовестные сотрудники пытались обмануть систему.
Метод _check_fio_mistake вызывает другие методы: _remove_accents и _set_hits.
Метод _remove_accents убирает диакритические знаки из ФИО.
Метод _set_hits сохраняет найденный хит.
Реализуем метод _set_data_to_search и связанные с ним методы
Метод _set_data_to_search формирует список всех вариантов ФИО
Только в нашем случае речь идет о связке Имя+Фамилия.
Метод _set_data_to_search вызывает методы _set_ru_list_to_search и_set_lat_data_to_search, которые занимаются формированием всех вариаций ФИО.
Также вызывается метод _transliterate_ru_to_lat, который занимается транслитерацией с кириллицы на латиницу.
Перейдем к реализации метода _loop_lists
Метод итерирует список ФИО, подготовленный методом _set_data_to_search.
В рамках каждой вариации клиентского ФИО итерируемся по черному списку, для этого мы ранее создали класс BlackListData, который открывает эксель файл и построчно извлекает из него информацию.
Реализуем метод _search_terrorists, который сравнивает ФИО клиента и террориста из черного списка.
Метод _search_terrorists подготавливает ФИО клиента и террориста для сравнения. Для этого из ФИО удаляются все лишние символы, кроме букв, плюс в ФИО террориста убираются диакритические знаки.
После всех преобразований вызываются методы _include_eq и _levenshtein_distance, которые осуществляют поиск совпадений.
С помощью метода _include_eq мы ищем включения одной подстроки в другую.
В методе _levenshtein_distance представлен вариант реализации поиска расстояния Левенштейна. Одна из причин появления этой статьи - это показать работу данного метода на практике.
Расстояние Левенштейна - это сумма вставок, удалений, замен символов между двух строк. Несколько примеров:
Немного другой вариант реализации расстояния Левенштейна я показываю в статье: https://dzen.ru/a/aGYUPIdNLRvkpq-T
Под конец реализуем методы, которые будут фиксировать хиты:
При проверке расстояния Левенштейна, если расстояние = 0, то считаем, что террорист в списке 100% найден, и укажем хит: DIST0. Если расстояние менее 3х, то укажем хит: DIST2.
Почему менее 3х? Из моей практики это самое оптимальное число. Большее число приведет к большому количеству ложных срабатываний, меньшее не будет сильно отличаться от хита DIST0.
На этом реализацию программы заканчиваем.
Запускаем программу
Запускаем скрипт через "py", таким образом запустится интерпретатор из виртуальной среды.
py app.py
Получаем JSON ответ:
{"black_list":{"result":2,"person":{"name":"МАГüМЕД","surname":"АБДУКАРИМОВ"},"hits":{"ERR_LAT":["u"]}}}
Исходники проекта
Подписывайтесь на Дзен, а также приглашаю в мой телеграмм канал, там публикую другой, но не менее интересный контент.