packages <- c("httr", "dplyr", "tidyr", "rlist", "stringr")
# install.packages(packages)
library(httr)
library(dplyr)
library(tidyr)
library(rlist)
library(stringr)
Давайте разберемся что такое API c помощью картинки.
Хотелось бы попросить сервер, в данном случае Vk, получить доступ к их базе данных, чтобы не мучиться с парсингом страницы. Очевидно, что прямой доступ до базы данных никто нам не даст. Для этого и был создан API. Это некоторая “прослойка”, которая позволяет получать данные из баз.
Первое, что нужно сделать перед парсингом, это проверить, есть ли API, которое облегчит вашу жизнь. У больших компаний есть API, например это Vk или Twitter. Важно понимать, что API это не только получение каких-то данных из баз сервера. Это может быть все что угодно. Например, с помощью API Яндекс.Переводчик вы можете перевести текст с одного языка на другой.
Работа с разными API очень схожи, везде есть токены, методы и тому подобное. Поэтому давайте разберём все на одном примере – VkApi.
У Vk есть свое API. Вот его документация.
Первое что нужно сделать для работы с любым API это получить токен. Токен это ваш аудентификатор. Грубо говоря, это ваш логин и пароль. Токен нужен, чтобы API понимал кто вы и какие данные вам можно показывать, а какие нельзя. Допустим, есть человек, у которого закрыт аккаунт. Если вы друг этого человека, то вам доступна информация на его странице, а если у вас нет в друзьях этого человека, то у вас нет доступа к информации.
ВАЖНО! ЕСЛИ У КОГО-ТО ЕСТЬ ВАШ ТОКЕН, ТО ОН ИМЕЕТ ПОЛНЫЙ ДОСТУП К ВАШЕЙ СТРАНИЦЕ. НИКОМУ НЕ ДАВАЙТЕ СВОЙ ТОКЕН.
Переходим по ссылке и создаем свое приложение.
В настройках вашего приложения есть его id.
В ней прописываем id вашего приложения, а также те доступы, которые будут разрешены с полученным токеном. В нашем случае мы прописываем scope=friends
, тем самым разрешая получать информацию о друзьях. Вы можете добавить доступы к другим вещам. Смотрите подробнее 4 пункт по ссылке.
id <- "7657737"
path <- paste("https://oauth.vk.com/authorize?client_id=", id, "&display=page&redirect_uri=https://oauth.vk.com/blank.html&scope=friends&response_type=token&v=5.52", sep = "")
cat(path)
https://oauth.vk.com/authorize?client_id=7657737&display=page&redirect_uri=https://oauth.vk.com/blank.html&scope=friends&response_type=token&v=5.52
Полученную ссылку вводим в браузере. Соглашаемся дать доступ (там будет как раз описано, к чему вы даете доступ). Копируем ссылку, которая получилась в итоге.
"https://oauth.vk.com/blank.html#access_token=388bafec47318fdbbb2bda6ef769adb8df50fda7a0a65e7d99caf4577bac6cedc0c67c16e99e0d605e186&expires_in=86400&user_id=73614108"
## [1] "https://oauth.vk.com/blank.html#access_token=388bafec47318fdbbb2bda6ef769adb8df50fda7a0a65e7d99caf4577bac6cedc0c67c16e99e0d605e186&expires_in=86400&user_id=73614108"
Здесь нужно обратить внимание на следующие вещи:
access_token
– это ваш токенexpires_in
– время жизни токена (по дефолту 86400 секунд = 1 сутки)user_id
– id человека, которому предоставлен доступЗапишем токен в отдельную переменную.
token <- "388bafec47318fdbbb2bda6ef769adb8df50fda7a0a65e7d99caf4577bac6cedc0c67c16e99e0d605e186"
Что можно делать с помощью API? Весь функционал реализован в методах. Их много, вы можете получить информацию о друзьях, посмотреть кто лайкнул пост, отправить кому-то сообщение и многое другое.
Здесь нужно вспомнить, что такое HTTP запросы и метод GET, с помощью которого мы будем получать нужные нам данные.
Давайте посмотрим как формируется url, по которому мы и сделаем запрос.
url <- "https://api.vk.com/method/" + название метода + "?" + параметры метода, разделенные &
Давайте попробуем использовать метод friends.get
[ссылка] , который возращает список друзей заданного пользователя. Если не указать пользователя (параметр user_id
), то он выведет ваших друзей. В параметрах нужно указать версию API (5.52 актуальная), ваш токен и какие-то еще параметры из описания метода.
method <- "friends.get"
version <- "5.52"
fields <- "first_name,last_name"
token <- "388bafec47318fdbbb2bda6ef769adb8df50fda7a0a65e7d99caf4577bac6cedc0c67c16e99e0d605e186"
url <- paste("https://api.vk.com/method/", method,
"?v=", version, "&access_token=", token,
"&fields=", fields,
sep = "")
cat(url)
https://api.vk.com/method/friends.get?v=5.52&access_token=388bafec47318fdbbb2bda6ef769adb8df50fda7a0a65e7d99caf4577bac6cedc0c67c16e99e0d605e186&fields=first_name,last_name
Полученную ссылку можно вставить в брузере. На выходе получим JSON с информацией о друзьях. Давайте получим эту информацию внутри R с помощью метода GET
.
response <- GET(url = url)
con <- content(response)
print(class(con))
## [1] "list"
В итоге мы получили list
с которым можем работать. Давайте посмотрим его структуру. Она очень большая, поэтому давайте введем максимальную глубину и количество первых объектов, которые отображаются.
str(con, max.level = 3, list.len = 2)
## List of 1
## $ response:List of 2
## ..$ count: int 515
## ..$ items:List of 515
## .. ..$ :List of 4
## .. ..$ :List of 4
## .. .. [list output truncated]
Видим, что у меня 515 друзей, и в items
лежит информация о каждом из них. Давайте попробуем посмотреть на какого-то друга.
con$response$items[[89]]
## $first_name
## [1] "Максим"
##
## $id
## [1] 50471454
##
## $last_name
## [1] "Мидюкин"
##
## $track_code
## [1] "434b09d1QUMt36PEAc-g0TlFPDKoesFanz0M3ar_Xd7O5stTb1AsKCS7k5IJzqDVVbTO7FweoVeIJmmz"
Или на другого друга.
con$response$items[[1]]
## $first_name
## [1] "Александр"
##
## $id
## [1] 201914
##
## $last_name
## [1] "Самойлов"
##
## $track_code
## [1] "3951827eX2n-PSG_P_-zYe-XaK-3Ae119lIO8ww3GR9_tt5SmlQyAvYKH7g_r7hn21XDFlxvmmGEOw"
Можно увидеть, что поля у всех друзей совпадают, поэтому можно отказаться от list
и перейти к data frame. Это можно сделать с помощью функции bind_rows
из пакета dplyr
, ей нужно лишь передать список списков. А она сама все склеит в один data frame.
df <- con$response$items %>%
bind_rows()
df %>% head()
## # A tibble: 6 x 5
## first_name id last_name track_code deactivated
## <chr> <int> <chr> <chr> <chr>
## 1 Александр 201914 Самойлов 3951827eX2n-PSG_P_-zYe-XaK-3Ae119lIO… <NA>
## 2 Ксения 347530 Матросова 61ef7392q8KcGSPK2c3kr9oakRoH5cNo_v1Y… <NA>
## 3 Дмитрий 2028993 Генералов 6e6903284A3BN2i3E1vTVeZPmXQhnWlDHqDj… <NA>
## 4 Никита 3577476 Чухров 8323f41e2NH57iJQxXx3ALZ2GIxVJ_VylaN4… <NA>
## 5 Kirill 3739940 Shilov 5d5afa8bwXyrXj2no8JvOKmFCkFCJiKINDiB… <NA>
## 6 Дмитрий 5778788 Емельянов fcaa92can3yglTk8WgnuCFKxwVlHL0wQwWh3… <NA>
Поле track_code
нам не нужно, поэтому давайте избавимся от него.
df <- df %>%
select(-track_code)
df %>% head()
## # A tibble: 6 x 4
## first_name id last_name deactivated
## <chr> <int> <chr> <chr>
## 1 Александр 201914 Самойлов <NA>
## 2 Ксения 347530 Матросова <NA>
## 3 Дмитрий 2028993 Генералов <NA>
## 4 Никита 3577476 Чухров <NA>
## 5 Kirill 3739940 Shilov <NA>
## 6 Дмитрий 5778788 Емельянов <NA>
Появилось какое-то поле deactivated
, видимо какие-то друзья удалили свои аккаунты. Давайте найдем их и уберем из df.
df %>%
filter(!is.na(deactivated)) %>%
head()
## # A tibble: 6 x 4
## first_name id last_name deactivated
## <chr> <int> <chr> <chr>
## 1 DELETED 27159331 "" deleted
## 2 Ирина 27225061 "Иванова" deleted
## 3 Илья 37280099 "Дуров" deleted
## 4 DELETED 70212394 "" deleted
## 5 DELETED 86090729 "" deleted
## 6 DELETED 98638665 "" deleted
df <- df %>%
filter(is.na(deactivated)) %>%
select(-deactivated)
df %>%
head()
## # A tibble: 6 x 3
## first_name id last_name
## <chr> <int> <chr>
## 1 Александр 201914 Самойлов
## 2 Ксения 347530 Матросова
## 3 Дмитрий 2028993 Генералов
## 4 Никита 3577476 Чухров
## 5 Kirill 3739940 Shilov
## 6 Дмитрий 5778788 Емельянов
Можно склеить имя и фамилию.
df <- df %>%
unite("name", last_name, first_name, sep = " ")
df %>%
head()
## # A tibble: 6 x 2
## name id
## <chr> <int>
## 1 Самойлов Александр 201914
## 2 Матросова Ксения 347530
## 3 Генералов Дмитрий 2028993
## 4 Чухров Никита 3577476
## 5 Shilov Kirill 3739940
## 6 Емельянов Дмитрий 5778788
Сейчас это просто data frame моих друзей, но в нем могут храниться и друзья другого человека, поэтому давайте хранить в data frame пару людей.
my_id <- 73614108
my_name <- "Зарманбетов Ахмед"
df <- df %>%
mutate(friend_id = my_id,
friend_name = my_name)
df %>%
head()
## # A tibble: 6 x 4
## name id friend_id friend_name
## <chr> <int> <dbl> <chr>
## 1 Самойлов Александр 201914 73614108 Зарманбетов Ахмед
## 2 Матросова Ксения 347530 73614108 Зарманбетов Ахмед
## 3 Генералов Дмитрий 2028993 73614108 Зарманбетов Ахмед
## 4 Чухров Никита 3577476 73614108 Зарманбетов Ахмед
## 5 Shilov Kirill 3739940 73614108 Зарманбетов Ахмед
## 6 Емельянов Дмитрий 5778788 73614108 Зарманбетов Ахмед
Мы написали много разного кода, хотелось бы обернуть это в виде функций, чтобы использовать в дальнейшем.
Для начала давайте напишем функцию, которая вызывает нужный нам метод и выдает результат. Можно не прописывать все параметры (версия, токен, параметры метода), а просто собрать их в виде списка и отдать функции GET
в аргумент query
.
get_method <- function(method, token, params, version = "5.52"){
url <- paste('https://api.vk.com/method/', method, sep = '')
params <- append(params, list(access_token = token, v = version))
request <- GET(url, query = params)
info <- content(request)
return(info)
}
Теперь у нас есть аккуратная функция, давайте попробуем вызвать ее.
params <- list(fields = "first_name,last_name")
con <- get_method(method = "friends.get", token = token, params = params)
str(con, max.level = 3, list.len = 2)
## List of 1
## $ response:List of 2
## ..$ count: int 515
## ..$ items:List of 515
## .. ..$ :List of 4
## .. ..$ :List of 4
## .. .. [list output truncated]
Получили то же самое, но теперь с этой функцией можем вызывать любой метод в 2 строчки.
Теперь давайте напишем функцию, которая обработает list
и получит data frame.
to_df <- function(con, friend_id, friend_name) {
friends_list <- con$response$items
friends_list<- list.apply(friends_list, list.flatten)
df <- friends_list %>%
bind_rows()
if ("deactivated" %in% names(df)) {
df <- df %>%
filter(is.na(deactivated))
}
df <- df %>%
unite("name", last_name, first_name, sep = " ") %>%
select(id, name) %>%
mutate(friend_id = friend_id,
friend_name = friend_name)
return(df)
}
Используем ее.
df <- to_df(con, friend_id = 73614108, friend_name = "Зарманбетов Ахмед")
df %>%
head()
## # A tibble: 6 x 4
## id name friend_id friend_name
## <int> <chr> <dbl> <chr>
## 1 201914 Самойлов Александр 73614108 Зарманбетов Ахмед
## 2 347530 Матросова Ксения 73614108 Зарманбетов Ахмед
## 3 2028993 Генералов Дмитрий 73614108 Зарманбетов Ахмед
## 4 3577476 Чухров Никита 73614108 Зарманбетов Ахмед
## 5 3739940 Shilov Kirill 73614108 Зарманбетов Ахмед
## 6 5778788 Емельянов Дмитрий 73614108 Зарманбетов Ахмед
Давайте попробуем решить следуюущую задачу. Нам нужно найти все пары людей из моих друзей, которые дружат друг с другом. Если мы соберем такой датасет, то сможем построить граф.
params <- list(fields = "first_name,last_name")
con <- get_method(method = "friends.get", token = token, params = params)
df <- to_df(con, 73614108, "Зарманбетов Ахмед")
df %>% head()
## # A tibble: 6 x 4
## id name friend_id friend_name
## <int> <chr> <dbl> <chr>
## 1 201914 Самойлов Александр 73614108 Зарманбетов Ахмед
## 2 347530 Матросова Ксения 73614108 Зарманбетов Ахмед
## 3 2028993 Генералов Дмитрий 73614108 Зарманбетов Ахмед
## 4 3577476 Чухров Никита 73614108 Зарманбетов Ахмед
## 5 3739940 Shilov Kirill 73614108 Зарманбетов Ахмед
## 6 5778788 Емельянов Дмитрий 73614108 Зарманбетов Ахмед
my_friends <- df$id
names(my_friends) <- df$name
my_friends[1]
## Самойлов Александр
## 201914
names(my_friends[1])
## [1] "Самойлов Александр"
for (i in 1:length(my_friends)) {
Sys.sleep(0.25)
params <- list(user_id = my_friends[i], fields = "name")
con <- get_method(method = "friends.get", token = token, params = params)
friend_df <- to_df(con, my_friends[i], names(my_friends[i])) %>%
filter(id %in% my_friends)
df <- df %>%
union(friend_df)
}
library(igraph)
graph_vk <- graph.edgelist(as.matrix(df %>% select(name, friend_name)), directed = FALSE)
V(graph_vk)$color = ifelse(V(graph_vk)$name == 'Зарманбетов Ахмед', 'red', 'orange')
plot(graph_vk)
size <- 10000
png('vk_graph.png', width = size, height = size)
plot(graph_vk, vertex.label.cex = 2, vertex.size = 3, vertex.label = str_replace(V(graph_vk)$name, " ", "\n "))
dev.off()
## quartz_off_screen
## 2