Requisições em APIs no R

No post passado, eu falei sobre segurança de redes e inteligência artificial usando o Pwnagotchi. Se você não leu e se interessa por esses temas, recomendo que volte ao post para entender melhor sobre o que tratarei hoje. Se você só quiser saber sobre APIs no R, é só continuar lendo :).

Bom, para começar, o que é uma API?

Segundo o Wikipedia,

Interface de Programação de Aplicações (pt) ou Interface de Programação de Aplicação (pt-BR)), cujo acrônimo API provém do Inglês Application Programming Interface, é um conjunto de rotinas e padrões estabelecidos por um software para a utilização das suas funcionalidades por aplicativos que não pretendem envolver-se em detalhes da implementação do software, mas apenas usar seus serviços.

De modo geral, a API é composta por uma série de funções acessíveis somente por programação, e que permitem utilizar características do software menos evidentes ao utilizador tradicional.

Em outras palavras, uma API é uma espécie de padrão estabelecido em um sistema, site ou aplicativo, pelo desenvolvedor, que permite o acesso facilitado a (pré-)determinadas funções. Por exemplo, algo muito utilizado na Ciência Política, que é minha área principal, são dados da Câmara dos Deputados. A Câmara dos Deputados possui uma API, que pode ser vista aqui e que será assunto de um post futuramente. Essa API permite a visualização (e por sua vez, o processamento) de dados sobre parlamentares, incluindo gastos de gabinete, discursos, eventos, frentes parlamentares, partidos, proposições e tramitação de matérias.

Se eu quisesse desenvolver um aplicativo que acompanhasse os gastos dos deputados, então, eu não precisaria construir um robô que raspa os dados do site da Câmara, os processa, e gera visualizações: eu poderia simplesmente fazer solicitações à API e teria esses dados disponibilizados e consolidados!

Então, como fazer isso no R? Para nosso exemplo, estarei usando a API do Pwnagotchi, a Pwngrid, que é como se fosse um ranking com todos os usuários. Ou seja, como dito no post anterior, é um sistema que funciona como um PokemonGo para redes WiFi. Na Pwngrid, que pode ser vista parcialmente aqui, temos todos os usuários com diversos dados sobre as suas últimas sessões: tempo de duração, quantos WiFi foram capturados, quantos handshakes, de quais países são, e por quantos epochs[^1] seu algoritmo treinou.

No caso de APIs de sites, geralmente temos uma URL para fazer a solicitação. Se quiséssemos transferir algum dado pelo terminal do sistema (ou seja, fora do R), utilizaríamos o cURL, que permite fazer esse tipo de requisição. Dentro do R, entretanto, utilizamos duas bibliotecas: o httr e o jsonlite. O primeiro, lida com as requisições, e o segundo nos permite tratar os dados (que vem em .json), permitindo transformar em um dataframe legível e inteligível pelo R.

No caso da Pwngrid, cuja API está documentada aqui, o layout do endereço é:

https://api.pwnagotchi.ai/api/v1/FUNCAO

Sendo /FUNCAO/ a função/dado desejado estabelecido na API. Logo, se eu quisesse saber quantas unidades do pwnagotchi existem por país, eu utilizaria a seguinte URL:

https://api.pwnagotchi.ai/api/v1/units/by_country

Se abrirmos essa URL no navegador do computador, teremos, no momento, o seguinte output:

[{"country":"US","units":1205},{"country":"DE","units":394},{"country":"FR","units":215},
{"country":"GB","units":208},{"country":"IT","units":193},{"country":"CA","units":157},
{"country":"NL","units":110},{"country":"AU","units":99},{"country":"ES","units":74},
{"country":"PL","units":55},{"country":"CH","units":53},{"country":"AT","units":47},
{"country":"RU","units":39},{"country":"SE","units":35},{"country":"BE","units":35},
{"country":"PT","units":31},{"country":"SG","units":26},{"country":"IE","units":25},
{"country":"BR","units":24},{"country":"CZ","units":23},{"country":"DK","units":22},
{"country":"ZA","units":21},{"country":"MX","units":20},{"country":"AR","units":19},
{"country":"TR","units":16},{"country":"JP","units":15},{"country":"SK","units":14},
{"country":"VN","units":13},{"country":"FI","units":12},{"country":"SI","units":11},
{"country":"BN","units":10},{"country":"NZ","units":10},{"country":"PH","units":10},
{"country":"MY","units":10},{"country":"IL","units":9},{"country":"IN","units":9},
{"country":"RO","units":9},{"country":"BG","units":9},{"country":"NO","units":8},
{"country":"CN","units":8},{"country":"GR","units":8},{"country":"HK","units":7},
{"country":"HU","units":7},{"country":"CY","units":7},{"country":"BY","units":6},
{"country":"LU","units":6},{"country":"LT","units":5},{"country":"CL","units":5},
{"country":"CO","units":5},{"country":"UA","units":5},{"country":"UY","units":4},
{"country":"AE","units":4},{"country":"DZ","units":4},{"country":"EE","units":4},
{"country":"IS","units":4},{"country":"TW","units":4},{"country":"EC","units":3},
{"country":"LV","units":3},{"country":"TH","units":3},{"country":"HR","units":2},
{"country":"LB","units":2},{"country":"AF","units":2},{"country":"SV","units":2},
{"country":"JO","units":2},{"country":"CR","units":2},{"country":"SA","units":1},
{"country":"KW","units":1},{"country":"XX","units":1},{"country":"PA","units":1},
{"country":"PK","units":1},{"country":"MA","units":1},{"country":"MK","units":1},
{"country":"BA","units":1},{"country":"QA","units":1},{"country":"NI","units":1},
{"country":"BL","units":1},{"country":"KR","units":1}]

