Vídeo da execução da automação
Repositório no GitHub: https://github.com/olucascruz/oficina_rpa
Introdução
Hoje, nesta oficina, vamos colocar a mão na massa e construir juntos um RPA! Nosso projeto será uma automação web e desktop, onde veremos, na prática, como essa tecnologia pode facilitar tarefas repetitivas.
O processo que vamos desenvolver consiste em:
- Acessar o Google News.
- Pesquisar um tema específico.
- Obter os títulos das notícias.
- Abrir o Impress (na oficina presencial, utilizamos o PowerPoint).
- Criar uma apresentação com esses títulos.
Meu nome é Lucas Cruz, sou desenvolvedor de RPAs e faço parte do projeto DX Academy. Atualmente, curso Sistemas de Informação e, nos últimos dois anos, desenvolvi diversos RPAs para automatizar processos e otimizar fluxos de trabalho.
A oficina foi conduzida por mim, com a colaboração do Henrique Franklin, que abordou conceitos fundamentais de RPA e mapeamento de processos. Todo o material utilizado, incluindo as apresentações, está disponível no repositório do projeto.
O que é RPA?
RPA (Robotic Process Automation) é a tecnologia que usa 'robôs' para automatizar tarefas manuais e repetitivas. Com ela, é possível aumentar a produtividade, reduzir erros e liberar tempo para atividades estratégicas. Vamos ver como isso funciona na prática!
Parte Técnica
Criando o Ambiente Virtual
Para começar, criem um ambiente virtual com o seguinte comando:
python -m venv venv
Ativem o ambiente virtual:
venv/Scripts/Activate.ps1
Instalando Dependências
Instale o Cookiecutter:
pip install cookiecutter
Em seguida, use o template da BotCity:
python -m cookiecutter https://sdk.botcity.dev/templates/python/v2.zip
Selecione a opção "ambos" (both) e dê um nome ao seu projeto.
Defina o Project ID, por exemplo:
rpa
Agora que baixamos o template da BotCity, vou explicar um pouco sobre os arquivos gerados. Entre eles, temos:
- Arquivos necessários para gerar a build.
- O arquivo
.botproject
, que permite abrir o projeto na IDE da BotCity.
Contextualização
Na indústria, uma atividade muito comum é baixar dados de um sistema e enviá-los por e-mail. Esse processo, quando feito manualmente, pode ser repetitivo e propenso a erros, tornando a automação uma solução eficiente.
Um aspecto importante das automações web é que devem ser executadas no mesmo navegador em que foram desenvolvidas. Isso ocorre porque páginas da web podem ter comportamentos distintos em diferentes navegadores, o que pode comprometer a estabilidade da automação. Embora esse problema nem sempre aconteça, é um risco que deve ser considerado.
Instalando Dependências do Projeto
Acesse a pasta do projeto:
cd rpa
Instale os pacotes necessários:
pip install -r requirements.txt
Escolhendo um WebDriver
Para iniciarmos, precisamos de um WebDriver, que permite ao nosso RPA interagir com o navegador. A escolha do driver depende do navegador que será utilizado na automação.
Aqui, utilizaremos o Geckodriver, que é o WebDriver do Firefox. Pode ser baixado em:
https://github.com/mozilla/geckodriver/releases
Outra alternativa é usar o WebDriver Manager:
https://pypi.org/project/webdriver-manager/
Atenção: O webdriver-manager pode facilitar a configuração inicial, mas também introduz uma dependência de terceiros. Isso pode gerar falhas na obtenção do WebDriver e comprometer a execução da automação. Portanto, avalie bem a melhor abordagem para o seu projeto.
Implementação
No processo manual, abriríamos o navegador, acessaríamos o Google e, então, buscaríamos pelo Google News. No entanto, ao automatizar esse fluxo, podemos otimizar o processo acessando diretamente a página desejada, tornando a automação mais eficiente.
webbot.browser("url")
então para maximizar usamos
webbot.maximize_window()
Localizando Elementos Web
Para interagir com os elementos da página, utilizamos as ferramentas de desenvolvedor do navegador para inspecioná-los e obter seus seletores. O XPath é um dos métodos mais utilizados para encontrar elementos.
Capturamos o elemento de input de pesquisa com:
webbot.find_element("xpath", by=By.XPATH)
Depois, podemos escrever no input com:
webbot.send_keys("texto")
Ou alternativamente:
webbot.click()
webbot.paste("texto")
Para enviar a pesquisa:
webbot.enter()
No código do projeto fica assim:
input_el = webbot.find_element("/html/body/div[4]/header/div[2]/div[2]/div[2]/form/div[1]/div/div/div/div/div[1]/input[2]", by=By.XPATH)
input_el.send_keys(search_text)
webbot.enter()
Para capturar os títulos da pesquisa, usamos:
title_text1 = webbot.find_element("xpath", by=By.XPATH).text
Se quisermos validar se o elemento foi encontrado corretamente, podemos imprimir o resultado:
print(title_text1)
Se não for o elemento correto, o retorno pode ser NoneType
.
Para capturar os 5 e guardar esses titulos em uma lista podemos fazer da seguinte maneira
list_text_news = []
webbot.wait(2000)
news1 = webbot.find_element('/html/body/c-wiz/div/main/div[2]/c-wiz/c-wiz[1]/c-wiz/article/div[1]/div[2]/div/a', by=By.XPATH).text
news2 = webbot.find_element('/html/body/c-wiz/div/main/div[2]/c-wiz/c-wiz[2]/c-wiz/div/div[1]/c-wiz/article/div[1]/div[2]/div/a', by=By.XPATH).text
news3 = webbot.find_element('/html/body/c-wiz/div/main/div[2]/c-wiz/c-wiz[2]/c-wiz/div/div[2]/c-wiz/article/div[1]/div[2]/div/a', by=By.XPATH).text
news4 = webbot.find_element('/html/body/c-wiz/div/main/div[2]/c-wiz/c-wiz[2]/c-wiz/div/div[3]/c-wiz/article/div[1]/div[2]/div/a', by=By.XPATH).text
news5 = webbot.find_element('/html/body/c-wiz/div/main/div[2]/c-wiz/c-wiz[3]/c-wiz/article/div[1]/div[2]/div/a', by=By.XPATH).text
webbot.stop_browser()
list_text_news.append(news1)
list_text_news.append(news2)
list_text_news.append(news3)
list_text_news.append(news4)
list_text_news.append(news5)
Uma boa prática no desenvolvimento de software é sempre manter o código desacoplado. Como essa parte da automação é responsável apenas por capturar noticias do google news podemos organizá-la em uma função específica:
def get_titles_news(webbot:WebBot, search_text):
webbot.browse("https://news.google.com/home?hl=pt-BR&gl=BR&ceid=BR:pt-419")
webbot.maximize_window()
input_el = webbot.find_element("/html/body/div[4]/header/div[2]/div[2]/div[2]/form/div[1]/div/div/div/div/div[1]/input[2]", by=By.XPATH)
input_el.send_keys(search_text)
webbot.enter()
list_text_news = []
webbot.wait(2000)
news1 = webbot.find_element('/html/body/c-wiz/div/main/div[2]/c-wiz/c-wiz[1]/c-wiz/article/div[1]/div[2]/div/a', by=By.XPATH).text
news2 = webbot.find_element('/html/body/c-wiz/div/main/div[2]/c-wiz/c-wiz[2]/c-wiz/div/div[1]/c-wiz/article/div[1]/div[2]/div/a', by=By.XPATH).text
news3 = webbot.find_element('/html/body/c-wiz/div/main/div[2]/c-wiz/c-wiz[2]/c-wiz/div/div[2]/c-wiz/article/div[1]/div[2]/div/a', by=By.XPATH).text
news4 = webbot.find_element('/html/body/c-wiz/div/main/div[2]/c-wiz/c-wiz[2]/c-wiz/div/div[3]/c-wiz/article/div[1]/div[2]/div/a', by=By.XPATH).text
news5 = webbot.find_element('/html/body/c-wiz/div/main/div[2]/c-wiz/c-wiz[3]/c-wiz/article/div[1]/div[2]/div/a', by=By.XPATH).text
webbot.stop_browser()
list_text_news.append(news1)
list_text_news.append(news2)
list_text_news.append(news3)
list_text_news.append(news4)
list_text_news.append(news5)
return list_text_news
Aprofundando
A página do Google News apresenta uma peculiaridade: seu conteúdo não é fixo. Além da lista de notícias, também são exibidas caixas com notícias de uma mesma fonte. Esses dois tipos de elementos não estão localizados em posições fixas, o que torna a busca pelos textos mais desafiadora.
Usar o método mencionado anteriormente não garante que a automação funcione de forma consistente. Quando isso acontece, precisamos buscar alternativas.
Uma opção seria buscar os textos usando seletores diferentes do XPath. Uma forma comum de resolver esse problema é buscar todos os elementos com uma determinada classe, utilizando o método:
webbot.find_elements("nome_da_classe", by = By.CLASS_NAME)
(Note que essa função retorna uma lista de múltiplos elementos)
No entanto, neste caso específico, esse método não funciona.
Uma forma de contornar esse problema é buscar todos os conteúdos de texto dentro das tags e, em seguida, filtrar para obter apenas as notícias. O código ficaria assim:
desta forma:
text_in_page = webbot.page_source().get_text(" | ")
list_text_in_page = text_in_page.split(" | ")
list_text_contains_search_text = list(filter(lambda value: search_text.lower() in value.lower() and "notícias sobre" not in value.lower(), list_text_in_page))
Aqui usamos um filtro que remove textos que contém "notícias sobre", pois os blocos com notícias de uma mesma fonte tem esse título.
Dessa maneira, nossa solução fica menos propensa a erros.
Ao desenvolver RPAs sempre nos deparamos com problemas que exigem soluções diferentes. A abordagem mostrada anteriormente é a forma mais geral para se buscar um elemento da página e obter seu texto. Nesta seção é mostrado uma solução melhor adaptada para esse site em específico e para está automação.
IMPORTANTE: A primeira solução foi criada na primeira vez que automatizei esse site, sem conhecer completamente seu comportamento, o que a tornava propensa a erros. Para evitar esses problemas, é fundamental realizar testes. Durante o desenvolvimento de uma automação, é comum deixá-la executando periodicamente em um período de testes, o que nos permite identificar erros, casos especiais e outros problemas. Ao corrigir essas questões, nossa solução se torna mais robusta.
Por fim a função fica assim:
def get_titles_news(webbot:WebBot, search_text:str):
webbot.browse("https://news.google.com/home?hl=pt-BR&gl=BR&ceid=BR:pt-419")
webbot.maximize_window()
input_el = webbot.find_element("/html/body/div[4]/header/div[2]/div[2]/div[2]/form/div[1]/div/div/div/div/div[1]/input[2]", by=By.XPATH)
input_el.send_keys(search_text)
webbot.enter()
webbot.wait(3000)
text_in_page = webbot.page_source().get_text(" | ")
list_text_in_page = text_in_page.split(" | ")
list_text_contains_search_text = list(filter(lambda value: search_text.lower() in value.lower() and "notícias sobre" not in value.lower(), list_text_in_page))
webbot.stop_browser()
return list_text_contains_search_text
Automação Desktop
Agora que temos os títulos, vamos abrir o Impress para criar uma apresentação.
Uma maneira simples de abrir um programa no Windows via automação é:
- Pressionar a tecla Windows.
- Pesquisar o nome do programa.
- Pressionar Enter.
Interação com Interface Gráfica
Para interagir com elementos visuais, utilizamos imagens de referência. Essas imagens podem ser capturadas manualmente ou pelo BotCity Studio que também gera o código para interagir com a imagem.
Vantagens do BotCity Studio:
- Gera automaticamente o código para interagir com as imagens.
- Facilita o uso de "click relativo", onde o usuário pode selecionar visualmente o local em que deseja clicar em relação à imagem identificada.
Para encontrar um elemento na interface, usamos o seguinte código:
desktop_bot.find("nome_da_imagem")
As imagens devem estar na pasta resources, que já vem configurada no template.
O código gerado pelo BotCity Studio para encontrar e clicar em um elemento é o seguinte:
if not desktop_bot.find("nome_da_imagem"):
not_found("nome_da_imagem")
desktop_bot.click()
Se o elemento for opcional (como um popup de atualização), podemos evitar erros verificando sua existência antes de tentar clicar:
if desktop_bot.find("nome_da_imagem"):
desktop_bot.click()
Se o item não for encontrado, a função de clique não será executada, evitando falhas na automação.
Podemos dividir a parte de criação da apresentação em uma função separada para manter o código mais organizado. O código final para a função de criação da apresentação seria:
def create_pptx(desktop_bot:DesktopBot, search_text, titles):
desktop_bot.type_windows()
desktop_bot.paste("LibreOffice Impress")
desktop_bot.enter()
if desktop_bot.find("fechar_btn"):
desktop_bot.click()
if not desktop_bot.find("title_field"):
not_found("text_field")
desktop_bot.click()
desktop_bot.paste(f"Principais notícias sobre: {search_text}")
if not desktop_bot.find("text_field"):
not_found("text_field")
desktop_bot.click()
for title in titles:
desktop_bot.paste(title)
desktop_bot.enter()
desktop_bot.backspace()
desktop_bot.control_a()
# decrease font size
for _ in range(4):
desktop_bot.control_key("[")
if not desktop_bot.find("formatte_bt"):
not_found("formatte_bt")
return
desktop_bot.click()
if desktop_bot.find("list_bt"):
desktop_bot.click()
if desktop_bot.find("unordered_list_bt"):
desktop_bot.click()
desktop_bot.control_s()
desktop_bot.wait(2000)
desktop_bot.kb_type("novo_file")
desktop_bot.enter()
desktop_bot.wait(2000)
# Close app
desktop_bot.control_key("q")
Dicas para automação desktop
- Use imagens únicas. Se um botão contém um ícone e um texto, prefira capturar a imagem com o texto para evitar confusões.
- Prefira usar os atalhos do programa, se disponíveis, em vez de depender de visão computacional, pois isso tende a ser menos propenso a erros.
- Use sabiamente o comando de espera, especialmente se o programa depender de algum processamento demorado.
IMPORTANTE: Imagens capturadas em determinadas telas não serão reconhecidas em telas com resoluções diferentes.
Estrutura do projeto
No final, a estrutura do projeto fica assim, com a pasta app contendo as funções tanto de acesso ao site quanto de uso do PowerPoint. Para automações mais complexas, em que vários sistemas estão envolvidos, podemos criar subpastas específicas para cada funcionalidade dentro da pasta app.
Embora não tenha sido usado neste projeto, em sistemas mais robustos, é comum criar uma pasta utils que contém funções utilitárias que são usadas em várias partes do projeto.
Com isso, finalizamos a construção do nosso RPA! Espero que esta oficina tenha sido útil para vocês e que consigam aplicar esses conhecimentos em novos projetos. Boa automação!
PS: Se você gostou desse texto, recomendo também o meu primeiro artigo sobre automação, onde compartilho o passo a passo para automatizar um editor de imagens GIMP. Confira aqui: Automatizando editor de imagens GIMP com Python/Botcity
Top comments (0)