pyOpenRPA туториал. Управление оконными GUI приложениями

  • Tutorial

Специально для Хабр я начинаю серию статей-туториалов по использованию RPA платформы OpenRPA. Буду рад получить от вас комментарии и замечания, если возникнут какие-либо вопросы. Надеюсь, что эта история не оставит вас равнодушными.


pyOpenRPA Туториал. Управление оконными GUI приложениями


Ранее я писал о том, что OpenRPA — это первая open source RPA платформа, которая позволяет полностью избавить себя от платных RPA аналогов. И, как выяснилось в процессе, эта тема позволяет не просто снять компании с "лицензионной иглы", а еще и увеличить получаемые бизнес-эффекты от разработанных роботов. Ведь архитектура новых RPA оказалась гораздо "легче" и, как следствие, быстрее.


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


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


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


Ремарка. OpenRPA теперь становится pyOpenRPA


С момента опубликования предыдущей статьи произошли небольшие изменения в названии RPA платформы, а именно: OpenRPA переименовывается в pyOpenRPA.


С чем это связано?


Дело в том, что само по себе название OpenRPA является "говорящим", и "лежит на поверхности". По этой причине его выбрал я, а через некоторое время и другие. Так как концепция pyOpenRPA заключается в абсолютно безвоздмездном использовании и открытости для всех, в этой RPA платформе нет каких-либо бюджетов. В связи с этим нет возможности и отстаивать монопольное право на использование названия (да это и не нужно). В связи с этим, было принято решение немного скорректировать название, чтобы избавить пользователей от возможной путаницы.


По поводу OpenRPA от другой команды: очень надеюсь, что им удастся реализовать свою идею, превзойти по всем параметрам платные RPA платформы, и сохранить свою открытось. В мире open source мы не конкуренты, а коллеги, которые трудятся в одном и том же направлении — в направлении создания полезного открытого продукта. Если говорить про их RPA платформу, то там поставлена цель создания аналога коммерческой RPA платформы с визуальным программированием в основе. Идея очень интересная и привлекательная, но крайне трудозатратная по исполнению (ведь не просто так лучшие RPA платформы постоянно дорабатываются огромными командами разработчиков, что ведет к комерциализации проекта). Желаю им удачи в достижении поставленных целей.


Навигация по туториалам pyOpenRPA


Туториал сверстан в виде серии статей, в которых будут освещаться ключевые технологии, необходимые для RPA.


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


Перечень статей-туториалов (опубликованные и планируемые):



А теперь перейдем к самому туториалу.


Немного теории и терминов


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


Начнем с простого. Рассмотрим на примере классического блокнота, что видим мы, и что видит робот.


Что видим мы?


Notepad_human


Что видит робот?


Notepad_pyOpenRPA


Интерпретация


Благодаря архитектуре современных операционных систем у сторонних программ имеется программная возможность по обращению к UI элементам — они же UIO сторонних GUI приложений. Эта возможность изначально разрабатывалась для того, чтобы позволить программистам проводить регрессионное тестирование свеого софта, но позже выяснилось, что эту возможность можно использовать и в бизнес-процессах компании.


Как мы видим на изображении выше, для робота, блокнот — это набор различных UIO. Причем не просто UIO, а UIO c набором различных атрибутов, и набором различных действий. И что самое главное — наш робот имеет полный доступ ко всем этим атрибутам и действиям.


Примеры атрибутов

  • hidden — элемент спрятан в GUI интерфейсе от глаз пользователя
  • disabled — элемент недоступен для выполнения действий (нажатие, наведение мышкой и и т.д.)


Примеры действий
  • left click — клик левой кнопкой мыши
  • right click — клик правой кнопкой мыши
  • type text — ввод текста в активную область
  • scroll up — пролистывание активной области вверх
  • scroll down — пролистывание активной области вниз
  • scroll left — пролистывание активной области влево
  • scroll right — пролистывание активной области вправо

Тут вы можете мне возразить, что это все ерунда, потому что в любой операционной системе реализованы алгоритмы, обеспечивающие разграничение информационных потоков, и одно приложение не может "залезть" в другое приложение. А я вам на это скажу, что это, действительно так, но только для программного (технического) уровня. Вся эта история с безопасностью не работает, когда речь заходит о доступе к GUI интерфейсам других приложений.


Что такое UIO?


