setwd('~/Projects/crawlr/')
library(crawlr)

Este documento contém algumas melhores práticas na contrução de pacotes no R que baixam e processos informações de sites disponíveis na web. O objetivo é ajudar o desenvolvedor a produzir um pacote que seja fácil de adaptar no tempo.

O documento foi construído com base na experiência em web crawlers simples, contruídos para acessar listas de páginas pré-definidas. Isto é muito diferente de web crawlers não supervisionados, como o do Google, que vão passeando pelas páginas de forma indefinida. Por conta disso, o nome crawler poderia até ser um pouco inadequado, mas estamos mantendo por falta de um nome melhor para definir essa tarefa.

Também é importante mencionar que só estamos trabalhando com páginas que são acessíveis publicamente. Caso tenha interesse e "crawlear" páginas que precisam de autenticação, recomendamos que estude os termos de uso do site.

Para ilustrar este texto, usaremos como exemplo o pacote tjsp, que acessa o site do Tribunal de Justiça de São Paulo para obter informações de processos judiciais. Trataremos de só uma parte das páginas acessadas pelo pacote, que é a consulta de primeiro grau.

Informações iniciais

Antes de tudo, verifique se existe alguma forma mais fácil de conseguir os dados que necessita. Construir um web crawler do zero é muitas vezes uma tarefa dolorosa e, caso o site seja atualizado, pode ser que boa parte do trabalho seja inútil. Se os dados precisarem ser extraídos apenas uma vez, verifique com o pessoal que mantém o site se eles não podem fazer a extração que precisa. Se os dados precisarem ser atualizados, verifique se a empresa não possui uma API para acesso aos dados.

Ao escrever um web crawler, as primeiras coisas que devemos pensar são

Sugerimos como melhores práticas dividir todas as atividades em três tarefas principais: i) buscar; ii) coletar e iii) processar. Existem casos em que a etapa de busca é desnecessária (por exemplo, se já sabemos de antemão quais são as URLs que vamos acessar).

Em certas situações, deixar os algoritmos de download e parsing dos dados em funções distintas isso pode tornar o código mais ineficiente, e os arquivos obtidos podem ficar pesados, mas isso geralmente aumenta o controle sobre o que as ferramentas estão fazendo, facilita o debug e a edição.

Diferença entre procurar, baixar e processar.

Procurar documentos significa, de uma forma geral, utilizar ferramentas de busca (ou acessar links de um site) para obter informações de uma nova requisição a ser realizada. Ou seja, essa etapa do crawler serve para "procurar links" que não sabíamos que existiam previamente. Isso será resolvido através da função look_for.

Baixar documentos, no entando, significa simplesmente acessar páginas pré-estabelecidas e salvá-las em disco. Em algumas situações, os documentos baixados (depois de limpos) podem conter uma nova lista de páginas a serem baixadas, formando iterações de coletas. A tarefa de baixar documentos pré-estabelecidos será realizada pela função collect.

Finalmente, processar documentos significa carregar dados acessíveis (ou seja, em disco) e transformar os dados brutos em dados trabalháveis analiticamente. Não existe um limite para a profundidade dessa estruturação de dados. Geralmente, no entanto, separamos a etapa de estruturação em duas atividades: i) transformar arquivos não-estruturados em um arquivos semi-estruturados (e.g. um arquivo HTML em uma tabela mais um conjunto de textos livres) e ii) transformar arquivos semi-estruturados em uma base analítica (estruturada). A tarefa de processar as páginas ainda está numa fase inicial, e será realizada pela função scrape.

Como veremos no decorrer do documento, no caso do TJSP, teremos um fluxo "look for" -> "collect" -> "scrape" -> "collect" -> "scrape" para conseguir nossos dados.

Procurar documentos

A tarefa de listar os documentos que queremos obter geralmente pode ser realizada de duas formas: i) utilizar uma ferramenta de busca do site e ii) acessar as páginas a partir do resultado de uma pesquisa. Dependendo do caso, será necessário realizar:

No exemplo de ilustração, nosso caso é de uma busca e muitas paginações. Acesse a página do e-SAJ e clique em "Consultar" para ter uma ideia de como é essa página. A página (acessada no dia r Sys.Date()) é uma ferramenta de busca com vários campos, que permite pesquisa com dados em branco. Na parte de baixo o site mostra uma série de documentos, organizados em páginas de dez em dez resultados.

