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.
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.
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.
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.
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.
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
.sch
. Um objeto de classe searchdoc
.pags
. As páginas que serão acessadas. O valor padrão é 'all',
indicando que todas as páginas devem ser baixadas. Como alternativa, é
possível informar ou um vetor nomeado c(from = min, to = max)
, onde min
e max
são os extremos do intervalo de páginas que se deseja acessar, ou
ainda um vetor numérico com os índices desejados.pag_parm
, indicando o nome do parâmetro que identifica a página no site.Alguns parâmetros opcionais a serem incluídos são
method
o método para acessar a página, entre 'get' e 'post', caso este seja
diferente do método utilizado na função search_docs
.url
a url para acessar a página, caso esta seja diferente da utilizada
na função search_docs
.name_fun
uma função para nomear os arquivos salvos. Mostraremos um exemplo
em seguida.wait
tempo de espera entre cada requisição. Pode ser útil caso o site
tenha algum limitador.path
, o caminho para salvar os arquivos. Por default é o próprio diretório.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.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.