UIO — это User Interface Object (терминология pyOpenRPA). В целях обеспечения максимальной совместимости, этот экземпляр наследуется от обьектной модели, разработанной в библиотеке pywinauto (нажми, чтобы получить список доступных функций класса).


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


Правила формирования UIO селектора (UIOSelector)


UIO селектор — это список характеристических словарей (спецификаций UIO). Данные спецификации UIO содержат условия, с помощью которых библиотека pyOpenRPA определит UIO, удовлетворяющий условиям, заданным в спецификации UIO. Индекс спецификации UIO в списке UIO селектора харакетризует уровень вложенности целевого UIO.


Говоря другим языком, UIO селектор — это перечень условий, под которые может попасть 0, 1 или n UIO.


Ниже приведен перечень атрибутов — условий, которые можно использовать в спецификациях UIO:


[
    {
        "depth_start" :: [int, start from 1] :: глубина, с которой начинается поиск (по умолчанию 1),
        "depth_end" :: [int, start from 1] :: глубина, до которой ведется поиск (по умолчанию 1),
        "ctrl_index" || "index" :: [int, starts from 0] :: индекс UIO в списке у родительского UIO,
        "title" :: [str] :: идентичное наименование атрибута *title* искомого объекта UIO,
        "title_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *title* должен удовлетворять условию данного регулярного выражения,
        "rich_text" :: [str] :: идентичное наименование атрибута *rich_text* искомого объекта UIO,
        "rich_text_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *rich_text* должен удовлетворять условию данного регулярного выражения,
        "class_name" :: [str] :: идентичное наименование атрибута *class_name* искомого объекта UIO,
        "class_name_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *class_name* должен удовлетворять условию данного регулярного выражения,
        "friendly_class_name" :: [str] :: идентичное наименование атрибута *friendly_class_name* искомого объекта UIO,
        "friendly_class_name_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *friendly_class_name* должен удовлетворять условию данного регулярного выражения,
        "control_type" :: [str] :: идентичное наименование атрибута *control_type* искомого объекта UIO,
        "control_type_re" :: [str] :: регулярное выражение (python диалект) для отбора UIO, у которого атрибут *control_type* должен удовлетворять условию данного регулярного выражения,
        "is_enabled" :: [bool] :: признак, что UIO доступен для выполнения действий,
        "is_visible" :: [bool] :: признак, что UIO отображается на экране,
        "backend" :: [str, "win32" || "uia"] :: вид способа адресации к UIO (по умолчанию "win32"). Внимание! Данный атрибут может быть указан только для первого элемента списка UIO селектора. Для остальных элементов списка данный атрибут будет проигнорирован.
    },
    { ... спецификация UIO следующего уровня иерархии }
]

Пример UIO селектора


[
    {"class_name":"CalcFrame", "backend":"win32"}, # Спецификация UIO 1-го уровня вложенности
    {"title":"Hex", "depth_start":3, "depth_end": 3} # Спецификация UIO 1+3-го уровня вложенности (так как установлены атрибуты depth_start|depth_stop, определяющие глубину поиска UIO)
]

PS. Перечень функций по работе с UIO селектором представлен в модуле UIDesktop (pyOpenRPA/Robot/UIDesktop.py). Именно эти функции будут использоваться при дальнейшей разработке робота.
Ознакомиться в полным перечнем функций модуля UIDesktop можно здесь


(По шагам) робот своими руками


Вот мы и добрались до самого интересного и важного раздела этого туториала — это пошаговый пример по созданию своего первого робота с использованием pyOpenRPA.


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


Шаг 0. Подготовим интерпретатор Python 3 для нового робота (развернем pyOpenRPA)


В отличии от подавляющего большинства RPA платформ, в pyOpenRPA реализован другой подход подключения к проекту. Если в остальных RPA платформах необходимо писать робота на языке этой платформы (текстовый, или графический, или скриптовый язык), то в случае pyOpenRPA именно вы определяете где, как и когда нужно использовать эту библиотеку в проекте.


Доступно несколько вариантов загрузки pyOpenRPA:


  • Вариант 1, простой. Скачать преднастроенную портативную версию с GitLab страницы проекта
  • Вариант 2, посложнее. Установить pyOpenRPA в свою версию интерпретатора Python 3 (pip install pyOpenRPA)

Шаг 1. Создать проект робота


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