Esse bloco de código acima é uma lista com duas chaves/variáveis, onde cada observação está dentro de “{ }”, e as variáveis estão seapradas por vírgula, sendo elas country e units: o país e o número de unidades pwnagotchi em cada um deles. Temos, no caso do Brasil (BR), o seguinte:

{"country":"BR","units":24}

Isso indica que no país BR temos 24 pwnagotchis.

Ok! Entendi! Mas como fazer isso no R, gerando bancos de dados prontos para serem utilizados?

No R, ao carregarmos as bibliotecas necessárias (como sempre, o tidyverse incluído), definimos a URL para qual a requisição será feita, processamos essa URL com base no GET (o tipo de requisição, que pode ser GET ou POST), extraímos o conteúdo, convertemos em JSON e, dali, adaptamos a um banco. A teoria parece mais complicada que a prática, então vamos botar a mão na massa para entender melhor:

pacman::p_load(tidyverse, httr, jsonlite, hrbrthemes, ggthemes)

# Definimos a URL
url_paises <- "https://api.pwnagotchi.ai/api/v1/units/by_country" 

# Solicitamos a mesma
GET_paises <- GET(url_paises)

# Extraímos o conteúdo
content_paises <- content(GET_paises, "text")
## No encoding supplied: defaulting to UTF-8.
# Convertemos de JSON
paises_df <- fromJSON(content_paises)

Agora, temos um objeto chamado paises_df que pode ser inspecionado, sumarizado e trabalhado como um banco qualquer no R. Se lembrarmos bem, no output dado pelo navegador, tínhamos duas variáveis: country e units. Será que as mesmas são encontradas no banco gerado?

glimpse(paises_df)
## Rows: 99
## Columns: 2
## $ country <chr> "US", "DE", "GB", "FR", "IT", "CA", "NL", "AU", "ES", "RU", "P…
## $ units   <int> 3727, 1287, 720, 519, 498, 433, 385, 342, 238, 216, 191, 167, …

Para mostrar que o objeto pode ser trabalhado como qualquer outro objeto nativo, façamos um gráfico dos 10 países com maior número de unidades:

paises_df %>% 
  top_n(10, units) %>% 
  ggplot(aes(x = reorder(country, units), y = units, fill = country))+
  geom_col(show.legend = F)+
  labs(x = "País", y = "Nº de unidades", 
       title = "PwnGrid", subtitle = "Número de unidades por país")+
  coord_flip()+
  theme_ipsum_tw()+
  scale_fill_gdocs()

Percebe-se que os Estados Unidos são o país que mais possuem unidades registradas!

Ok! Entendi como se faz requisição de API no R. Mas e se a API tiver muitas páginas?

Esse exemplo é visto também na Pwngrid. Se observarmos a documentação, vemos que se quisermos todas as unidades, temos um novo argumento na URL, que é ?p=2, no caso da segunda página. Como baixar todas as páginas? Como descobrir quais são todas as páginas?

Bom, a solução é simples. O R (e toda linguagem de programação) possui um recurso chamado for, que permite faze loops, ou seja, trabalhar recursivamente até determinado ponto. A sintaxe de um for funciona da seguinte maneira:

for(i in n){
  faça_algo(baseado_em = i)
}

Se quiséssemos printar algo de maneira recursiva no R, faríamos da seguinte forma:

elec_pres <- seq(1994, 2018, 4)
for(ano in elec_pres){
  print(paste("Em", ano, "houve uma eleição presidencial no Brasil"))
}
## [1] "Em 1994 houve uma eleição presidencial no Brasil"
## [1] "Em 1998 houve uma eleição presidencial no Brasil"
## [1] "Em 2002 houve uma eleição presidencial no Brasil"
## [1] "Em 2006 houve uma eleição presidencial no Brasil"
## [1] "Em 2010 houve uma eleição presidencial no Brasil"
## [1] "Em 2014 houve uma eleição presidencial no Brasil"
## [1] "Em 2018 houve uma eleição presidencial no Brasil"