Para resolver o problema, precisaremos de duas funções principais, uma que faz a busca e outra que acessa uma página específica (que será repetida várias vezes). Utilizaremos as funções look_for e paginate para cada um desses problemas.

Search docs

cjpg_url <- function() {
  u <- 'https://esaj.tjsp.jus.br/cjpg/pesquisar.do'
  u
}

A função search_docs precisa ser capaz de realizar uma pesquisa e retornar a resposta do servidoe que contém a primeira página dos resultados. Para isso, ela recebe uma lista com dados da busca (do formulário) a url base e um método para realizar a requisição, podendo ser 'get' ou 'post'. Caso a pesquisa seja mais complicada, é possível adicionar também uma função que sobrepõe a busca padrão.

Futuro: A função também realizará algumas tarefas conhecidas de forma automática. Primeiramente acessa a página inicial, verifica se ela contém certos tipos de tags ocultas, como '__VIEWSTATE' para páginas em aspx ou 'javax.server.faces' para páginas em java faces e adicionará esses parâmetros automaticamente na requisição, quando esta for do tipo 'POST'.

No nosso caso, a requisição é um simples 'GET', mas com muitos parâmetros. Assim, o nosso pacote, dentro do esquema do pacotes crawlr, ficaria algo como:

cjpg_search_data <- list(
  'dadosConsulta.nuProcesso' = '',
  'dadosConsulta.pesquisaLivre' = 'danos morais',
  'dadosConsulta.dtInicio' = '01/10/2014',
  'dadosConsulta.dtFim' = '01/11/2014'
)

cjpg_search_result <- cjpg_search_data %>% 
  search_docs(url = cjpg_url(), method = 'get')

cjpg_search_result

Também é possível incluir os parâmetros diretamente na função search_docs

cjpg_search_result <- search_docs(
  url = cjpg_url(), 
  'dadosConsulta.nuProcesso' = '',
  'dadosConsulta.pesquisaLivre' = 'danos morais',
  'dadosConsulta.dtInicio' = '01/10/2014',
  'dadosConsulta.dtFim' = '01/11/2014',
  method = 'get'
)

No RStudio, é possível visualizar a página baixada com a função visualize. O documento aparecerá na

visualize(cjpg_search_result)

OBS: A imagem fica "feia" pois está sem a folha de estilos e as imagens.

Em alguns casos ser uma boa prática criar funções que facilitam a entrada de parâmetros de busca. No nosso exemplo, existem parâmetros necessários na requisição que não precisam ser preenchidos, e parâmetros que precisam ser preenchidos de uma maneira específica, como as datas, que precisam ser inseridas no formato '%d/%m/%Y'. Assim, incluimos uma função de "ajuda".

cjpg_parms <- function(livre = '', classes = '', assuntos = '',
                        data_inicial = '', data_final = '', varas = '') {
  classes = paste0(classes, collapse = ',')
  assuntos = paste0(assuntos, collapse = ',')
  varas = paste0(varas, collapse = ',')
  if(data_inicial != '' & data_final != '') {
    # aqui eu uso o pacote lubridate para construir a data no formato
    # que o e-SAJ exige.
    cod_data_inicial <- paste(lubridate::day(data_inicial),
                              lubridate::month(data_inicial),
                              lubridate::year(data_inicial) ,
                              sep = '/')
    cod_data_final <- paste(lubridate::day(data_final),
                            lubridate::month(data_final),
                            lubridate::year(data_final) ,
                            sep = '/')
  }
  parms <- list('dadosConsulta.nuProcesso' = '',
                'dadosConsulta.pesquisaLivre' = livre,
                'classeTreeSelection.values' = classes,
                'assuntoTreeSelection.values' = assuntos,
                'varasTreeSelection.values' = varas,
                'dadosConsulta.dtInicio' = cod_data_inicial,
                'dadosConsulta.dtFim' = cod_data_final)
  parms
}

Dessa forma, a chamada ficaria um pouco mais padronizada.

cjpg_search_data <- cjpg_parms(livre = 'danos morais', 
                               data_inicial = '2014-10-01', 
                               data_final = '2014-11-01')

