sábado, 5 de fevereiro de 2022

Selenium com Python

Índice



Introdução

Selenium automates browsers. That's it! What you do with that power is entirely up to you. Primarily it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should) also be automated as well.

Essa frase foi retirada diretamente do site oficial do Selenium. Traduzindo, o Selenium começou como um framework para testes funcionais de aplicações web, porém com o passar do tempo foi amplamente utilizado para automatizar tarefas como acessar e coletar informações de páginas web.

Neste artigo, vamos abordar esse aspecto de automação e webscraping em um exemplo prático, onde vamos buscar informações sobre ações no site Fundamentus e montar um arquivo CSV que possa ser importado em qualquer lugar (banco de dados, Google Sheets, etc.)



Arquitetura

Para começar precisamos entender como funciona a arquitetura do selenium que está dividida em três partes.

  • Cliente: é uma biblioteca específica para uma determinada linguagem feita para interagir com o selenium
  • Driver: é o agente que irá permitir a interação entre o cliente e o browser de fato
  • Browser: é o ambiente onde a página será renderizada e executada.







Instalações

Neste exemplo, vamos utilizar a linguagem python, então precisamos baixar duas coisas, a biblioteca oficial do selenium para python e o driver correspondente ao browser que estamos utilizando.

A instalação da biblioteca é bem simples:

pip install selenium

(ou você pode usar o poetry)

A instalação do driver pode ser um pouco complicada pois a versão dele deve ser idêntica a versão do browser instalado na máquina, então para facilitar, é possível utilizar a imagem docker do selenium.

version: '3.8'

services:
    selenium-chrome:
        image: selenium/standalone-chrome:latest
        hostname: selenium-chrome
        networks:
            - selenium-network
        privileged: true
        shm_size: 2g
        ports:
            - "4444:4444"
            - "7900:7900"
        healthcheck:
            test: ["CMD", "curl", "-f", "http://localhost:7900"]
            interval: 10s
            timeout: 10s
            retries: 5
    app:
        build: ./
        volumes:
            - /Users/geraldoferraz/repositorios/blog/selenium-with-python:/tmp/
        networks:
            - selenium-network
        depends_on:
            selenium-chrome:
                condition: service_healthy

networks:
    selenium-network:
      driver:  bridge



Site Fundamentus

Agora que temos tudo que precisamos, vamos analisar a página do Fundamentus e entender onde estão as informações que queremos coletar. Vamos acessar a página de análise da ação da Magazine Luiza (MGLU3). Para isso, acesse este link e você verá uma página semelhante a essa:




Inspect do Chrome

A forma mais fácil de localizar um elemento é utilizando o XPATH. Vamos utilizar a ferramenta de inspeção do browser para recuperar o xpath das informações de “Cotação”, “Min 52 sem”, “Max 52 sem”, “Div. Yield” e a Oscilação “Dia”.

  • Encontre a informação desejada, clique com o botão direito do mouse e selecione a opção “inspect”


  • Na ferramenta de inspeção que se abriu, no item selecionado, clique com o botão direito do mouse e selecione a opção “Copy”→ “Copy full xpath”



Hands-on

Agora que sabemos como chegar no nosso elemento, vamos ver o código necessário para realizar a coleta.

Primeiro, a partir do driver, precisamos construir o objeto que irá representar o browser.

from selenium import webdriver
from selenium.webdriver.chrome.service import Service

def main():
    option = webdriver.ChromeOptions()

    s = Service('/Users/geraldoferraz/Downloads/chromedriver')
    navegador = webdriver.Chrome(service=s, options=option)

    #para trabalhar dentro do docker
    #navegador = webdriver.Remote('http://selenium-chrome:4444/wd/hub', options=option)

if __name__ == '__main__':
    main()

Segundo, precisamos carregar a página desejada e utilizar o XPATH que obtivemos no passo anterior para encontrar as informações que queremos.

navegador.get("<https://fundamentus.com.br/detalhes.php?papel=MGLU3>")

try:
    xpath_valor_atual = '/html/body/div[1]/div[2]/table[1]/tbody/tr[1]/td[4]/span'        
    valor_atual = navegador.find_element(By.XPATH, xpath_valor_atual).text
    print(f'valor atual={valor_atual}')
finally:
    navegador.close()

A lib do selenium possui diversas formas de encontrar elementos na página sendo o XPATH o mais preciso:

  • By.ID
  • By.XPATH
  • By.NAME
  • By.TAG_NAME
  • By.CLASS_NAME
  • By.CSS_SELECTOR

Você deve escolher a melhor forma de acordo com a sua lógica. Para o nosso exemplo, o XPATH será suficiente.

No exemplo a seguir, vamos ver como pegar todas as informações que precisamos de um conjunto de ações:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By

