Пишем первого робота для банка

О чем это

В этой статье мы разберемся, что такое «робот», поймем, как они помогают операционистам, напишем и запустим простого робота на Python.

Исходный код робота и данные для работы можно скачать здесь.

Макросы на стероидах

Робот имитирует действия человека, сидящего за компьютером.

Он может открывать программы, нажимать в них на кнопки, читать и вводить данные.

То, что сейчас называется роботом, впервые стало популярно в 80-х годах прошлого века под названием «макрос»: при нажатии определенной комбинации клавиш макрос вместо оператора вводил и считывал необходимые данные из текстового терминала, что помогало форматировать текст и выполнять другие рутинные операции.

С появлением графических интерфейсов макросы на время остановились в развитии. Прорыв случился только с появлением пакета Microsoft Office '97 в 1997 году с появлением возможности записывать действия пользователя и превращать их в исполняемый код на Visual Basic for Applications.

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

В 2010 начинается бурный переход от самописных решений к коробочным и облачным решениям, которые не всегда легко изменить под потребности клиента. Например, довольно трудно как технически, так и организационно из облачного решения обратиться к учетной системе компании и вытянуть из нее какие-либо справочные данные.

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

Сценарии могут быть и более сложными, причем нередко требуется применение интеллекта операциониста.

Задача нашего первого робота

Наш первый робот будет имитировать работу операциониста CRM в сценарии «обогати клиентские данные в локальной CRM полем ИНН из облачного сервиса».

Процесс AS IS (как работает операционист CRM до появления робота)

  • Операционист открывает локальную CRM-систему. Для нашего учебного примера для простоты мы не будем устанавливать промышленную CRM, а воспользуемся приложением Microsoft Office Excel, в котором будет открыт файл с полями «Фамилия», «Имя», «Отчество», «Дата рождения» и «Номер паспорта».

8de8bb8ed49861c21ceae76a6c2cc65c.PNG257753ecde46279953aaa6dce761b822.PNG

  • Операционист читает правила сервиса и соглашается с ними нажимая соответствующую галочку.

  • Сайт отображает форму поиска ИНН.

e56546a1adfd4c602c0b1b8826beaae6.PNG

  • Операционист переключается в окно Excel, находит ячейку с фамилией и нажимает клавиши Ctrl + C, копируя данные в буфер обмена.

  • Операционист переключается в броузер и вставляет в него поле данные из буфера обмена.

  • Пункты 4–5 операционист повторяет для полей «Имя», «Отчество», «Дата рождения» и «Номер паспорта».

  • Операционист нажимает на сайте кнопку «Отправить запрос».

50d1bb9b69ea6651e4b2bd415a9cfcd2.PNG

  • Сайт какое-то время думает и затем отображает ИНН.

  • Операционист выделяет ИНН, копирует его в буфер обмена и переносит его обратно в Excel.

  • Операционист повторяет шаги выше для остальных записей о пользователе.

Процесс TO BE (как будет работать робот)

  1. Робот находит окно Excel и перемещает курсор в левый верхний угол (в начало клиентских данных). Конечно, робот мог бы прочесть Excel-файл напрямую, но для учебного примера мы предположим, что этой возможности нет, что робот имеет доступ только к пользовательскому интерфейсу CRM и вынужден имитировать действия оператора.

  2. Робот запускает браузер и открывает в нем ссылку https://service.nalog.ru/inn.do. Если появляется окно с приглашением принять условия обслуживания, то робот имитирует нажатие на эту галочку.

  3. Робот переключается в окно Excel и последовательно копирует все клиентские данные из ячеек Excel в буфер обмена, а из буфера обмена — в массив в памяти робота.

  4. Робот переключается в браузер и последовательно получает ИНН для каждой клиентской записи, для этого:

    1. Вставляет данные из памяти в форму на сайте https://service.nalog.ru/inn.do.

    2. Нажимает кнопку «Отправить запрос».

    3. Ждет пока сайт отобразит ИНН во всплывающей подсказке.

    4. Копирует ИНН из всплывающей подсказки в память программы рядом с данными о клиенте.

  5. Робот переключается в окно Excel, находит столбец «ИНН» и вставляет ИНН из памяти программы в соответствующие ячейки.