# por default method = "get"
cjpg_search_result <-  cjpg_search_data %>% search_docs(cjpg_url())

É possível utilizr a função cjpg_parms com dados incluídos diretamente na função através do parâmetro parm_fun:

cjpg_search_result <- search_docs(url = cjpg_url(),
                                  livre = 'danos morais', 
                                  data_inicial = '2014-10-01', 
                                  data_final = '2014-11-01',
                                  parm_fun = cjpg_parms)

Por fim, é uma boa prática criar uma função que extrai o número de páginas a serem acessadas pela paginação. Geralmente esse número existe pois as ferramentas de busca usualmente mostram o número de resultados.

cjpg_npag <- function(r) {
  val <- xml2::read_html(httr::content(r, 'text')) %>%
    xml2::xml_find_all(".//*[@id = 'resultados']//td") %>%
    `[[`(1) %>%
    xml2::xml_text() %>%
    stringr::str_trim() %>%
    stringr::str_match('de ([0-9]+)')
  num <- as.numeric(val[1, 2])
  num
}

cjpg_npag(cjpg_search_result$result)

Podemos adicionar essa função como parâmetro npag_fun de nossa função search_docs.

cjpg_search_result <- search_docs(url = cjpg_url(),
                                  livre = 'danos morais', 
                                  data_inicial = '2014-10-01', 
                                  data_final = '2014-11-01',
                                  parm_fun = cjpg_parms,
                                  npag_fun = cjpg_npag)

O objeto retornado pela função search_docs é um objeto do tipo searchdoc, que guarda, além da resposta da requisição web, a url base utilizada, a lista com os parâmetros, e o número de páginas, estes últimos somente se os parâmetros parm_fun e npag_fun forem informados.

Paginate

Após conseguir os resultados pela ferramenta de busca e acessar os resultados, o próximo desafio é baixar as páginas dos resultados. Realizar a paginação nada mais é do que repetir a tarefa de acessar uma página diversas vezes, mudando somente o parâmetro da página.

Algumas vezes, é possível que a URL base para acessar a paginação seja diferente da ferramenta de busca. Esse é o caso do nosso exemplo. Nesses casos, podemos criar uma nova função para guardar essa URL.

cjpg_url_pag <- function() {
  u <- 'https://esaj.tjsp.jus.br/cjpg/trocarDePagina.do'
  u
}

A função paginate recebe como parâmetros

Alguns parâmetros opcionais a serem incluídos são

Utilização básica de paginate

cjpg_search_result %>% 
  paginate(pags = 1:30, parm_pag= 'pagina', 
           url = cjpg_url_pag(), path = 'data-raw/cjpg')
dir('data-raw/cjpg')

No exemplo, fizemos o download de 30 páginas a partir do resultado da pesquisa. Os arquivos salvos são arquivos .rds que guardam os resultados das requisições. Eles devem ser lidos com a função readRDS.

Com isso, conseguimos baixar as páginas que listam os itens que queremos baixar. Em muitos casos, esse

Futuro: Infelizmente, alguns sites não permitem a inclusão do número de uma página como parâmetro para realizar a paginação. Muitas vezes esses sites possuem somente um botão "Next". No futuro vamos adaptar a função paginate para esses casos.

Onde guardar os dados? Ao construir um pacote que utiliza o pacote crawlr, pode fazer sentido guardar os dados baixados dentro do próprio pacote, para reprodutibilidade. Nesse caso, o melhor lugar para guardar esses dados é na pasta data-raw, como sugerido por Hadley Wickham no livro r-pkgs. No entanto, Se os dados forem muito volumosos, colocá-los dentro do pacote pode ser ruim para colocar no GitHub e no CRAN. Por isso, pode ser necessário colocar esses documentos numa pasta externa ao pacote. Para garantir a reprodutibilidade, recomendo que criem um pacote no R cujo objetivo é guardar somente esses dados, e coloque esse pacote em um repositório na nuvem (Dropbox, por exemplo). No pacote que contém as funções de extração, guarde os dados já processados (se couberem) num arquivo .rda dentro da pasta data do pacote.

Processar

TODO



jtrecenti/crawlr documentation built on May 20, 2019, 3:17 a.m.