DEV Community

Cover image for Crie seu primeiro RPA com BotCity
Lucas Cruz
Lucas Cruz

Posted on

Crie seu primeiro RPA com BotCity

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:

  1. Acessar o Google News.
  2. Pesquisar um tema específico.
  3. Obter os títulos das notícias.
  4. Abrir o Impress (na oficina presencial, utilizamos o PowerPoint).
  5. 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
Enter fullscreen mode Exit fullscreen mode

Ativem o ambiente virtual:

venv/Scripts/Activate.ps1
Enter fullscreen mode Exit fullscreen mode

Instalando Dependências

Instale o Cookiecutter:

pip install cookiecutter
Enter fullscreen mode Exit fullscreen mode

Em seguida, use o template da BotCity:

python -m cookiecutter https://sdk.botcity.dev/templates/python/v2.zip
Enter fullscreen mode Exit fullscreen mode

Selecione a opção "ambos" (both) e dê um nome ao seu projeto.
Defina o Project ID, por exemplo:

rpa
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Instale os pacotes necessários:

pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Depois, podemos escrever no input com:

webbot.send_keys("texto")
Enter fullscreen mode Exit fullscreen mode

Ou alternativamente:

webbot.click()
webbot.paste("texto")
Enter fullscreen mode Exit fullscreen mode

Para enviar a pesquisa:

webbot.enter()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Para capturar os títulos da pesquisa, usamos:

title_text1 = webbot.find_element("xpath", by=By.XPATH).text
Enter fullscreen mode Exit fullscreen mode

Se quisermos validar se o elemento foi encontrado corretamente, podemos imprimir o resultado:

print(title_text1)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Enter fullscreen mode Exit fullscreen mode

(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))
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 é:

  1. Pressionar a tecla Windows.
  2. Pesquisar o nome do programa.
  3. 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")
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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") 
Enter fullscreen mode Exit fullscreen mode

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

Print da 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

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay