Web scraping com Python para preguiçosos (unindo BeautifulSoup e Selenium) — Parte 2.

Explorando o Selenium

Caio Estrella
Data Hackers
8 min readJan 7, 2021

--

Este artigo é uma continuação de https://bit.ly/3hN1LOp

Uma das ferramentas mais populares para web scraping é o Selenium. O Selenium é um framework gratuito voltado à testes de aplicações web pelo browser de forma automatizada. Além do seu uso como ferramenta de testes, é bastante utilizado na coleta de dados ou na criação de rotinas para manipulação de páginas da web. Ele basicamente simula o comportamento do usuário em uma página web.

Neste artigo veremos alguns exemplos de como o Selenium pode ser utilizado neste ultimo caso. No final do artigo indicarei um projeto completo que desenvolvi utilizando os conceitos apresentados.

Aproveito e deixo aqui uma dica de curso de automação e webscraping com Python. Tem desconto ;)

Por que Selenium?

Na parte 1 vimos como coletar dados com BeautifulSoup, uma biblioteca Python para web scraping. Apesar de ser fácil de usar, nela temos alguns problemas para lidar com páginas que são renderizadas via Javascript que é carregado dinamicamente.

Já com o Selenium, os testes escritos com o WebDriver são bastante realistas, pois, em vez de usar um engine JavaScript próprio, ele chama diretamente o navegador. Isso torna possível (ou pelo menos mais fácil) preencher formulários, simular cliques em links, botões, além de coletar textos etc.

Você pode estar se perguntando: “Porque considerar o BeautifulSoup se posso fazer tudo com Selenium?”. Longos trechos de código com Selenium nem sempre funcionam da mesma maneira. Não é recomendável utilizá-lo sem necessidade. Alguns fatores justificam isso:

  • Para utilizar o Selenium muitas vezes requer coletar todos os recurso de uma página como stylesheets, scripts, imagens e etc. Isso nem sempre é necessário para executar a sua ação.
  • Uso de CPU e memória.
  • O Selenium é um pouco “frágil”, podendo trazer problemas de estabilidade.

Portanto o ideal é que usemos o Selenium para lidar com os casos onde o conteudo é adicionado à pagina via JavaScript.

Além disso, o BeautifulSoup é melhor para localizar elementos na página e coletar os dados. Suponha que você queira acessar umas divs lá no meio do HTML, sem id, nem classe nem texto. É mais comum do que eu gostaria.

Com o BS eu consigo unir diferentes lógicas e encontrar, por exemplo, a segunda e quinta divs com class = nome_da_classe que são filhas do elemento com id = id_da_tag. Esse nível de especificidade só é possivel no Selenium buscando um elemento por vez via XPATH, e eu precisaria editar o seu conteúdo para conseguir o que quero. Em páginas dinamicas é inevitável, mas não há necessidade em páginas estáticas.

WebDriver

WebDrivers são modulos executáveis que rodam no sistema com o browser a ser manipulado. O driver é específico para cada browser, como ChromeDriver para o Chrome/Chromium e o GeckoDriver para Firefox.

Geralmente utilizo o ChromeDriver que pode ser obtido aqui. Verifique a versão do seu browser e baixe o driver correto, do contrário o Selenium não funcionará.

Iniciando

Para efeito de demonstração, uma maneira de iniciar um projeto com Selenium seria essa:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
## faz com que o browser não abra durante o processo
chrome_options.add_argument("--headless")
## caminho para o seu webdriver
p = caminho_do_web_driver
driver = webdriver.Chrome(p +'\chromedriver.exe', options=chrome_options)

A opção ‘ — headless’ pode ser útil depois de você realizar alguns testes. Ao invés de abrir o browser toda vez que o código rodar, tudo vai ocorrer sem a necessidade de renderizar graficamente a página, o que economiza os custos de CPU e memória.

Localizando elementos

O Selenium oferece diversos métodos para localizar elementos em uma página. A lista completa você encontra na documentação. Repare que ele permite a seleção de um ou vários elementos de uma vez, portanto dê atenção ao plurais. Por exemplo, find_element_by_class_name retorna o primero elemento com uma classe ‘x’ passada como parâmetro, enquanto find_elements_by_class_name retorna uma lista com todos elementos com essa classe como parâmetro. A documentação é bem clara além de dar diversos exemplos, portanto prosseguirei.

Waits

Assunto geralmente confuso e com informações contraditórias. Mas acredito ter aqui uma boa explicação para cada tipo de uso.

Os elementos em uma página web levam algum tempo para serem carregados no DOM. Caso seu script tente localizar um elemento que ainda não está visível , o Selenium lança um erro de ElementNotVisibleException, por exemplo. Outros erros podem e provavelmente ocorrerão por falta de waits no script. Um dado elemento pode simplesmente não existir ou estar coberto por outro. Cada página (e cada elemento) se comporta de um jeito, então tudo pode acontecer.

Vamos aos tipos de waits:

Sleep

Este método interrompe a execução do script por um tempo específico, independente do elemento ter sido encontrado ou não.

from time import sleep...
#Faz o script parar por 2 segundos
sleep(2)
#Faz o script parar por meio segundo
sleep(0.5)

Vamos às desvantagens desta prática:

  • O script é obrigado a aguardar todo tempo do sleep passar, mesmo que o elemento seguinte já esteja disponível, aumentando o tempo geral de execução.
  • O Selenium lança um erro se o elemento não aparecer depois do sleep. Nesse caso você não saberá o quanto leva para que ele apareça, te forçando a experimentar diversas vezes até acertar o tempo do sleep que, aliás, pode variar a depender da sua conexão com o site.
  • O sleep só funciona para o próximo elemento no script. No caso de haver vários elementos que necessitam de espera, você precisa especificar um sleep para cada um deles, poluindo o código.

