William Amorim

9 minutos de leitura

Conversando com um colega irlandês, ele me disse que os melhores web scrapers devem ser brasileiros. Isso não é uma coisa legal.

Web scraping (ou raspagem web) é a arte de se coletar dados da internet. A necessidade de se raspar uma página web surge quando baixar manualmente as informações que queremos analisar se torna uma tarefa chata, demorada ou até mesmo inexequível.

Por princípio, dados públicos deveriam ser sempre acessíveis e disponibilizados prontos para análise. No entanto, por motivação política, desconhecimento de engenharia e análise de dados ou mera falta de interesse, é muito comum termos dados públicos escondidos atrás captchas ou sistemas ineficientes. E os órgãos públicos brasileiros são especialistas nisso.

O objetivo deste post é discutir ideias gerais sobre web scraping e usá-las para construir um scraper para coletar dados de poluição do ar da CETESB.

Qualar

O Qualar é o sistema de informações de qualidade do ar da CETESB. Por meio dele, podemos acessar os dados de todas as estações de monitoramento do estado de São Paulo. O sistema exige a criação de um login e então o envio de um pequeno formulário com quais informações você gostaria de visualizar.

O Qualar funciona muito bem para pequenas consultas, mas na extração de uma massa grande de dados existem duas dificuldades:

  1. Se você precisa de dados de várias estações e de vários poluente, você precisará repetir esse processo para cada combinação de estação/poluente. Pegar todos os dados de ozônio da Grande São Paulo, por exemplo, exigiria repetir a solicitação 23 vezes.

  2. Como a planilha é impressa na tela, se você precisar de uma série muito longa, você vai demorar bastante para carregar a página e seu computador corre um grande risco de travar no meio do caminho por falta de RAM.

Para contornar este problema, vamos usar o R para construir um código que simule uma requisição de dados ao Qualar. Em seguida, vamos transformar o código numa função para replicar o processo para diversos parâmetros rodando apenas algumas linhas de códigos.

Observação: o sistema também tem uma opção “Exportar dados Avançado”. Nela, é possível escolher até 3 parâmetros para cada estação e os dados não são impressos na tela, sendo gerado diretamente um arquivo csv para download. Porém, com a desculpa de praticar a construção do scraper, não vamos usar essa opção.

Construindo o scraper

Para construir o scraper, vamos seguir os passos definidos neste post da Curso-R escrito pelo Fernando Corrêa. São eles:

  1. Definir a página que você quer raspar.
  2. Identificar exatamente as requisições que produzem o que você quer.
  3. Construir um programa que imite as requisições que você faria manualmente.
  4. Repetir o passo (3) quantas vezes quiser.

Um fluxo mais estruturado do web scraping é discutido neste post do Caio Lente.

PASSO 1

Para chegar na página que queremos raspar, precisamos passar pelas seguintes etapas dentro do Qualar: login, pesquisa e janela com os dados. Veja abaixo como prosseguir.

Login: fazer o login na página inicial.

Pesquisa: na próxima página, acessar “Consultas/Exportar Dados” no menu da esquerda e preencher a pesquisa com os dados do parâmetro que você quer acessar.

Dados: na nova janela aberta pelo site estão os dados que queremos raspar.


PASSO 2

Para descobrir qual requisição é feita em cada momento, podemos utilizar o “Inspect element” do navegador. Eu estou usando o Firefox neste post, mas o procedimento é semelhante em outros navegadores.

O login é uma submissão de formulário. Inspecionando o html da página, podemos ver que os itens que o formulário precisa enviar são o “cetesb_login” e o “cetesb_passoword”.

Para descobrir que tipo de requisição o login faz, basta abrir o Inspect element antes de fazer o login, logar no site e olhar a aba “Network”. A requisição que queremos é a “autenticador”. Ela faz uma requisição POST para a url “http://qualar.cetesb.sp.gov.br/qualar/autenticador”.

Não vou mostrar aqui, mas a requisição que a pesquisa faz para acessar os dados também é a submissão de um formulário e pode ser encontrada de forma equivalente.

Assim, para acessar os dados, precisaremos enviar outra requisição POST, agora para a url “http://qualar.cetesb.sp.gov.br/qualar/exportaDados.do”, contendo os itens desse novo formulário, que são as informações que preencheríamos na pesquisa. No próximo passo, vai ficar mais claro o que estamos fazendo nessa etapa.

