Web Scraping moderno com Python e Selenium
Publicados: 2022-03-11A raspagem da Web tem sido usada para extrair dados de sites quase desde o nascimento da World Wide Web. Nos primeiros dias, o scraping era feito principalmente em páginas estáticas – aquelas com elementos, tags e dados conhecidos.
Mais recentemente, no entanto, tecnologias avançadas em desenvolvimento web tornaram a tarefa um pouco mais difícil. Neste artigo, exploraremos como podemos fazer o scraping de dados no caso de novas tecnologias e outros fatores impedirem o scraping padrão.
Raspagem de dados tradicional
Como a maioria dos sites produz páginas destinadas à legibilidade humana, em vez de leitura automatizada, o web scraping consistia principalmente em digerir programaticamente os dados de marcação de uma página da Web (pense em clicar com o botão direito do mouse, View Source) e, em seguida, detectar padrões estáticos nesses dados que permitiriam ao programa para “ler” várias informações e salvá-las em um arquivo ou banco de dados.
Se os dados do relatório fossem encontrados, muitas vezes, os dados seriam acessíveis passando variáveis de formulário ou parâmetros com a URL. Por exemplo:
https://www.myreportdata.com?month=12&year=2004&clientid=24823
Python se tornou uma das linguagens de web scraping mais populares devido em parte às várias bibliotecas da web que foram criadas para ele. Uma biblioteca popular, Beautiful Soup, é projetada para extrair dados de arquivos HTML e XML, permitindo pesquisar, navegar e modificar tags (ou seja, a árvore de análise).
Raspagem baseada em navegador
Recentemente, tive um projeto de raspagem que parecia bastante simples e estava totalmente preparado para usar a raspagem tradicional para lidar com isso. Mas, à medida que fui avançando, encontrei obstáculos que não podiam ser superados com os métodos tradicionais.
Três problemas principais me impediram de meus métodos de raspagem padrão:
- Certificado. Havia um certificado necessário para ser instalado para acessar a parte do site onde os dados estavam. Ao acessar a página inicial, apareceu um prompt solicitando que eu selecionasse o certificado adequado daqueles instalados em meu computador e clique em OK.
- Iframes. O site usava iframes, o que atrapalhava minha raspagem normal. Sim, eu poderia tentar encontrar todos os URLs de iframe e, em seguida, criar um mapa do site, mas parecia que poderia ficar complicado.
- JavaScript. Os dados foram acessados após o preenchimento de um formulário com parâmetros (por exemplo, ID do cliente, intervalo de datas, etc.). Normalmente, eu ignoraria o formulário e simplesmente passaria as variáveis do formulário (via URL ou como variáveis de formulário ocultas) para a página de resultados e veria os resultados. Mas neste caso, o formulário continha JavaScript, o que não me permitia acessar as variáveis do formulário de maneira normal.
Então, decidi abandonar meus métodos tradicionais e procurar uma possível ferramenta para raspagem baseada em navegador. Isso funcionaria de maneira diferente do normal - em vez de ir diretamente para uma página, baixar a árvore de análise e extrair elementos de dados, eu "agiria como um humano" e usaria um navegador para chegar à página de que precisava e, em seguida, rasparia o arquivo dados - assim, contornando a necessidade de lidar com as barreiras mencionadas.
Selênio
Em geral, o Selenium é conhecido como uma estrutura de teste de código aberto para aplicativos da Web - permitindo que especialistas em controle de qualidade realizem testes automatizados, executem reproduções e implementem a funcionalidade de controle remoto (permitindo muitas instâncias de navegador para testes de carga e vários tipos de navegador). No meu caso, isso parecia que poderia ser útil.
Minha linguagem preferida para web scraping é o Python, pois possui bibliotecas bem integradas que geralmente podem lidar com todas as funcionalidades necessárias. E com certeza, existe uma biblioteca Selenium para Python. Isso me permitiria instanciar um “navegador” – Chrome, Firefox, IE, etc. – e fingir que eu mesmo estava usando o navegador para obter acesso aos dados que estava procurando. E se eu não quisesse que o navegador realmente aparecesse, eu poderia criar o navegador no modo “headless”, tornando-o invisível para qualquer usuário.
Configuração do projeto
Para começar a experimentar, precisei configurar meu projeto e obter tudo o que precisava. Eu usei uma máquina Windows 10 e me certifiquei de ter uma versão do Python relativamente atualizada (era v. 3.7.3). Criei um script Python em branco e carreguei as bibliotecas que achei que poderiam ser necessárias, usando o PIP (instalador de pacote para Python) se eu ainda não tivesse a biblioteca carregada. Estas são as principais bibliotecas com as quais comecei:
- Solicitações (para fazer solicitações HTTP)
- URLLib3 (manipulação de URL)
- Sopa Linda (caso o Selenium não aguente tudo)
- Selenium (para navegação baseada em navegador)
Também adicionei alguns parâmetros de chamada ao script (usando a biblioteca argparse) para poder brincar com vários conjuntos de dados, chamando o script da linha de comando com opções diferentes. Esses incluíam ID do cliente, de mês/ano e até mês/ano.
Problema 1 – O Certificado
A primeira escolha que eu precisava fazer era qual navegador eu diria ao Selenium para usar. Como geralmente uso o Chrome, e ele é construído no projeto Chromium de código aberto (também usado pelos navegadores Edge, Opera e Amazon Silk), imaginei tentar isso primeiro.
Consegui iniciar o Chrome no script adicionando os componentes da biblioteca de que precisava e, em seguida, emitindo alguns comandos simples:
# Load selenium components from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait, Select from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException # Establish chrome driver and go to report site URL url = "https://reportdata.mytestsite.com/transactionSearch.jsp" driver = webdriver.Chrome() driver.get(url)
Como não iniciei o navegador no modo headless, o navegador realmente apareceu e pude ver o que estava fazendo. Ele imediatamente me pediu para selecionar um certificado (que eu havia instalado anteriormente).
O primeiro problema a resolver foi o certificado. Como selecionar o adequado e aceitá-lo para entrar no site? No meu primeiro teste do script, recebi este prompt:
Isso não era bom. Eu não queria clicar manualmente no botão OK toda vez que executasse meu script.
Como se vê, consegui encontrar uma solução alternativa para isso - sem programação. Embora eu esperasse que o Chrome tivesse a capacidade de passar um nome de certificado na inicialização, esse recurso não existia. No entanto, o Chrome tem a capacidade de selecionar automaticamente um certificado se uma determinada entrada existir no registro do Windows. Você pode configurá-lo para selecionar o primeiro certificado que ele vê, ou então ser mais específico. Como eu tinha apenas um certificado carregado, usei o formato genérico.
Assim, com esse conjunto, quando eu disse ao Selenium para iniciar o Chrome e um prompt de certificado apareceu, o Chrome “Seleciona automaticamente” o certificado e continua.
Problema 2 - Iframes
Ok, então agora eu estava no site e um formulário apareceu, solicitando que eu digitasse o ID do cliente e o intervalo de datas do relatório.