Выбираем язык программирования

Робота можно написать практически на любом современном языке программирования. Помимо этого в свободной продаже можно найти лицензии на целые пакеты роботизации и существуют сценарии, при которых их использование экономически оправдано.

Мы будем использовать язык Python, поскольку мы в конечном счете заменяем операциониста и со временем для этого могут потребоваться инструменты для программирования искусственного интеллекта, которые в Python присутствуют в полном объеме.

Помимо этого язык Python является простым в освоении и на нем проходит обучение программированию в Minecraft. Я знаю это потому, что мой семилетний сын программирует на Python в Minecraft и, кстати, одно из его любимых развлечений — роботизировать в Minecraft рутинные задачи, например, рыть тоннели к алмазам и строить место для ночлега.

Язык Python используется многими компаниями и банками, в том числе для задач роботизации он используется и в МКБ.

Подготовка

Для работы потребуется установить:

  • Excel для имитации CRM

  • Google Chrome для доступа к сайту https://service.nalog.ru/inn.do

  • Python 3, его можно скачать с сайта https://www.python.org/downloads/

  • PyWinAuto для имитации работы операциониста в Excel и любом другом не-броузерном приложении

pip3 install pywinauto

  • selenium web driver для имитации работы операциониста в Google Chrome. Для установки этого пакета необходимо выполнить два шага:

    • Скачать исполняемое приложение ChromeWebDriver отсюда https://chromedriver.chromium.org/downloads, при этом обратите внимание на версию — она должна совпадать с версией Google Chrome

    • Установить пакет для Python3

pip3 install selenium

Переходим к программированию

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

В обычной жизни это делает операционист.

При программировании робота эти действия имитируют библиотеки pywinauto для классических приложений и selenium — для браузерных.

При работе этих приложений со стороны будет создаваться ощущение, что за компьютером кто-то сидит: конечно, мышь по столу ездить не будет и кнопки на клавиатуре не будут продавливаться, но на мониторе будет кипеть бурная деятельность: данные будут вводиться, экраны переключаться, текст будет копироваться и вставляться — почти как при работе настоящего операциониста.

Из плохих новостей — компьютером в этот момент нельзя будет пользоваться: за ним «сидит» робот.

Код CrmAppAgent

Вся логика работы с CRM упакована в класс CrmAppAgent.

В нем реализованы как методы перемещения курсора, копирования и вставки данных, так и более высокоуровневые методы для получения списка записей и вставки ИНН напротив записей о клиенте.

from pywinauto import Application
from pywinauto import clipboard
import time

class CrmAppAgent:
    def __init__(self):
        window_title_regular_expression = ".*data.*"
        excel_app = Application(backend="uia").connect(title_re=window_title_regular_expression)
        excel_window = excel_app.window(title_re=window_title_regular_expression)
        self.excel_app = excel_app
        self.excel_window = excel_window

    def deselect(self):
        excel_window = self.excel_window
        excel_window.type_keys('{ESC}')

    def move_cursor_to_top_left_corner(self):
        excel_window = self.excel_window
        excel_window.type_keys('^{HOME}')

    def move_cursor_down(self):
        excel_window = self.excel_window
        excel_window.type_keys('{DOWN}')

    def move_cursor_to_first_person_cell(self):
        excel_window = self.excel_window
        self.move_cursor_to_top_left_corner()
        self.move_cursor_down()

    def read_cell_contents(self):
        excel_window = self.excel_window
        excel_window.type_keys('^c')
        time.sleep(0.1)
        cell_data = clipboard.GetData()
        result = cell_data.rstrip()
        return result

    def move_cursor_right(self):
        excel_window = self.excel_window
        excel_window.type_keys('{RIGHT}')

    def move_cursor_to_first_left_cell(self):
        excel_window = self.excel_window
        excel_window.type_keys('{HOME}')

    def read_person(self):
        excel_window = self.excel_window
        last_name = self.read_cell_contents()

        if not last_name:
            return None

        self.move_cursor_right()
        first_name = self.read_cell_contents()

        self.move_cursor_right()
        middle_name = self.read_cell_contents()

        self.move_cursor_right()
        birthday = self.read_cell_contents()

        self.move_cursor_right()
        passport = self.read_cell_contents()

        self.move_cursor_down()
        self.move_cursor_to_first_left_cell()

        return {
            "last_name": last_name,
            "first_name": first_name,
            "middle_name": middle_name,
            "birthday": birthday,
            "passport": passport
        }

    def read_persons(self):
        excel_window = self.excel_window
        result = []
        self.deselect()
        self.move_cursor_to_first_person_cell()
        while True:
            person = self.read_person()
            if person:
                result.append(person)
            else:
                break

        return result
    
    def move_cursor_to_first_inn(self):
        excel_window = self.excel_window
        self.move_cursor_to_top_left_corner()
        self.move_cursor_down()

        for i in range(5):
            self.move_cursor_right()
        
    def fill_inns(self, inns):
        excel_window = self.excel_window
        self.move_cursor_to_first_inn()
        for inn in inns:
            excel_window.type_keys(inn)
            self.move_cursor_down()