Создадим следующую структуру проекта:


  • Папка "RobotCalc":
    • Файл "RobotCalc_1.py" — скрипт робота 1, который мы пишем сейчас
    • Файл "RobotCalc_1_Run_x64.cmd" — скрипт запуска робота 1
    • Файл "RobotCalc_2.py" — скрипт робота 2, дополнение
    • Файл "RobotCalc_2_Run_x64.cmd" — скрипт запуска робота 2

.cmd файлы в этом проекте играют важную роль — именно благодаря этим файлам мы будем иметь возможность выполнять запуск интересующего нас робота простым кликом по файлу.


Ниже приведу пример "RobotCalc_1_Run_x64.cmd" файла (файл "RobotCalc_2_Run_x64.cmd" аналогичен):


cd %~dp0 
..\Resources\WPy64-3720\python-3.7.2.amd64\python.exe "RobotCalc_1.py"
pause >nul

Шаг 2. Запустить студию pyOpenRPA и сформировать необходимые UIO селекторы


  • Открыть калькулятор (win + r > calc > enter)

Если вы скачали преднастроенную версию pyOpenRPA с GitLab (вариант 1, простой):


  • Выполнить запуск cmd файла web студии pyOpenRPA из репозитория "pyOpenRPA\Studio\pyOpenRPA.Studio_x64.cmd"

Если вы скачали пакет pyOpenRPA с помощью pip install pyOpenRPA (вариант 2, посложнее):


  • Выполнить запуск интерпретатора python со следующими аргументами: python -m pyOpenRPA.Studio "..\Studio\SettingsStudioExample.py", где SettingsStudioExample.py — это конфигурационный файл запуска студии pyOpenRPA. Преднастроенный шаблон этого файла можно скачать с репозитория pyOpenRPA в GitLab

При любом из вариантов через 5 — 15 сек. должна автоматически отобразиться web студия pyOpenRPA (см. ниже)


pyOpenRPA_studio
Внешний вид web студии pyOpenRPA


  • В списке открытых оконных GUI приложений найти калькулятор и активировать режим поиска UI элемента по наведению указателя мыши (Кнопка "Mouse search")
  • Переключиться на калькулятор (alt + tab)
  • Навести указатель мыши на тот элемент, который нам необходим для того, чтобы определить состояние интерфейса калькулятора. Выберем radio кнопку Hex. Зеленая окантовка появляется поверх калькулятора благодаря студии pyOpenRPA — именно таким образом студия сообщает нам о том, какой UI элемент она видит в калькулятора по той точке, куда наведен указатель мыши.

calc_radiobutton


Студии pyOpenRPA подсвечивает зеленой окантовкой обнаруженный UI элемент по месту указателя мыши на калькуляторе


  • Для того, чтобы остановить процесс поиска UI элемента необходимо зажать клавишу ctrl на 2-4 секунды, после чего в WEB интерфейсе студии появится иерархния до UI элемента, который студия подсвечивала зеленой окантовкой.

pyOpenRPA_studio_calc_ui_hex


Студия pyOpenRPA отобразила иерархию нахождения UI элемента в калькуляторе после отправки сигнала завершения поиска UI элементов (длительное нажатие ctrl)


  • Для того, чтобы убедиться в том, что элемент был обнаружен корректно, достаточно нажать кнопку "Highlight" по тому UI элементу, который интересует. Программа повторно нарисует эеленую окантовку поверх того UI элемента, который был обнаружен.


  • Далее выполнить клик по UI элементу в окне иерархии в студии, после чего перейти в окно редактирования UIO селектора (UIO селектор далее будет использоваться в коде робота в Python 3)



pyOpenRPA_studio_calc_ui_hex_uio