Ao examinar o formulário nas ferramentas do desenvolvedor (F12), notei que o formulário foi apresentado dentro de um iframe. Então, antes que eu pudesse começar a preencher o formulário, eu precisava “mudar” para o iframe apropriado onde o formulário existia. Para fazer isso, invoquei o recurso switch-to do Selenium, assim:
# Switch to iframe where form is frame_ref = driver.find_elements_by_tag_name("iframe")[0] iframe = driver.switch_to.frame(frame_ref)
Bom, agora no quadro certo, consegui determinar os componentes, preencher o campo de ID do cliente e selecionar as listas suspensas de data:
# Find the Customer ID field and populate it element = driver.find_element_by_name("custId") element.send_keys(custId) # send a test id # Find and select the date drop-downs select = Select(driver.find_element_by_name("fromMonth")) select.select_by_visible_text(from_month) select = Select(driver.find_element_by_name("fromYear")) select.select_by_visible_text(from_year) select = Select(driver.find_element_by_name("toMonth")) select.select_by_visible_text(to_month) select = Select(driver.find_element_by_name("toYear")) select.select_by_visible_text(to_year)
Problema 3 - JavaScript
A única coisa que restava no formulário era “clicar” no botão Localizar, para iniciar a pesquisa. Isso foi um pouco complicado, pois o botão Localizar parecia ser controlado por JavaScript e não era um botão do tipo “Enviar” normal. Inspecionando-o nas ferramentas do desenvolvedor, encontrei a imagem do botão e consegui obter o XPath dele, clicando com o botão direito.
Então, armado com essas informações, encontrei o elemento na página e cliquei nele.
# Find the 'Find' button, then click it driver.find_element_by_xpath("/html/body/table/tbody/tr[2]/td[1]/table[3]/tbody/tr[2]/td[2]/input").click()
E pronto, o formulário foi enviado e os dados apareceram! Agora, eu poderia simplesmente raspar todos os dados na página de resultados e salvá-los conforme necessário. Ou eu poderia?
Obtendo os dados
Primeiro, tive que lidar com o caso em que a busca não encontrou nada. Isso foi bem direto. Ele exibiria uma mensagem no formulário de pesquisa sem sair dele, algo como "Nenhum registro encontrado". Eu simplesmente procurei por essa string e parei ali mesmo se a encontrasse.
Mas se os resultados chegassem, os dados eram apresentados em divs com um sinal de mais (+) para abrir uma transação e mostrar todos os seus detalhes. Uma transação aberta mostrava um sinal de menos (-) que, quando clicado, fechava a div. Clicar em um sinal de mais chamaria uma URL para abrir sua div e fechar qualquer uma aberta.
Assim, era necessário encontrar qualquer sinal de mais na página, reunir o URL ao lado de cada um e, em seguida, percorrer cada um para obter todos os dados de cada transação.
# Loop through transactions and count links = driver.find_elements_by_tag_name('a') link_urls = [link.get_attribute('href') for link in links] thisCount = 0 isFirst = 1 for url in link_urls: if (url.find("GetXas.do?processId") >= 0): # URL to link to transactions if isFirst == 1: # already expanded + isFirst = 0 else: driver.get(url) # collapsed +, so expand # Find closest element to URL element with correct class to get tran type tran_type=driver.find_element_by_xpath("//*[contains(@href,'/retail/transaction/results/GetXas.do?processId=-1')]/following::td[@class='txt_75b_lmnw_T1R10B1']").text # Get transaction status status = driver.find_element_by_class_name('txt_70b_lmnw_t1r10b1').text # Add to count if transaction found if (tran_type in ['Move In','Move Out','Switch']) and (status == "Complete"): thisCount += 1
No código acima, os campos que recuperei foram o tipo de transação e o status, então adicionados a uma contagem para determinar quantas transações se encaixam nas regras especificadas. No entanto, eu poderia ter recuperado outros campos nos detalhes da transação, como data e hora, subtipo etc.
Para este projeto, a contagem foi retornada para um aplicativo de chamada. No entanto, ele e outros dados raspados podem ter sido armazenados em um arquivo simples ou em um banco de dados.
Possíveis obstáculos e soluções adicionais
Inúmeros outros obstáculos podem ser apresentados ao raspar sites modernos com sua própria instância do navegador, mas a maioria pode ser resolvida. Aqui estão alguns:
Tentando encontrar algo antes que apareça
Enquanto navega por si mesmo, com que frequência você acha que está esperando que uma página apareça, às vezes por muitos segundos? Bem, o mesmo pode ocorrer ao navegar programaticamente. Você procura uma classe ou outro elemento – e não está lá!
Felizmente, o Selenium tem a capacidade de esperar até ver um determinado elemento e pode expirar se o elemento não aparecer, assim:
element = WebDriverWait(driver, 10). until(EC.presence_of_element_located((By.ID, "theFirstLabel")))
Passando por um Captcha
Alguns sites empregam Captcha ou similar para evitar robôs indesejados (que eles podem considerar você). Isso pode atrapalhar a raspagem da web e desacelerá-la.
Para prompts simples (como “o que é 2 + 3?”), eles geralmente podem ser lidos e descobertos facilmente. No entanto, para barreiras mais avançadas, existem bibliotecas que podem ajudar a tentar quebrá-lo. Alguns exemplos são 2Captcha, Death by Captcha e Bypass Captcha.
Mudanças estruturais do site
Os sites são feitos para mudar – e muitas vezes mudam. É por isso que ao escrever um script de raspagem, é melhor manter isso em mente. Você deve pensar em quais métodos usará para encontrar os dados e quais não usar. Considere técnicas de correspondência parcial, em vez de tentar corresponder uma frase inteira. Por exemplo, um site pode alterar uma mensagem de “Nenhum registro encontrado” para “Nenhum registro localizado” – mas se sua correspondência estiver em “Nenhum registro”, tudo bem. Além disso, considere se deve corresponder em XPATH, ID, nome, texto de link, tag ou nome de classe ou seletor de CSS – e qual é menos provável de mudar.
Resumo: Python e Selenium
Esta foi uma breve demonstração para mostrar que quase qualquer site pode ser raspado, não importa quais tecnologias sejam usadas e quais complexidades estejam envolvidas. Basicamente, se você puder navegar no site por conta própria, geralmente ele pode ser raspado.
Agora, como ressalva, isso não significa que todo site deva ser raspado. Alguns têm restrições legítimas em vigor, e houve vários processos judiciais decidindo a legalidade da raspagem de determinados sites. Por outro lado, alguns sites aceitam e incentivam a recuperação de dados de seu site e, em alguns casos, fornecem uma API para facilitar as coisas.
De qualquer forma, é melhor verificar os termos e condições antes de iniciar qualquer projeto. Mas se você for em frente, tenha certeza de que você pode fazer o trabalho.
Recursos recomendados para Web Scraping Complexo:
- Web Scraping avançado em Python: práticas recomendadas e soluções alternativas
- Raspagem escalável do tipo faça você mesmo: como construir e executar raspadores em larga escala