Web Scraping: Python, Selenium e BeautifulSoup

Web Scraping: Python, Selenium e BeautifulSoup

Muita gente na internet tem dúvida de como fazer robôs que buscam coisas em sites, baixam conteúdo ou simplesmente executam ações para testar alguma funcionalidade do site, sistema ou algo relacionado. Sendo assim resolvi fazer esse Post onde eu faço o acesso a um site, analiso os elementos da página HTML e faço a automação da navegação via  Selenium. Uma vez que a automação da navegação é concluída, pego os dados de uma determinada página, faço uma conversão deles pra um formato CSV e o download desses dados para uma análise posterior, essa técnica é chamada Web Scraping.

O Framework mais famoso em Python para fazer Web Scraping é o Scrapy, porém nele temos alguns problemas para renderizar páginas que usam javascript, como por exemplo SPAs ( Single Page Applications ), essas páginas são todas renderizadas via Javascript que é carregado dinamicamente. Isso faz a navegação do site ficar mais amigável, sem a necessidade de ficar recarregando toda a página cada vez que é clicado em um link do site, como acontecia antigamente. Para contornar esse problema do Scrapy é necessário utilizar um outro módulo chamado Splash. Quem quiser saber mais sobre eles pode acessar esse link: https://github.com/scrapy-plugins/scrapy-splash .

Bom, o site que eu quero pegar os dados segue abaixo:

http://leideacesso.etransparencia.com.br/itaquaquecetuba.prefeitura.sp/Portal/desktop.html?410

Interações automatizadas com Selenium

Esse site tem os dados referente as receitas e as despesas da prefeitura da Itaquaquecetuba, ele é um site que usa javascript pra tudo, então tem uma série de páginas pra fazer o carregamento o que acaba deixando um pouco mais difícil o uso de frameworks como o Scrapy, sendo assim utilizei o Selenium para fazer a automação da navegação.

O Selenium sempre vai utilizar um navegador para acessar o site que você quer pegar os dados, para isso é necessário instalar um driver que pode ser encontrado no site oficial:

http://www.seleniumhq.org/download/

Fiz a instalação do GeckoDriver para o Windows, esse driver irá fazer toda a navegação no navegador Firefox.

Bom, agora que a instalação foi feita, vamos acessar o site e ver qual o caminho que será necessário fazer para chegar nos dados que eu quero coletar.

Acessando a página inicial:

selenium

Acima temos a página inicial e os dados que eu quero extrair estão dentro de receitas. Após clicar nesse valor será aberta uma segunda página.

No Selenium para fazer a navegação é necessário saber o nome dos elementos DOM ( Document Object Model ) que eu quero interagir, então vamos fazer uma análise.

selenium

Na imagem acima em amarelo está o botão em que eu quero automatizar uma ação de clique e precisaremo inspecionar o elemento através do navegador:

selenium

Qualquer atributo do elemento pode ser usado para interagir com ele. Eu escolhi a classe que tem o nome val01. O ideal é sempre tentar achar alguma tributo que seja único, como por exemplo o ID do elemento, mas durante o post vou mostrar como se seleciona o elemento pelo ID também.

Uma vez clicado nesse botão será carregada essa segunda página:

selenium

 

 

 

 

 

 

 

 

Nessa segunda página, temos mais elementos para interagir, como o Select, onde posso selecionar o ano de onde quero pegar os dados, assim que selecionado o ano é necessário clicar no botão filtrar.

selenium

Agora vamos pegar algum atributo para identificar esses elementos também.

Primeiro vou inspecionar o elemento Select que usaremos para selecionar o ano antes de clicar no botão.
selenium

Neste elemento, vou pegar o atributo name que possui o valor exe. Esse valor será utilizado para fazer a identificação dele via Selenium.

Agora vou inspecionar o botão filtrar.

selenium

Esse eu vou interagir com ele através do campo ID, que possui o valor imgFiltrar.

Agora que já sei como interagir, vou precisar pegar os dados da tabela abaixo:

selenium

Agora vou inspecionar a tabela:

selenium

A imagem acima mostra que ela está dentro de uma DIV com o ID bd01. Esse id que vou usar para interagir.

Um pouco de código

Até agora fizemos somente a análise do que queremos pegar, então vamos entrar em código.

Primeiro passo é instalar o módulo do Selenium para Python e o BeautifulSoup.

 
python3 -m pip install selenium bs4

Agora que já temos o módulo instalado vou colocar embaixo a parte do código que se refere ao selenium e as explicações estão nos comentários:

 
# O modulo time aqui foi utilizado para esperar o carregamento das paginas atraves do firefox
import time

# o modulo webdriver e necessario para definir qual navegador sera utilizado para fazer a automacao
from selenium import webdriver

