packages <- c("readr", "dplyr", "readxl")
# install.packages(packages)
library(readr)
library(readxl)
library(dplyr)
HTTP – это система правил обмена данными между компьютерами. По сути это “язык интернета”. Вы сталквиваетесь с протоколом HTTP каждый день, когда сидите в интернете. Допустим, вы открываете какой-то сайт, ваш компьютер - клиент (client) делает запрос (request) к серверу (server). Затем сервер отправляет обратно ответ (response), в котором содержатся файлы, с помощью которых получается веб-страница.
Вы можете не только попросить какие-то данные, но и, например, отправить их. Для разных задача есть разные методы для обращения к серверу. Ниже представлены некоторые из них.
Нас будет интересовать только получение данных от сервера, поэтому мы сконцентрируем свое внимание на методе GET.
На самом деле вы уже использовали этот метод ранее, когда использовали функцию read_csv
передавая ей url, где лежит нужная нам csv.
url <- 'https://github.com/ahmedushka7/R/raw/master/docs/homeworks/test/data/covid.csv'
df <- read_csv(url)
df %>% head(n = 3)
## # A tibble: 3 x 5
## country state date confirmed_cases fatalities
## <chr> <chr> <date> <dbl> <dbl>
## 1 Afghanistan <NA> 2020-01-23 0 0
## 2 Afghanistan <NA> 2020-01-24 0 0
## 3 Afghanistan <NA> 2020-01-25 0 0
Функция read_csv
под капотом вызывает метод GET
и получает csv.
Не всегда можно подгрузить файл из интернета сразу в R. Например, это не работает с excel файлами.
url <- "https://github.com/ahmedushka7/R/blob/master/docs/scripts/hse_data_analysis/sem_7/data_extra/file_excel.xlsx?raw=true"
df <- read_excel(url)
## Error: `path` does not exist: 'https://github.com/ahmedushka7/R/blob/master/docs/scripts/hse_data_analysis/sem_7/data_extra/file_excel.xlsx?raw=true'
Но не очень хочется заходить в браузер, переходить по ссылке, скачивать файл, переносить его из папки с загрузками в нужную директорию, и только потом импортировать его. Особенно грустно, когда таких файлов много и вам постоянно приходится делать это. Хочется скачать его и сразу подгрузить с помощью R.
С помощью функции file.path
можно создать путь, куда сохранится файл. Она просто расставляет слэши (/
).
url <- "https://github.com/ahmedushka7/R/blob/master/docs/scripts/hse_data_analysis/sem_7/data_extra/file_excel.xlsx?raw=true"
path <- file.path("~", "test.xlsx")
print(path)
## [1] "~/test.xlsx"
А теперь используем функцию download.file
.
download.file(url, path)
Теперь файл у нас на компьютере и мы можем его подгрузить.
df <- read_excel(path)
df
## # A tibble: 3 x 4
## Ахмед `19` М `7.75`
## <chr> <dbl> <chr> <dbl>
## 1 Миша 20 М 6.65
## 2 Алина 25 Ж 5.56
## 3 Ангелина 23 Ж 1.15
Если вы не хотите оставлять файл у себя на компьютере, то можно его удалить.
file.remove(path)
## [1] TRUE
Если вы хотите делать свои HTTP запросы, то можно использовать библиотеку httr
. Давайте установим и подгрузим её.
# install.packages("httr")
library(httr)
Давайте попробуем сделать свой первый HTTP запрос. Для этого просто используем функцию GET
, в которую передадим url
и запишем результат, который выдаст нам сервер в переменную response
.
url <- "http://www.example.com/"
response <- GET(url)
Что такое response
? Это list
, в котором хранится некоторая метаинформация о запросе, а также сами данные, которые мы хотели получить.
print(response)
## Response [http://www.example.com/]
## Date: 2020-11-08 21:41
## Status: 200
## Content-Type: text/html; charset=UTF-8
## Size: 1.26 kB
## <!doctype html>
## <html>
## <head>
## <title>Example Domain</title>
##
## <meta charset="utf-8" />
## <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
## <meta name="viewport" content="width=device-width, initial-scale=1" />
## <style type="text/css">
## body {
## ...
Из метаинформации стоит обратить на Status
. Это код, который говорит о том, отработал ли запрос или нет.
Все ошибки можно посмотреть по ссылке.
Статус-код можно получить несколькими способами.
print(response$status_code) # обращение к листу
## [1] 200
print(status_code(response)) # использование функции
## [1] 200
Другая метаинформация обычно не нужна, поэому хочется получить те данные (контент), которые мы просили. Можно получить их с помощью функции content
.
data <- content(response)
print(data)
## {html_document}
## <html>
## [1] <head>\n<title>Example Domain</title>\n<meta charset="utf-8">\n<meta http ...
## [2] <body>\n<div>\n <h1>Example Domain</h1>\n <p>This domain is for use ...
По дефолту функция content
сама парсит полученные данные. Но если вы хотите получить “голый” текст, то можете указать второй параметр as = "text"
.
data <- content(response, as = "text")
print(data)
## [1] "<!doctype html>\n<html>\n<head>\n <title>Example Domain</title>\n\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <style type=\"text/css\">\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </style> \n</head>\n\n<body>\n<div>\n <h1>Example Domain</h1>\n <p>This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.</p>\n <p><a href=\"https://www.iana.org/domains/example\">More information...</a></p>\n</div>\n</body>\n</html>\n"
Тот ужас, который вы видите перед глазами называется HTML
страницей. Это код страницы, которую вы видите в браузере. О контенте, который мы получаем и HTML
мы поговорим позже.
Ну сделали мы запрос, получили статус код 200, что дальше? Контент, который мы получаем может быть абсолютно разным. Но зачастую, это либо HTML, либо JSON. С HTML мы разберемся на следующем занятии. А вот про JSON поговорим поподробнее.
JSON это key-value структура. Есть ключ, а ему соотвествует какое-то значение. По такой структуре очень удобно перемещаться. В R нет JSON, но есть структура данных
list
, которая очень похожа. Если вам все таки потребуется работать с JSON, то попробуйте пакет jsonlite
.
Структура данных list
(список) очень удобна, так как может содержать в себе абсолютно любые другие объекты. В списке может содержаться другой список. Это очень напоминает JSON. Список можно создать с помощью функции list
.
my_list <- list(10, "R", c(-12, 13, 14))
print(my_list)
## [[1]]
## [1] 10
##
## [[2]]
## [1] "R"
##
## [[3]]
## [1] -12 13 14
print(class(my_list))
## [1] "list"
Очень удобно здесь использовать функцию str
, которая может показать структуру списка.
str(my_list)
## List of 3
## $ : num 10
## $ : chr "R"
## $ : num [1:3] -12 13 14
В списках можно давать наименования.
my_list <- list(number = 10, language = "R", ages = c(20, 30, 44))
str(my_list)
## List of 3
## $ number : num 10
## $ language: chr "R"
## $ ages : num [1:3] 20 30 44
В списке могу быть другие списки.
my_list = list(flist = list(1, 2), slist = list(3, 4))
str(my_list)
## List of 2
## $ flist:List of 2
## ..$ : num 1
## ..$ : num 2
## $ slist:List of 2
## ..$ : num 3
## ..$ : num 4
Создавать научились, осталось научиться работать с ними. Есть три типа доступа к элементам.
[]
– берет срез текущего списка, результат список[[]]
– переходит в элемент, который содержится в текущем списке$
– аналогично [[]]
, только более простоmy_list <- list(number = 10, name = "Ahmed", na = NA, other_list = list(array = rep(1, 4)))
my_list[1:3] # вернули list с его первыми 3 элементами
## $number
## [1] 10
##
## $name
## [1] "Ahmed"
##
## $na
## [1] NA
my_list[[1]] # вытащили значение второго элемента
## [1] 10
my_list$number # аналогично, только доступ по имени
## [1] 10
my_list$other_list$array # можно делать даже так
## [1] 1 1 1 1
Большой функционал для работы с list
есть в пакете rlist
.
url_sw4 <- "http://www.omdbapi.com/?apikey=72bc447a&i=tt0076759&r=json"
response <- GET(url_sw4)
con <- content(response)
str(con)
## List of 25
## $ Title : chr "Star Wars: Episode IV - A New Hope"
## $ Year : chr "1977"
## $ Rated : chr "PG"
## $ Released : chr "25 May 1977"
## $ Runtime : chr "121 min"
## $ Genre : chr "Action, Adventure, Fantasy, Sci-Fi"
## $ Director : chr "George Lucas"
## $ Writer : chr "George Lucas"
## $ Actors : chr "Mark Hamill, Harrison Ford, Carrie Fisher, Peter Cushing"
## $ Plot : chr "Luke Skywalker joins forces with a Jedi Knight, a cocky pilot, a Wookiee and two droids to save the galaxy from"| __truncated__
## $ Language : chr "English"
## $ Country : chr "USA"
## $ Awards : chr "Won 6 Oscars. Another 52 wins & 29 nominations."
## $ Poster : chr "https://m.media-amazon.com/images/M/MV5BNzVlY2MwMjktM2E4OS00Y2Y3LWE3ZjctYzhkZGM3YzA1ZWM2XkEyXkFqcGdeQXVyNzkwMjQ"| __truncated__
## $ Ratings :List of 3
## ..$ :List of 2
## .. ..$ Source: chr "Internet Movie Database"
## .. ..$ Value : chr "8.6/10"
## ..$ :List of 2
## .. ..$ Source: chr "Rotten Tomatoes"
## .. ..$ Value : chr "92%"
## ..$ :List of 2
## .. ..$ Source: chr "Metacritic"
## .. ..$ Value : chr "90/100"
## $ Metascore : chr "90"
## $ imdbRating: chr "8.6"
## $ imdbVotes : chr "1,208,256"
## $ imdbID : chr "tt0076759"
## $ Type : chr "movie"
## $ DVD : chr "N/A"
## $ BoxOffice : chr "N/A"
## $ Production: chr "Lucasfilm Ltd."
## $ Website : chr "N/A"
## $ Response : chr "True"
Рейтинг фильма на Metacritic.
con$Ratings[[3]]$Value
## [1] "90/100"
Обычно хочется сделать не один HTTP запрос, а очень много. Давайте попробуем сделать 30 запросов на сайт Авито. Будем следить за статус кодом и с помощью функции Sys.time()
посмотрим как часто будут делаться запросы.
for (i in 1:20) {
response <- GET(url = "https://www.avito.ru/moskva")
print(paste("Статус код = ", status_code(response), ", время запроса = ", Sys.time()))
}
Если запустить код выше, то будет видно, что запросы делаются буквально каждую секунду. Кажется, что нет ничего плохого в этом, но есть одно но. Сервер не любит, когда один и тот же клиент отправляет много запросов. Если посмотреть на статус код, то сначала он равен 200, а потом будет равен 429 (Too Many Requests). Сервер заблокировал нас на какое-то время.
Нужно умереть пыл и делать запросы не каждую секунду, а с некоторой задержкой. Самый простой способ, это использовать функцию Sys.sleep
. Она принимает на вход количество секунд, на которое нужно “заснуть”. То есть R засыпает на какое-то время и ничего не делает. Давайте делать запрос каждые 4 секунды.
for (i in 1:20) {
response <- GET(url = "https://www.avito.ru/moskva")
print(paste("Статус код = ", status_code(response), ", время запроса = ", Sys.time()))
Sys.sleep(4)
}
Более элегантный способ, это использовать функцию slowly
из пакета purrr
.
# install.packages("purrr")
library(purrr)
Если у вас есть готовая функция, которую вы хотите “замедлить”, то можно это сделать следующим образом.
request <- function(url){ # наша функция
response <- GET(url = url)
}
slowed_request <- slowly(~request, rate = rate_delay(4)) # создаем новую функцию с задержкой в 4 секунды
for (i in 1:20) {
response <- slowed_request("https://www.avito.ru/moskva")
print(paste("Статус код = ", status_code(response), ", время запроса = ", Sys.time()))
}
Когда мы делаем HTTP запрос, вместе с ним отправляется некоторая мета информация о нас (клиенте). Можно увидеть эту метаинформацию перейдя на сайт https://httpbin.org/headers. Нас интересует User-Agent
. Можно увидеть, что там описан браузер, а также устройство с которого мы зашли на сайт. Давайте попробуем сделать аналогичный запрос через R.
response <- GET("https://httpbin.org/headers")
cont <- content(response)
cont$headers$`User-Agent`
## [1] "libcurl/7.64.1 r-curl/4.3 httr/1.4.2"
Видно, что User-Agent
совсем другой. Здесь перечислены некоторые пакеты операционной системы, с помощью которых делается HTTP запрос.
Так как эта информация приходит на сервер, сервер может увидеть, что запрос сделал не человек, а “робот”. И просто заблокировать нам доступ (обычно это ошибка 403). К счастью, мы можем менять User-Agent
так как мы хотим.
response <- GET(url = "https://httpbin.org/headers",
user_agent("Hey, my name is Ahmedushka!"))
cont <- content(response)
cont$headers$`User-Agent`
## [1] "Hey, my name is Ahmedushka!"
Таким образом, мы можем притвориться настоящим человеком.
Установить User-Agent
можно глобально, он будет применяться ко всем запросам. Это бывает удобно!
set_config(add_headers(`User-Agent` = "Hey, my name is Ahmedushka!"))
response <- GET('http://example.com')
cont <- content(response)
cont$headers$`User-Agent`
## NULL
Даже если вы меняете User-Agent
и делаете запросы не так часто, сервер может все равно разозлиться, дело в том, что ваш ip адрес остается таким же.
response <- GET(url = "https://httpbin.org/ip")
cont <- content(response)
cont$origin
## [1] "109.252.51.59"
Поменять свой ip нельзя, но можно попросить другие серверы с другим ip сделать нужный нам запрос. Для этого есть proxy серверы. Их можно найти на разных сайтах. Вот некоторые из них.
К сожалению, некоторые бесплатные прокси быстро отваливаются, а некоторые и вовсе не работают. Поэтому иногда пишут парсер для поиска хороших прокси, а потом уже с ними парсят то, что нужно.
response <- GET(url = "https://httpbin.org/ip",
use_proxy(url = "36.89.148.161", port = 8080))
## Error in curl::curl_fetch_memory(url, handle = handle): Timeout was reached: [httpbin.org] Operation timed out after 10001 milliseconds with 0 out of 0 bytes received
cont <- content(response)
cont$origin
## [1] "109.252.51.59"