O for acima fez um loop que repetiu a mesma frase para cada item do vetor elec_pres, que eu denominei ano. Entenderam?

O princípio de baixar muitas páginas de uma só vez em uma API é o mesmo. Abaixo, minha saída para tal problema:

baixa_pwngrid <- function(z = npages) {
  pages <- list()
  base <- "https://api.pwnagotchi.ai/api/v1/units"
  pagesfun <- function(x = 1) {
    if (x == 1) {
      return()
    }
    else{
      paste0("?p=", x)
    }
  }
  unit1 <- fromJSON(content(GET(paste0(base, pagesfun(1))),
                            "text"),
                    flatten = T)
  npages <- unit1$pages
  for(i in 0:z){
    json <- fromJSON(paste0(base, pagesfun(i)))
    message("Requisitando página ", i)
    pages[[i+1]] <- json$units
    units_df <- rbind_pages(pages)
  }
  return(units_df)
}

Explicando o comando acima: como eu frequentemente solicito esses dados da Pwngrid à API, desenvolvi uma função, chamada baixa_pwngrid() que faz o seguinte:

  • Cria um objeto denominado pages, vazio, determinando que é uma lista;
  • Cria um objeto chamado base, que contém a URL que será requisitada;
  • Cria uma subfunção chamada pagesfun, que itera as páginas com o argumento extra, ?p= para cada página;
  • Baixa, extrai e converte para um objeto denominado unit1 a primeira página. Baixar a primeira página é importante pois, nesse caso especificamente, ela contém uma variável que determina quantas páginas são encontradas no total;
  • Cria o objeto npages, que possui como valor o total de páginas, visto em unit1;
  • Crio um loop para cada página presente em npages, printando no terminal em qual página estou, e direcionando tudo para um objeto, chamado units_df. Todas as páginas são unidas pela função rbind_pages, que serve para isso.

Para fins de teste, vamos inspecionar o banco e criar um gráfico com as unidades ativas do EUA que possuem pelo menos uma rede capturada:

# units_df <- baixa_pwngrid() 
glimpse(units_df)
## Rows: 525
## Columns: 8
## $ enrolled_at <chr> "2021-03-02T23:09:13Z", "2021-03-02T22:17:15Z", "2021-03-0…
## $ updated_at  <chr> "2021-03-02T23:09:13Z", "2021-03-02T23:19:20Z", "2021-03-0…
## $ country     <chr> "US", "US", "CH", "RU", "CN", "DE", "ZA", "US", "MX", "GB"…
## $ name        <chr> "pwnacartman", "Joshua", "pwnagotchi", "faradeika", "Shire…
## $ fingerprint <chr> "5fd38e1fb8af1cbe1fbf6242b7d6907b9fe744de9e53484949bf90afe…
## $ public_key  <chr> "-----BEGIN RSA PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFA…
## $ data        <df[,5]> <data.frame[26 x 5]>
## $ networks    <int> 0, 0, 0, 233, 0, 0, 23, 0, 0, 0, 31, 48, 0, 0, 0, 1, 0,…
units_df %>%
  filter(country == "US" & networks > 0) %>%
  arrange(-networks) %>% 
  head(10) %>% 
  group_by(name) %>% 
  summarise(networks = sum(networks)) %>% 
  ggplot(aes(x = reorder(name, networks), y = networks, fill = name))+
  geom_col(show.legend = F)+
  labs(x = "Nome da unidade", y = "Nº de redes", 
       title = "PwnGrid US", subtitle = "Número de redes capturadas por cada unidade")+
  coord_flip()+
  theme_ipsum_tw()+
  scale_fill_gdocs()

Legal, não? Toda e qualquer API que utilize esse formato pode ser buscada, tratada e processada no R, podendo então ser inserida em dashboards, shinyApps e muitos outros: as possibilidades são praticamente infinitas.

Bom, por hoje é só! Qualquer dúvida, correção ou sugestão pode ser enviada para o meu email

[^1] Epochs são o tempo de exato um ciclo para o treinamento de um algoritmo. É uma medida extremamente variável. Em tese, quanto mais epochs são utilizados no treinamento, melhor fica o nosso algoritmo.

Mateus Cavalcanti Pestana
Mateus Cavalcanti Pestana
Doutorando e Mestre em Ciência Política

Interessado em ciência de dados, ciência política, política russa, impressão 3D, redes neurais e aprendizado de máquina.

Relacionados