Portanto, sleep não é uma boa prática. Contudo, eu uso. Especialmente quando mudo ou atualizo a página. Mas vou parar. E começar aquela dieta que prometi faz 2 anos… e fazer mais exercícios também (este ainda é um post para preguiçosos?).

Implicit wait

O implicit wait manda o driver aguardar por um tempo quando tentando localizar algum elemento(s) não disponíveis imediatamente. As vantagens sobre o sleep são:

  • só é declarado uma vez e se mantém até o fim do uso do WebDriver. Ex:
from selenium import webdriver
driver = webdriver.Firefox()
driver.implicitly_wait(10)
  • O driver não precisa esperar todos os segundos imputados. O script continuará assim que o elemento for localizado 🙌. Caso não seja, após o tempo de espera o Selenium lança um erro de NoSuchElementException.

As desvantagens deste método são:

  • Performance: Você pode pensar que definir um tempo razoável (tipo 30 segundos) vai resolver seus problemas já que todos componentes existentes carregam mais rápido do que isso e o programa vai continuar de qualquer forma. Mas essa estratégia pode consumir muito tempo se você só precisa verificar a presença de um elemento e continuar testando a partir daí. Por exemplo, você pode pensar “se uma janela pop-up estiver aberta, feche-a e continue”. Neste cenário o driver vai esperar por 30 segundos só para confirmar que não existe nenhuma janela.
  • Exceptions: Passando o tempo estipulado, o implicit wait só nos retorna NoSuchElementException, e saber o que está dando errado é provavelmente a parte mais importante em um código de automação. Como vimos, nem sempre a explicação é que o elemento não existe. Claro que voce pode usar try/catch em torno da invocação do elemento para ver o que é retornado. Mas como veremos adiante, existe uma estratégia melhor.
  • Confiabilidade: Como dito anteriormente, o script continua assim que o elemento é localizado no DOM. Mas nem sempre queremos isso. Pois ao lidar com muito JavaScript o elemento pode ter sido localizado mas estar temporariamente invisível (causando ElementNotVisibleException); em movimento, não sendo possível clicar nele (causando WebDriverException: Element is not clickable at point…) ou pode ter sido desacoplado e depois reinserido no DOM (StaleElementReferenceException).
  • Condições: O implicit wait só se aplica para encontrar elementos (find_element_by_…). Mas existem diversas condições que podem se beneficiar de um período de espera. Os já citados elementos invisíveis ou não clicáveis são alguns exemplos. Caso só realizassemos uma ação SE o elemento fosse visível ou clicável, o erro não ocorreria. Apesentarei as condições mais adiante.

Portanto, implicit wait é mais prático do que o puro sleep, mas também é bastante limitado. Existe uma alternativa melhor.

Explicit wait

A grande vantagem sobre os outros métodos é poder estipular um tempo de espera até que certa condição se cumpra. Digamos “espere por 30 segundos até que o elemento X seja clicável”. Caso a condição não se cumpra no tempo estipulado o Selenium retorna um erro. Caso se cumpra, o script continua e, se quisermos, podemos realizar uma ação, como clicar no elemento (se pertencer a um link ou botão) ou preencher um campo. A lista de condições esperadas (Expected Conditions) pode ser encontrada aqui.

from selenium import webdriver 
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
#Estabelece o tempo
wait = WebDriverWait(driver, 10)
#aguarda até o elemento com certo ID ser clicável
wait.until(EC.element_to_be_clickable((By.ID, 'id_do_elemento')))

A implementação ‘By’ aceita estas estratégias de localização.

A tabela abaixo compara de forma resumida implicit e explicit waits:

tabela comparando implicit e explicit wait

Ainda existe uma quarta maneira.

Fluent wait

Define o a quantidade máxima de tempo a se se esperar por uma condição, assim como a frequencia com que se verifica essa condição.

Podemos configurar esse tipo de wait para ignorar alguns exceptions específicos enquanto aguarda a busca por um elemento na página. Ex:

wait = WebDriverWait(driver, 10, poll_frequency=1, ignored_exceptions=[ElementNotVisibleException, ElementNotSelectableException]) element = wait.until(EC.element_to_be_clickable((By.XPATH, "//div")))

Ações

Diversas ações podem ser realizadas com os elementos encontrados. São maneiras de automatizar interações como movimento do mouse, clique, pressionar tecla, interação com menus. Também é possível interações mais complexas como pairar (hover) e arrastar e soltar (drag and drop). A lista completa você encontra aqui.

Um dos exemplos mais comuns é o.click() :

## Podemos armazenar o elemento em uma variávelelement = wait.until(EC.element_to_be_clickable((By.XPATH, "//div")))element.click()## Também podemos executar a ação em cadeiawait.until(EC.element_to_be_clickable((By.XPATH, "//div"))).click()

E o BeautifulSoup?

Lembre-se: O BS não lida bem com páginas dinamicas, ou seja, aquelas que carregam sue conteudo por JavaScript. Uma forma de conciliar os dois é utilizar Selenium quando existem muitos cliques para se fazer, preencher formulários e interações para a qual é especializado. Se você mudar de URL e tiver que coletar alguns dados, é só ir acompanhando com o BS com

bs = BeautifulSoup(driver_source) onde ‘driver_source’ é a URL para onde o Selenium está apontando naquele trecho do código.

Projeto

Tenho um projeto completo para quem tiver interesse em ver todas esses conceitos funcionando em conjunto: Python, Pandas, BeautifulSoup, Selenium além de subir os dados para banco Mongo.

https://github.com/CaioEstrella/Scraping---Telelistas

Referências

https://selenium-python.readthedocs.io/index.html

--

--