Код InnAppAgent

Логика работы с приложением https://service.nalog.ru/inn.do упакована в класс InnAppAgent.

Работа с этим приложением ведется через selenium web driver, что позволяет находить элементы на HMTL-странице веб-приложения по идентификаторам и код получается чище и короче, нежели код роботизации классического приложения.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
import os

class InnAppAgent:
    def __init__(self):        
        browser = self.get_browser()
        self.browser = browser
        
        browser.implicitly_wait(10)

        SERVICE_URL = 'https://service.nalog.ru/inn.do'
        browser.get(SERVICE_URL)

        accept_terms_and_conditions_page_shown = browser.current_url != SERVICE_URL

        if accept_terms_and_conditions_page_shown:
            self.accept_terms_and_conditions()
            
    def get_browser(self):
        options = webdriver.ChromeOptions()
        options.add_argument("--start-maximized")

        current_folder = os.getcwd()
        web_driver_executable_name = "chromedriver.exe"
        web_driver_executable_path = "{}\\{}".format(current_folder, web_driver_executable_name)
        result = webdriver.Chrome(executable_path=web_driver_executable_path, chrome_options=options)
        return result

    def accept_terms_and_conditions(self):
        browser = self.browser
        browser.find_element(By.XPATH, '//a[@class="checkbox checkbox-off"]').click()
        browser.find_element(By.XPATH, '//button[@id="btnContinue"]').click()

    def fill_person_data(self, person):
        browser = self.browser
        input_data = {
            "fam": person["last_name"],
            "nam": person["first_name"],
            "otch": person["middle_name"],
            "bdate": person["birthday"],
            "docno": person["passport"]
        }

        for element_id, input_value in input_data.items():
            element = browser.find_element(By.ID, element_id)
            element.clear()
            for symbol in input_value:
                element.send_keys(symbol)
                time.sleep(0.1)

    def submit_data(self):
        browser = self.browser
        browser.find_element(By.ID, 'btn_send').click()

    def read_inn(self):
        browser = self.browser
        previous_inn_element = browser.find_element(By.ID, "resultInn")
        previous_inn = previous_inn_element.text
        WebDriverWait(driver=browser, timeout=10, poll_frequency=1).until(lambda drv: drv.find_element(By.ID, "resultInn").text != previous_inn)

        inn_element = browser.find_element(By.ID, "resultInn")
        result = inn_element.text

        return result

    def submit_data_and_read_inn(self):
        browser = self.browser
        self.submit_data()

        return self.read_inn()

    def find_inn(self, person):
        browser = self.browser
        self.fill_person_data(person=person)

        result = self.submit_data_and_read_inn()

        return result

    def find_inns(self, persons):    
        browser = self.browser

        return [self.find_inn(person=person) for person in persons]

Код сценария робота

Бизнес-логика по взаимодействию роботов вынесена в класс EnrichPersonsWithInnsScenario.

Сценарий очень короткий, поскольку все нюансы взаимодействия с приложением CRM и приложением https://service.nalog.ru/inn.do вынесена в классы-агенты.