PASSO 3

Vamos criar um código que imite essas requisições.

Primeiro, como o sistema Qualar exige login, precisamos capturar o cookie do site para manter a sessão logada nas requisições seguintes. O cookie da sessão é guardado no objeto my_cookie, que será passado em todas as requisições.

library(magrittr)
library(httr)

res <- GET("http://qualar.cetesb.sp.gov.br/qualar/home.do")
my_cookie <- cookies(res)$value %>% purrr::set_names(cookies(res)$name)

Agora, precisamos enviar a requisição POST para fazer o login e acessar o sistema.

  • Os parâmetros do formulário são colocados dentro do argumento body= da função POST(). Se você estiver replicando, basta substituir os valores seu_login e sua_senha pelo login e senha que você obteve ao se cadastrar no Qualar.

  • O argumento encode = "form" especifica que a requisição é uma submissão de formulário.

  • O parâmetro enviar = "OK" indica que estamos submetendo o formulário.

  • O cookie é passado usando a função set_cookies().

url <- "http://qualar.cetesb.sp.gov.br/qualar/autenticador"

res <- POST(
  url, 
  body = list(
    cetesb_login = "seu_login",
    cetesb_password = "sua_senha",
    enviar = "OK"
  ), 
  encode = "form",
  set_cookies(my_cookie)
)

Então fazemos uma requisição POST para acessar os dados. Essa requisição imita a pesquisa dentro da página “Exportar dados”. Nela, precisamos definir quais dados queremos acessar.

url <- "http://qualar.cetesb.sp.gov.br/qualar/exportaDados.do"

res <- POST(
  url,
  query = list(method = "pesquisar"),
  body = list(
    irede = "A",
    dataInicialStr  = "04/03/2018",
    dataFinalStr = "05/03/2018",
    cDadosInvalidos = "on",
    iTipoDado = "P",
    estacaoVO.nestcaMonto = "72",
    parametroVO.nparmt = "63"
  ),
  encode = "form",
  set_cookies(my_cookie)
)

Observe que, apesar de na pesquisa conseguirmos selecionar o nome da estação e do parâmetro, a requisição envia ids numéricos. No Qualar, eu encontrei apenas os ids das estações, mas os valores de ambos os parâmetros podem ser encontrados inspecionando o html da página. Para facilitar a nossa vida, eu salvei esses valores nos arquivos station_ids.csv e param_ids.csv, que podem ser baixados pelo repositório do blog no Github.

readr::read_csv("data/station_ids.csv")
## # A tibble: 62 x 3
##       id stationname                initial_date
##    <int> <chr>                      <chr>       
##  1    65 Mauá                       01/01/1998  
##  2    66 Cubatão-V.Parisi           01/01/1998  
##  3    67 Sorocaba                   01/01/1998  
##  4    73 Congonhas                  01/01/1998  
##  5    87 Cubatão-Centro             01/01/1998  
##  6    88 S.José Campos              01/01/1998  
##  7    89 Campinas-Centro            01/01/1998  
##  8    91 Cerqueira César            01/01/1998  
##  9    92 Diadema                    01/01/1998  
## 10    95 Cid.Universitária-USP-Ipen 01/01/1998  
## # ... with 52 more rows
readr::read_csv("data/param_ids.csv")
## # A tibble: 20 x 3
##       id param_abbrev param                              
##    <int> <chr>        <chr>                              
##  1    61 BEN          Benzeno                            
##  2    16 CO           Monóxido de Carbono                
##  3    23 DV           Direção do Vento                   
##  4    21 DVG          Direção do Vento Global            
##  5    19 ERT          Enxofre Reduzido Total             
##  6    59 HCNM         Hidrocarbonetos Totais menos Metano
##  7    12 MP10         Partículas Inaláveis               
##  8    57 MP2.5        Partículas Inaláveis Finas         
##  9    17 NO           Monóxido de Nitrogênio             
## 10    15 NO2          Dióxido de Nitrogênio              
## 11    18 NOx          Óxidos de Nitrogênio               
## 12    63 O3           Ozônio                             
## 13    29 PRESS        Pressão Atmosférica                
## 14    26 RADG         Radiação Solar Global              
## 15    56 RADUV        Radiação Ultra-violeta             
## 16    13 SO2          Dióxido de Enxofre                 
## 17    25 TEMP         Temperatura do Ar                  
## 18    62 TOL          Tolueno                            
## 19    28 UR           Umidade Relativa do Ar             
## 20    24 VV           Velocidade do Vento