# o modulo Select sera utilizado para interagir com a caixa de selecao onde sera definido o ano em que quero buscar os dados
from selenium.webdriver.support.ui import Select

# esse modulo sera utilizado para 
from bs4 import BeautifulSoup

# a linha abaixo define qual e o navegador que queremos utilizar, lembrando que eu instalei somente o driver para conexao com o firefox, mas existem tambem para Chrome e InternetExplorer
driver = webdriver.Firefox()

# abaixo foi definido qual e o site que quero acessar
driver.get("http://leideacesso.etransparencia.com.br/itaquaquecetuba.prefeitura.sp/Portal/desktop.html?410")

# o Sleep abaixo e para aguardar o carregamento da pagina
time.sleep(15)

# Aqui estou buscando o elemento que possui na classe o valor valo01, que e respectivo ao valor da receita onde quero clicar para ir na proxima pagina
receita = driver.find_element_by_class_name("val01")

# aqui e feito um clique no elemento que foi encontrado acima
receita.click()

# aguardando o carregamento da pagina
time.sleep(10)

# agora quero as receitas desde 2013 ate 2017
anos = ["2013","2014","2015","2016","2017"]

for a in anos:
    # aqui e utilizado o modulo Select do selenium para interagir com o ComboBox
    select = Select(driver.find_element_by_name("exe"))

    # aqui e alterado o valor do ComboBox
    select.select_by_value(a)
    # agora e buscado o elemento cujo o ID e igual a imgFiltrar
    filtrar = driver.find_element_by_id('imgFiltrar')
    # retornado o elemento da busca e clicado no botao
    filtrar.click()
    # aguardando o carregamento da tabela
    time.sleep(3)

    # armazenando a div que possui a tabela dentro da variavel dados
    dados = driver.find_element_by_id("bd10")

Se tudo estiver instalado corretamente e você executar esse script, verá que o navegador Firefox irá abrir sozinho e os botões começarão a ser clicados automaticamente através do selenium.

Com a parte da automação já feita, agora vamos fazer a raspagem dos dados.

O código abaixo está dentro do for no pedaço de código acima, no final vou postar o código completo.

 
    # aqui e pegado o codigo HTML que esta dentro da div bd01 no codigo que foi mostrado acima
    html = dados.get_attribute("innerHTML")

    # com o codigo HTML dentro da variavel, vamos usar o BeautifulSoup para fazer o parser desse HTML
    soup = BeautifulSoup(html, "html.parser")
    
    # dentro da variavel soup temos o codigo html retornado pelo Selenium ja convertido para o BS
    # vou utilizar o metodo select_one para buscar o elemento table dentro desse codigo
    table = soup.select_one("table")

    # no conteudo dessa tabela temos varias virgulas e espacos, como vou converter esses dados pra csv, vou definir o delimitador com o caracter |
    # na linha abaixo estou buscando todos os elementos tr, que possui a classe Grid_title e os elementos filhos cujo a tag e td
    # e feito um list comprehesion para pegar somente os elementos e eles estao sendo separados pelo caracter |

    headers = [header.text+"|" for header in table.select("tr.Grid_title td")]
   
    # abaixo estou buscando os elementos tr que possuem a classe Grid_line no css
    # e um novo list comprehension para criar uma lista somente com o s elementos que eu quero
    line = []
    data = [d for d in table.select("tr.Grid_line")]
    for d in data:
        linha = ""
        for t in d.select("td"):
            linha += t.text+"|"
        line.append(linha)

    # aqui e a mesma coisa que acima porem com a classe Grid_line_even
    line_even = []
    data = [ d for d in table.select("tr.Grid_line_even")]
    for d in data:
        linha = ""
        for t in d.select("td"):
            linha += t.text+"|"
        line_even.append(linha)

    # agora que os dados ja foram parseados, vou fazer a escrita do arquivo CSV
    with open("%s.csv"%a,"w") as f:
        s = "".join(headers)
        f.write(s+"\n")

        for l in line:
            f.write(l+"\n")

        for l in line_even:
            f.write(l+"\n")

# fim 
time.sleep(10)
driver.close()

Sendo assim, o código completo ficou da seguinte maneira:

 
# O modulo time aqui foi utilizado para esperar o carregamento das paginas atraves do firefox
import time

# o modulo webdriver e necessario para definir qual navegador sera utilizado para fazer a automacao
from selenium import webdriver

# o modulo Select sera utilizado para interagir com a caixa de selecao onde sera definido o ano em que quero buscar os dados
from selenium.webdriver.support.ui import Select

# esse modulo sera utilizado para 
from bs4 import BeautifulSoup

# a linha abaixo define qual e o navegador que queremos utilizar, lembrando que eu instalei somente o driver para conexao com o firefox, mas existem tambem para Chrome e InternetExplorer
driver = webdriver.Firefox()