class EnrichPersonsWithInnsScenario:
    def __init__(self, crm_app_agent, inn_app_agent):
        self.crm_app_agent = crm_app_agent
        self.inn_app_agent = inn_app_agent
        
    def run(self):
        crm_app_agent = self.crm_app_agent

        persons = crm_app_agent.read_persons()

        inn_app_agent = self.inn_app_agent
        inns = inn_app_agent.find_inns(persons=persons)

        crm_app_agent.fill_inns(inns=inns)

crm_app_agent = CrmAppAgent()
inn_app_agent = InnAppAgent()
enrich_persons_with_inns_scenario = EnrichPersonsWithInnsScenario(crm_app_agent=crm_app_agent, inn_app_agent=inn_app_agent)
enrich_persons_with_inns_scenario.run()

Финальный листинг

Исходный код робота и данные для работы можно скачать здесь: https://github.com/vasiliy-mikhailov/robot_tutorial

Запуск программы

  • Откройте файл data.xlsx при помощи Excel. Робот будет искать окно с названием «data», поэтому переименовывать файл нельзя.

  • Опционально: поменяйте в Excel тестовые данные клиента на свои. Если этого не сделать, то сайт https://service.nalog.ru/inn.do не сможет найти ИНН и сценарий не дойдет до конца. Реальные данные не включены в учебный пример по соображениям соблюдения закона о персональных данных.

  • Сохраните скрипт robot.py в любую папку.

  • Положите в эту же папку файл chromedriver.exe.

  • Перейдите в эту папку и выполните в ней команду.

python3.exe robot.py

  • Вы увидите как запустится браузер и робот примет в нем условия обслуживания.

  • Затем откроется окно Excel и робот начнет перемещаться по ячейкам и копировать их содержимое.

  • После того как робот скопирует все ячейки, переключитесь в окно браузера и посмотрите как робот вводит эти данные в форму и получает ИНН.

  • Если робот сможет вычислить все ИНН, то он перейдет обратно в Excel и заполнит ячейку с ИНН.

  • Обратите внимание на то, что робот может не выполнить работу до конца. Наиболее частые ошибки: невозможность скопировать данные в буфер обмена с ошибкой «Доступ запрещен» и зависание сервиса https://service.nalog.ru/inn.do на стадии поиска ИНН.

Поздравляю, что дальше?

Вы только что написали своего первого робота на языке программирования Python.

Обязательно покажите его домочадцам и посмотрите на их удивленные глаза, когда компьютер сам будет открывать окна и нажимать на клавиши — это бесценно.

Для принятия решения о том, стоит ли заниматься этим дальше, нужно найти задачи для роботизации в компании и убедить руководство в том, что это выгодно.

Поиск задач под роботизацию

Если вы выходите домой в 21:00, а операционисты все еще сидят, подойдите к ним и посмотрите, что они делают. Вполне может быть, что они выполняют рутинные операции по вводу данных, тогда весьма вероятно, что это ваши будущие клиенты. Присмотритесь и к другим шаблонным процессам в компании. Подумайте, как можно улучшить работу.

Экономика вопроса

Роботизация имеет смысл, если стоимость работы команды разработчиков окупается экономией от сокращения ручного труда операционистов и их высвобождения для решения других задач.

Простые роботы могут быть написаны Junior-разработчиком, однако, когда количество роботов растет, возникает потребность в размещении их на серверах, организации автоматического процесса поставки и мониторинга исполнения. Для организации этого процесса и формирования правильной архитектуры обычно требуется Senior-разработчик.

Роботы капризны, и за ними нужно приглядывать. Сократить затраты на сопровождение роботов помогут системы автоматизированного развертывания и мониторинга, которые может сделать Senior-разработчик.

Развиваем технические навыки

Для более глубокого изучения рекомендую прочесть и перенабрать своими руками примеры кода из книга «Автоматизация рутинных задач с помощью Python».

Это поможет набрать необходимую скорость перед тем, как появится первый реальный заказ на роботизацию.

Удачи в изучении Python!

© Habrahabr.ru