Студия pyOpenRPA сформировала UIO селектор в автоматическом режиме к UI элементу калькулятора


  • В нашем примере UI элемент расположен на 4-м уровне вложенности с атрибутом title = "Hex". В автоматическом режиме pyOpenRPA формирует UIO селектор по индексам расположения в вышестоящих UI элементах. Такой подход достаточно нежелательно использовать в конечных роботах, потому что индексы расположения UI элементов могут динамически изменяться во время работы программы.


  • Произведем преобразование нашего UIO селектора:


    [{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"},{"ctrl_index":0},{"ctrl_index":6},{"ctrl_index":1}]

    в следующий вид:



[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]

  • В результате преобразований убрали лишнее условие "title":"Калькулятор" и промежуточные уровни, которые характеризовались только индексами нахождения UI элементов. Вместо этого добавили условие поиска "title":"Hex" и установили область поиска "depth_start":3, "depth_end": 3 (в нашем случае это необходимо, потому что мы убрали явные уровни вложенности). Атрибуты "class_name" накладывает условие, что надо искать прилоежние с class_name = CalcFrame, а backend указывает pyOpenRPA, какую систему поиска UI элементов использовать (win32 или uia, у каждой и них есть + и -)
  • С помощью кнопки "Hightlight element" убедимся в том, что UI элемент, по-прежнему, обнаруживается студией pyOpenRPA (при нажатии на кнопку поверх UI элемента должна быть отрисована зеленая окантовка — новый UIO селектор работает корректно)

  • Данный UIO селектор будем использовать в роботе для проверки состояния интерфейса калькулятора: если UI элемент успешно обнаруживается, то режим калькулятора установлен верный. Если UI элемент не обнаруживается, то режим калькулятора установлен неверный, и его нужно будет изменить. Для того, чтобы проверить наличие UI элемента по UIO селектору воспользуемся функцией pyOpenRPA.Robot.UIDesktop.UIOSelector_Exist_Bool


    lCalcHex_IsExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]) # Проверить наличие UI элемента по UIO селектору

  • Для того, чтобы установить режим программиста, воспользуемся еще одной возможностью win32 — активация события, расположенного в меню приложения (см. ниже).



Calc Vid Programmist


Вид "Программист" в калькуляторе


Активация элемента меню выполняется с помощью специальной функции menu_select у корневого UIO объекта GUI приложения.


  • С помощью студии pyOpenRPA сформируем UIO селектор корневого объекта калькулятора


    lUIOSelectorCalculator = [{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"}] # Сформировали UIO селектор из студии pyOpenRPA

  • Далее запросим UIO объект по UIO селектору, после чего вызовем функцию menu_select, в которую передадим строковый адрес вызываемого элемента меню


    lUIOCalculator = UIDesktop.UIOSelector_Get_UIO(inSpecificationList=lUIOSelectorCalculator) # Получить UIO экземпляр
    lUIOCalculator.menu_select("&Вид -> &Программист") # Выполнить смену режима калькулятора


Шаг 3. Консолидируем код в проекте робота


Обадая всеми необходимыми UIO селекторами и функциями, перейдем к составлению целостного скрипта робота. Ниже я приведу код RobotCalc_1.py файла, готового для запуска (python.exe "RobotCalc_1.py") c детальным описанием каждой строки.


from pyOpenRPA.Robot import UIDesktop # Импорт модуля, который умеет управлять UI элеметами GUI приложений
import time # Библиотека сна алгоритма
import os # Библиотека системных функций, позволит открять калькулятор, если это потребуется
lUIOSelectorCalculator = [{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"}] # Сформировали UIO селектор из студии pyOpenRPA
while True: # Вечный цикл
    lUIOCalculator = UIDesktop.UIOSelector_Get_UIO(inSpecificationList=lUIOSelectorCalculator) # Получить UIO экземпляр
    lCalcHex_IsExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]) # Проверить наличие UI элемента по UIO селектору
    if not lCalcHex_IsExistBool: # Проверить, что UI элемент отсутствует
        lUIOCalculator.menu_select("&Вид -> &Программист") # Выполнить смену режима калькулятора
    time.sleep(1) # Выполнить сон на 1 сек., после чего перейти на следующую итерацию

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


Дополнение. Дорабатываем робота, чтобы он еще включал калькулятор (если он выключен), раскрывал его (если он свернут)


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


  • Для запуска калькулятора будем использовать функцию os.system



os.system("calc") # Открыть калькулятор

  • Для проверки состояния окна (свернуто в трэй или развернуто) is_minimized

lUIOCalculator.is_minimized()

  • Для восстановления свернутого окна будем использовать функцию restore

lUIOCalculator.restore() # Восстановить окно калькулятора из свернутого вида

  • Итого получим следующий исходный код робота (файл RobotCalc_2.py).

from pyOpenRPA.Robot import UIDesktop # Импорт модуля, который умеет управлять UI элеметами GUI приложений
import time # Библиотека сна алгоритма
import os # Билбиотека системных функций, позволит открять калькулятор, если это потребуется
lUIOSelectorCalculator = [{"title":"Калькулятор","class_name":"CalcFrame","backend":"win32"}] # Сформировали UIO селектор из студии pyOpenRPA
while True: # Вечный цикл
    lExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=lUIOSelectorCalculator) # Проверить наличие окна по UIO селектору
    if not lExistBool: # Проверить наличие окна калькулятора
        os.system("calc") # Открыть калькулятор
    else: # Проверить, что окно калькулятора не свернуто
        lUIOCalculator = UIDesktop.UIOSelector_Get_UIO(inSpecificationList=lUIOSelectorCalculator) # Получить UIO экземпляр
        if lUIOCalculator.is_minimized(): # Проверить, что калькулятор находится в свернутом виде
            lUIOCalculator.restore() # Восстановить окно калькулятора из свернутого вида
        else:
            lCalcHex_IsExistBool = UIDesktop.UIOSelector_Exist_Bool(inUIOSelector=[{"class_name":"CalcFrame","backend":"win32"},{ "title":"Hex", "depth_start":3, "depth_end": 3}]) # Проверить наличие UI элемента по UIO селектору
            if not lCalcHex_IsExistBool: # Проверить, что UI элемент отсутствует
                lUIOCalculator.menu_select("&Вид -> &Программист") # Выполнить смену режима калькулятора
    time.sleep(1) # Выполнить сон на 1 сек., после чего перейти на следующую итерацию

PS 1. Для сравнения: Реализация аналогичного алгоритма в другой RPA платформе с помощью инструментов визуального программирования потребует в 3-4 раза больше пространства рабочей области экрана (в связи со спецификой визуального программирования).


PS 2. Перечень всех функций в модуле UIDesktop (pyOpenRPA/Robot/UIDesktop.py)
Ознакомиться в полным перечнем функций модуля UIDesktop можно здесь


Подведем итоги


Итак, мы успешно преодолели первые шаги по созданию бесплатных программных роботов. Безусловно, эта статья покрывает далеко не все области программной роботизации. В следующих статьях-туториалах мы остановимся на оставшихся "столпах" роботизированного управления (мышь, клавиатура, распознавание изображения с экрана и web манипуляции).


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


Всегда открыт к вашим комментариям, и буду рад помочь вам в решении ваших вопросов.


До скорых публикаций!

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Насколько актуальна тема RPA для вас и вашего бизнеса?

  • 26,3%Тема актуальна, уже активно использую (платные RPA платформы)5
  • 63,2%Тема актуальна, еще не использовал12
  • 5,3%Тема не актуальна, но использую1
  • 5,3%Тема не актуальна, не использую1
  • 0,0%Другое (очень рады будем узнать ваш вариант ответа в комментах ниже)0

Комментарии 7

    0
    А как на счет других приложений??? Даже самописных — будет работать корректно?? Заголовки окон, элементы и прочее- выбирал программой spy++ — но там не все элементы нормально были найдены.
      0
      Да, с другими приложениями работать будет, но с каждым приложением индивидуально. Действительно, встречаются ситуации, когда элементы обнаруживает не так детально, но по своей текущей практике могу сказать, что нам все ситуации удалось стабилизировать. Иногда прибегали к вспомогательным технологиям роботизации, о которых я планирую написать в следующих туториалах.
        0
        Поэтому на таких приложениях делал по другому, брал заголовок окна- центрировал по экрану и дальше по координатам стабильно-четко нажимал кнопки ))), НО не хватало поправки на различное разрешение мониторов.
          0
          Абсолютно согласен — тема с координатами иногда супер актуальна. Собственно про нее я тоже буду писать, когда зайдет речь о манипуляции мышь + клавиатура (кнопки, hotkeys и т.д.)

          По поводу GUI окон еще могу добавить, что иногда спасает 2-й способ адресации — uia. То есть, если win32 не видит тех полей/кнопок, которые мне нужны, то достаточно часто их видит uia. Буду рад, если где-то это вам пригодится! :)
      0

      Спасибо большое за подробную статью, обязательно опробую данный инструмент и буду ждать следующие, тема актуальна как никогда

        0

        Очень интересная статья спасибо, надеюсь остальные части выйдут скоро.


        А что можете сказать про библиотечку RPA Python, бывший TAGUI for python? Тоже весьма интересный проект на первый взгляд, какие там подводные камни?

          0
          Добрый день! Да, изучал эту библиотеку. У нее, к сожалению, нет функциональности по влезанию в GUI декстопных приложений. В ней можно управлять этими приложениями только косвенно через клавиатуру/мышку/картинки работать.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое