Как автоматизировать рутинные операции с помощью Jupyter, Python и Selenium

Привет, Хабр! Меня зовут Николай Суворов, я руководитель направления в МТС Digital. Занимаюсь продуктом МТС Premium — это единая подписка на сервисы МТС и партнеров. Сегодня я расскажу о нашем опыте создания робота для автоматизации повторяющихся действий сотрудников с помощью Jupyter, Python и Selenium. Статья будет интересна прежде всего менеджерам, которые хотят оптимизировать свою работу. Разработчикам мой текст будет полезен с точки зрения понимания возможностей по ускорению повторяющихся действий в интерфейсах. Весь необходимый код — ниже.

f9de52835fd0bc551b1893e170722b90.jpg

Зачем автоматизировать рутину

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

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

Часть рутинных операций можно устранить при помощи грамотного UX. Но бывает и так, что требуется постоянно повторять одни и те же действия и в самых продуманных интерфейсах. Даже с кнопкой «копировать» или «создать по шаблону» рутинное действие не всегда происходит быстрее. Кроме того, при ручных операциях с увеличением количества задач возрастает и вероятность ошибки. Особенно, если дело касается цифр, дат, опций. Менеджер забыл поставить опцию отмены промокода в определённый день — и код становится вечным. Узнаем мы об этом только потом, читая еженедельный отчёт по активациям кодов. И это — совсем не единичный случай.

UX/CX-анализ — дело хлопотное и затратное, но доработки, ускоряющие выявленные повторяющиеся операции, обойдутся еще дороже. Их надо проаналитить, закодить, оттестировать, зарелизить. Между выявлением проблемы и ее устранением могут пройти месяцы и даже годы. И все это время компания продолжит тратить ресурсы на повторяющиеся однообразные операции, которые будут выполнять всё возрастающее количество ассистентов.

Как одному сотруднику завести 204 промокода за 2 часа

Итак, вернемся к нашему координатору проектов. Суть ее задачи состояла в том, чтобы через особый интерфейс в 17 подписках завести по 12 промокодов, которые затем идут в некую базу данных, где для каждого такого кода генерируются тысячи уникальных кодов. Такова архитектура, менять ее мы не можем. 

Кроме того, необходимо к каждому коду указать даты его действия, размер предоставляемой скидки, название для облегчения поиска среди других кодов. Сам код нужно придумать, следуя специальному соглашению о нейминге. Помножив 17 на 12 — получаем 204 кода. На каждый тратим около 1–2 минут. Итого, в лучшем случае, уйдет 3,5 часа. А в реальности эта работа займет — 5–6 часов, которые уйдут на нажатия кнопок в интерфейсе и ввод данных в текстовые поля. Кажется, что это слишком простая работа для целого координатора проектов. Но такой работы много и кому-то надо ее выполнить, причем очень быстро. Зачастую бизнес ожидает, что коды будут заведены в тот же день, даже если задача поступает в 17:00.

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

Промокоды были придуманы нами неожиданно. Понятное дело, что система не может дорабатываться под «хотелки» команды каждого отдельного продукта по мере придумывания сотрудниками новых фич. Итогом наших прений была работа координатора проектов над задачей по ручному заведению 200+ промокодов 2–3 раза в неделю.

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

Как нам помог Jupyter, Python и Selenium

При помощи связки Jupyter+Python+Selenium можно в 4–5 раз ускорить известные, повторяемые изо дня в день, операции в интерфейсах. Selenium — известный продукт для симуляции действий в интерфейсах и тестирования. Python — простейший язык, доступный для освоения даже младшим школьникам. Jupyter — лучший инструмент моделирования алгоритмов. Для разработки мы использовали окружение Anaconda, поскольку оно сразу дает почти весь нужный инструментарий в готовом виде. Повозиться пришлось только с установкой Selenium и его драйвера для Google Chrome. Но про это написано множество статей и руководств, поэтому повторяться не будем.

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

Мы не думаем о тестировании — блокнот сам по себе инструмент тестирования гипотез о работе алгоритма. Мы тут же устанавливаем все нужные модули и смотрим справку всех функций, которые нас интересуют, описываем алгоритм словами, на языке разметки Markdown. Не знаю, как для разработчиков, а для менеджеров ничего лучше не придумано. Пожалуй, визуальное zero-code программирование посложнее будет. И оно менее гибкое, если нужен только бэк.

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

Сначала заводим свойства для нашего робота:

#Логин и пароль нашей Большой системы (в которой заводим промокоды)
lg = "oursuperman"
ps = "qwerty1234"
 
#Настройки промо-кампании общие
promoActionName = 'Всемирный день благодарности за хорошие продукты'
campaign_num = '003432' #Общий номер промокампании для облегчения поиска
campaign_length = '45' #Длительность промокампании
campaign_tariff_period = '45' #Через сколько начинаем брать деньги (да, это разные параметры)
tariff_category = '0 ₽' #Пока действует промокод не берём с клиента ни копейки
campaign_period_start = '20.10.2022' #Дата начала промокампании
campaign_period_stop = '28.10.2022' #Дата окончания
promocodes_generated = [] #Сюда будем складывать наши промики для статистики
#А тут у нас будут настройки промокампании по подпискам (те самые 17 подписок)
subs_to_promo = [
	{
    	'name':'МТС некая подписка 1',
    	'prefix':'N',
    	'num_promocodes':1
	},
	{
    	'name':'МТС некая подписка 2',
    	'prefix':'N',
        'num_promocodes':1
    }
]