# abaixo foi definido qual e o site que quero acessar
driver.get("http://leideacesso.etransparencia.com.br/itaquaquecetuba.prefeitura.sp/Portal/desktop.html?410")

# o Sleep abaixo e para aguardar o carregamento da pagina
time.sleep(15)

# Aqui estou buscando o elemento que possui na classe o valor valo01, que e respectivo ao valor da receita onde quero clicar para ir na proxima pagina
receita = driver.find_element_by_class_name("val01")

# aqui e feito um clique no elemento que foi encontrado acima
receita.click()

# aguardando o carregamento da pagina
time.sleep(10)

# agora quero as receitas desde 2013 ate 2017
anos = ["2013","2014","2015","2016","2017"]

for a in anos:
    # aqui e utilizado o modulo Select do selenium para interagir com o ComboBox
    select = Select(driver.find_element_by_name("exe"))

    # aqui e alterado o valor do ComboBox
    select.select_by_value(a)
    # agora e buscado o elemento cujo o ID e igual a imgFiltrar
    filtrar = driver.find_element_by_id('imgFiltrar')
    # retornado o elemento da busca e clicado no botao
    filtrar.click()
    # aguardando o carregamento da tabela
    time.sleep(3)

    # armazenando a div que possui a tabela dentro da variavel dados
    dados = driver.find_element_by_id("bd10")

    # aqui e pegado o codigo HTML que esta dentro da div bd01 no codigo que foi mostrado acima
    html = dados.get_attribute("innerHTML")

    # com o codigo HTML dentro da variavel, vamos usar o BeautifulSoup para fazer o parser desse HTML
    soup = BeautifulSoup(html, "html.parser")
    
    # dentro da variavel soup temos o codigo html retornado pelo Selenium ja convertido para o BS
    # vou utilizar o metodo select_one para buscar o elemento table dentro desse codigo
    table = soup.select_one("table")

    # no conteudo dessa tabela temos varias virgulas e espacos, como vou converter esses dados pra csv, vou definir o delimitador com o caracter |
    # na linha abaixo estou buscando todos os elementos tr, que possui a classe Grid_title e os elementos filhos cujo a tag e td
    # e feito um list comprehesion para pegar somente os elementos e eles estao sendo separados pelo caracter |

    headers = [header.text+"|" for header in table.select("tr.Grid_title td")]
   
    # abaixo estou buscando os elementos tr que possuem a classe Grid_line no css
    # e um novo list comprehension para criar uma lista somente com o s elementos que eu quero
    line = []
    data = [d for d in table.select("tr.Grid_line")]
    for d in data:
        linha = ""
        for t in d.select("td"):
            linha += t.text+"|"
        line.append(linha)

    # aqui e a mesma coisa que acima porem com a classe Grid_line_even
    line_even = []
    data = [ d for d in table.select("tr.Grid_line_even")]
    for d in data:
        linha = ""
        for t in d.select("td"):
            linha += t.text+"|"
        line_even.append(linha)

    # agora que os dados ja foram parseados, vou fazer a escrita do arquivo CSV
    with open("%s.csv"%a,"w") as f:
        s = "".join(headers)
        f.write(s+"\n")

        for l in line:
            f.write(l+"\n")

        for l in line_even:
            f.write(l+"\n")

# fim 
time.sleep(10)
driver.close()

Anterior PHP, como aprender? Por onde começar?
Próxima 4Linux lança curso preparatório para a certificação DEVOPS Master e faz parceria com a EXIN

About author

Alisson Machado
Alisson Machado 8 posts

Desenvolvedor Python e Arquiteto DevOps 8 anos de experiência em projetos FOSS (Free and Open Source Software) e Python; Certificações LPI1, LPI2 e SUSE CLA

View all posts by this author →

Você pode gostar também

Prometheus: uma nova proposta de monitoramento

Conheça o Prometheus, ferramenta open source de monitoramento adaptada ao atual modelo de TI focada em serviços e opção aos tradicionais Zabbix/Nagios.

DevOps 1Comments

4Linux presente no evento internacional DevOpsDays Maringá.

Sétima edição no Brasil e a primeira a ser realizada no estado do Paraná, o DevOpsDays Maringá reunirá especialistas para expor e debater temas do mundo DevOps. O DevOpsDays terá

Git: Adicione ciclos de vida aos seus arquivos

Git é um versionador de código fonte fácil de usar, isso quase todos sabem, entretanto sua experiência de uso pode ser bem confusa em alguns casos. Convido-os a uma breve

1 Comentário

  1. Mokhtar Ebrahim
    Fevereiro 08, 06:58 Reply

    Eu escrevi um artigo sobre Python web scraping, mas em inglês.
    Eu usei as mesmas bibliotecas. Seu artigo é ótimo.
    Muito obrigado.

Deixe uma resposta