Para finalizar, precisamos ler o resultado da nossa requisição e transformar a tabela existe no html em um data frame.

content(res) %>% 
  rvest::html_table(fill = TRUE) %>%
  extract2(2)

Passo 4

Agora, vamos transformar nosso código numa função para poder repetir o processo várias vezes para diversos parâmetros.

scraper_CETESB <- function(station, parameter, start, end, type = "P", login, password, invalidData = "on", network = "A") {
  
  
  res <- GET("http://qualar.cetesb.sp.gov.br/qualar/home.do")
  my_cookie <- cookies(res)$value %>% purrr::set_names(cookies(res)$name)
  
  url <- "http://qualar.cetesb.sp.gov.br/qualar/autenticador"
  
  res <- POST(
    url, 
    body = list(
      cetesb_login = login,
      cetesb_password = password,
      enviar = "OK"
    ), 
    encode = "form",
    set_cookies(my_cookie)
  )
  
  url <- "http://qualar.cetesb.sp.gov.br/qualar/exportaDados.do"
  
  res <- POST(
    url,
    query = list(method = "pesquisar"),
    body = list(
      irede = network,
      dataInicialStr  = start,
      dataFinalStr = end,
      cDadosInvalidos = invalidData,
      iTipoDado = type,
      estacaoVO.nestcaMonto = station,
      parametroVO.nparmt = parameter
    ),
    encode = "form",
    set_cookies(my_cookie)
  )
  
  content(res) %>% 
  rvest::html_table(fill = TRUE) %>%
  extract2(2)
  
}

Assim, basta rodar a função abaixo com o seu login e senha para obter em alguns segundos uma tabela estruturada com os dados solicitados.

scraper_CETESB(station = "72", 
               parameter = "63", 
               start = "04/03/2018", 
               end = "05/03/2018", 
               type = "P", 
               login = "seu_login", 
               password = "sua_senha", 
               invalidData = "on", 
               network = "A")

Agora só precisamos tirar colunas vazias e corrigir o nome das colunas.

dados_cetesb <- dados_cetesb %>%
  janitor::remove_empty_cols()
  
col_names <- as.character(dados_cetesb[1,])

dados_cetesb <- dados_cetesb %>% 
  magrittr::set_colnames(col_names) %>% 
  dplyr::slice(-1)

Eis um resumo dos dados que queríamos:

dados_cetesb %>% 
  dplyr::select(`Nome Estação`:`Média Horária`) %>% 
  head %>% 
  knitr::kable()
Nome Estação Nome Parâmetro Unidade de Medida Média Horária
Parque D.Pedro II O3 (Ozônio) µg/m3 20
Parque D.Pedro II O3 (Ozônio) µg/m3 19
Parque D.Pedro II O3 (Ozônio) µg/m3 18
Parque D.Pedro II O3 (Ozônio) µg/m3 12
Parque D.Pedro II O3 (Ozônio) µg/m3 11
Parque D.Pedro II O3 (Ozônio) µg/m3 12

Iterando essa função fica fácil repetir o processo para diversas estações e parâmetros.

Observações

  • Fazer um scraper exige um conhecimento não trivial sobre Web. Eu deixei vários pontos obscuros para não deixar o post muito denso e principalmente por não saber o suficiente para me aprofundar sem falar eventuais besteiras. Estou deixando várias referências para quem quiser tirar estudar o tema e fico à disposição para tirar dúvidas sobre o código.

  • Um scraper sempre será subordinado à estrutura do site. Se a CETESB mudar alguma coisa na estrutura do Qualar, esse código pode não funcionar mais e precisará ser adaptado ou até mesmo construído do zero.

  • Quando você estiver raspando dados da web, tome cuidado para não bombardear o servidor da página com requisições. Você pode acabar tendo o IP bloqueado ou derrubando o site.

  • Antes de fazer raspagem de sites particulares, leia sempre os Termos de uso para evitar problemas legais. A empresa ou responsável pelo site pode proibir a automatização de requisições.

Agradecimentos

Agradecimento especial ao excelente Daniel Falbel pela ajuda com o código e com web scraping.

comments powered by Disqus