def main():
    option = webdriver.ChromeOptions()
    s = Service('/Users/geraldoferraz/Downloads/chromedriver')
    navegador = webdriver.Chrome(service=s, options=option)

    #para trabalhar dentro do docker
    #navegador = webdriver.Remote('http://selenium-chrome:4444/wd/hub', options=option)

    acoes = ['MGLU3', 'LREN3', 'BBAS3', 'ITSA4']

    try:
        for acao in acoes:
            navegador.get(f"https://fundamentus.com.br/detalhes.php?papel={acao}")
            xpath_valor_atual = '/html/body/div[1]/div[2]/table[1]/tbody/tr[1]/td[4]/span'
            xpath_min = '/html/body/div[1]/div[2]/table[1]/tbody/tr[3]/td[4]/span'
            xpath_max = '/html/body/div[1]/div[2]/table[1]/tbody/tr[4]/td[4]/span'
            xpath_yield = '/html/body/div[1]/div[2]/table[3]/tbody/tr[9]/td[4]/span'
            xpath_valorizacao = '/html/body/div[1]/div[2]/table[3]/tbody/tr[2]/td[2]/span/font'

            valor_atual = navegador.find_element(By.XPATH, xpath_valor_atual).text
            min = navegador.find_element(By.XPATH, xpath_min).text
            max = navegador.find_element(By.XPATH, xpath_max).text
            _yield = navegador.find_element(By.XPATH, xpath_yield).text
            valorizacao = navegador.find_element(By.XPATH, xpath_valorizacao).text

            print(f'valor atual={valor_atual}, min={min}, max={max}, min={_yield}, min={valorizacao} ')
    finally:
        navegador.close()

if __name__ == '__main__':
    main()

A parte mais simples é gerar o CSV

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import csv

def main():
    option = webdriver.ChromeOptions()
    utilize esse formato para trabalhar localmente
    s = Service('/Users/geraldoferraz/Downloads/chromedriver')
    navegador = webdriver.Chrome(service=s, options=option)

    #para trabalhar dentro do docker
    #navegador = webdriver.Remote('http://selenium-chrome:4444/wd/hub', options=option)

    acoes = ['MGLU3', 'LREN3', 'BBAS3', 'ITSA4']

    try:
        with open('acoes.csv', 'w', newline='') as csvfile:
            cabecalho = ['valor_atual', 'min', 'max', 'yield', 'valorizacao']
            writer = csv.DictWriter(csvfile, delimiter=',', fieldnames=cabecalho)
            writer.writeheader()

            for acao in acoes:
                navegador.get(f"https://fundamentus.com.br/detalhes.php?papel={acao}")
                xpath_valor_atual = '/html/body/div[1]/div[2]/table[1]/tbody/tr[1]/td[4]/span'
                xpath_min = '/html/body/div[1]/div[2]/table[1]/tbody/tr[3]/td[4]/span'
                xpath_max = '/html/body/div[1]/div[2]/table[1]/tbody/tr[4]/td[4]/span'
                xpath_yield = '/html/body/div[1]/div[2]/table[3]/tbody/tr[9]/td[4]/span'
                xpath_valorizacao = '/html/body/div[1]/div[2]/table[3]/tbody/tr[2]/td[2]/span/font'

                valor_atual = navegador.find_element(By.XPATH, xpath_valor_atual).text
                min = navegador.find_element(By.XPATH, xpath_min).text
                max = navegador.find_element(By.XPATH, xpath_max).text
                _yield = navegador.find_element(By.XPATH, xpath_yield).text
                valorizacao = navegador.find_element(By.XPATH, xpath_valorizacao).text

                writer.writerow({'valor_atual': valor_atual, 'min': min, 'max': max, 
                'yield': _yield, 'valorizacao': valorizacao})
    finally:
        navegador.close()

if __name__ == '__main__':
    main()



Atenção!

  • Ao executar o programa, você deve ter notado que um browser se abriu. Para o desenvolvimento, pode ser interessante acompanhar visualmente tudo o que está acontecendo, assim podemos pegar alguma falha na nossa lógica, porém em um ambiente de produção, provavelmente não teremos um ambiente visual para que um browser seja aberto, então é interessante adicionar a configuração abaixo na construção do browser.
option.add_argument('headless')
  • Algumas vezes, o site pode ter algum tipo de proteção contra robôs, que podem prejudicar a performance do site, então, precisamos ficar atentos a isso, caso contrário pode ser que o nosso robô não funcione. Não é o escopo desse artigo mostrar como contornar esse cenário, mas saiba que é possível, basta realizar uma pesquisa rápida no google.
  • Para trabalhar dentro do docker, basta descomentar a linha abaixo e executar o docker-compose.yml
navegador = webdriver.Remote('http://selenium-chrome:4444/wd/hub', options=option)



Conclusão

O selenium é uma ferramenta extremamente versátil que permite automatizar diversas tarefas. A criatividade é o limite.

Github do projeto



Referências