Далее пишем основные функции, которые будем использовать из раза в раз:

def openContent(content_obj):
	#Открываем нужную подписку
	url = f"https://our.domain/subs/{content_obj['content_id']}"
	driver.get(url)
	
def processContent(content_obj):
	#Скролл вниз
	driver.find_element(By.TAG_NAME, "body").send_keys(Keys.PAGE_DOWN)
	#Открываем расхлоп "Промоакции"
	promo_block = driver.find_element(By.XPATH, "//span[text()=' Промоакции ']")
	promo_block.find_element(By.CSS_SELECTOR, "button").click()
	
def createPromocode(num_promocode):
	#Создаём новую промоакцию
	promo_block = driver.find_element(By.XPATH, "//span[text()=' Промоакции ']")
	promo_block.find_element(By.CSS_SELECTOR, "button.inserted").click()
	
def setFieldsPromocode(num_promocode):
	#Выставляем свойства промоакции
	f_dateRange = driver.find_element(By.NAME, "dateRange")
	f_dateRange.click()
    sleep(1) #Обязательно делаем паузы, чтобы было время прогрузиться элементам
    f_dateRange.send_keys(campaign_period_start) #Тут просто печатаем символы в поля ввода
	f_dateRange.send_keys(campaign_period_stop)
	#Добавляем название кампании
	f_promoActionName = driver.find_element(By.NAME, "promoActionName")
	f_promoActionName.find_element(By.CSS_SELECTOR, "input.input_111").send_keys(f"{promoActionName}_{num_promocode}")
	#Добавляем длительность
	f_period = driver.find_element(By.NAME, "period")
	f_period.find_element(By.CSS_SELECTOR, "input.input_222").send_keys(campaign_length)
	#Добавляем период тарификации
	f_tarifficationPeriod = driver.find_element(By.NAME, "tarifficationPeriod")
    f_tarifficationPeriod.find_element(By.CSS_SELECTOR, "input.input_333").send_keys(campaign_tariff_period)
	#Формируем текст промо-кода
	promocode = ""
    #Просто собираем текстовую строку. Проще конечно по шаблону, но тут нагляднее процесс сборки
    promocode += content_obj["prefix"] + "_"
    #... тут много всего в части соблюдения нашего соглашения о нейминге
	promocode += campaign_num + "_"
	promocode += str(num_promocode)
	#Добавляем текст промокода
	f_promoActionName = driver.find_element(By.NAME, "promoCode")
    f_promoActionName.find_element(By.CSS_SELECTOR, "input.input_444").send_keys(promocode)
	return promocode

Далее открываем браузер и логинимся при помощи Selenium:

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from time import sleep
 
#Открываем сайт Большой системы (в которой заводим промокоды) при помощи драйвера Selenium
url = "https://big.system.domain"
driver = webdriver.Chrome(executable_path=r"C:\Chrome\DriverPath\chromedriver.exe")
driver.get(url)
#Обязательно ждём пока загрузится URL
sleep(5)
#Находим все нужные поля. Я для этого использовал XPath (см. режим разработчика в Google Chrome и правой кнопкой мыши по нужному нам элементу, там Copy Xpath)
login = "/html/body/div[2]/app-input/div/input"
password = "/html/body/div[2]/app-input-password/div/input"
btn_enter = "/html/body/app-form/form/input"
login_control = driver.find_element(By.XPATH, login)
password_control = driver.find_element(By.XPATH, password)
btn_enter = driver.find_element(By.XPATH, btn_enter)
 
#Логинимся
login_control.clear()
login_control.send_keys(lg)
password_control.clear()
password_control.send_keys(ps)
sleep(3)
btn_enter.click()

И в завершение, основная логика работы робота по заведению промокодов через интерфейс Большой системы:

#Цикл перебора подписок, в которых мы хотим завести промокоды (см. первый блок с настройками)
for content in list(range(0,len(subs_to_promo))):
	content_obj = subs_to_promo[content]
	#Тут уже вызываем описанную нами ранее функцию (см. второй блок с функциями)
    openContent(content_obj)
    #Всегда даём страничке прогрузиться
    sleep(3)
    #Задача этой функции открыть все нужные расхлопы на странице. У вас может быть по другому
	processContent(content_obj)
	sleep(3)
                                  	
	for num_promocode in list(range(1, content_obj['num_promocodes']+1)):
    	createPromocode(num_promocode)
        #Добавляем сгенерированный и положенный нами промокод для статистики
        promocodes_generated.append(setFieldsPromocode(num_promocode))
    	#Нажимаем кнопку "Создать"
    	driver.find_element(By.XPATH, f"//button[text()='Создать']").click()
    	sleep(8)
        
#В конце выводим список сгенерированных промокодов
print("Список промокодов:")
for code in promocodes_generated:
	print(code)

Заключение

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

Если что-то идет не так — всегда можно остановить выполнение и начать с того места на котором остановились. Это же Jupyter, он позволяет такие вещи. Выглядит просто, но оно работает и реально экономит время на другие задачи, много времени. 

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

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

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

© Habrahabr.ru