Получение данных из внешних источников

Функции R умеют читать данные не только с локального компьютера, но и из сети. Допустим, вам упала задача собрать все доступные контакты менеджеров по продажам определенного товара в вашем регионе и поместить всю информацию в отформатированном виде в excel. Для этого нужно либо часами ходить по сайтам и копипастить данные, либо пробежаться скриптом по всем сайтам и спарcить данные. Но перед этим ознакомьтесь с законодательством вашей страны насчет парсинга веб-сайтов. А так как сайты физически хранятся по всему миру, то лучше дополнительно получить письменное согласие на парсинг данных от владельцев сайта. И только после этого вы имеете право провести реверс-инжиниринг.

Первое, что вы делаете перед поиском данных на сайте, это смотрите файл robots.txt: baidu_robotstxt <- "http://www.baidu.com/robots.txt". Внутри такого файла перечислены разделы сайта, и у некоторых будет стоять атрибут Allow, который разрешает парсинг поисковым роботам. Если вы видите ситуацию вроде Disallow: /data/ и Allow: /data/texts/, то каталог data запрещен для парсинга, но texts посмотреть можно. Также, в заголовке кода самой страницы можно найти строчку content="noindex, nofollow", это также явный запрет на копирование информации. Существует скрипт, который смотрит на robots.txt и выводит список запрещенных к парсингу директорий сайта непосредственно в косноль RStudio.

В рамках этой статьи мы будем работать с источниками открытых данных, например, репозиториями данных для машинного обучения. Самая простая задача это скачать готовый файл .csv из интернета (HTTP) или с FTP вашей компании. Обычно источник данных предоставляется в формате .csv (comma separated values), но данные все равно могут быть разделены точкой с запятой, поэтому лучше использовать не read.csv, а более общую функцию read.table. Для первого примера возьмем dataset с archive.ics.uci.edu.

dafileMaster <- "http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv"
df <- read.table(file = dafileMaster , sep=";", header=TRUE)
View(head(df))

Сработало, таблица отобразилась в RStudio. Отмечу, что header позволяет указать, являются ли значения в первой колонке заголовками. Формат .csv для работы популярен, но в корпоративной среде легко наткнуться и на файл excel. И уж тем более может понадобиться записывать данные непосредственно в файл .xlsx, обновляя расчет всех формул, и считывать результаты обновленных расчетов в другие таблицы. С помощью XLConnect вы сможете работать с файлами Excel как в древнем и медленном формате Excel ’97 (*.xls и привет интерфейсам бухгалтеров), так и с современным OOXML (Excel 2007+, *.xlsx), и записывать значения в файл. Пример считывания данных:

install.packages('XLConnect')
library(XLConnect)
wb = loadWorkbook("C:/Users/Downloads/testfile.xlsx",create=T)
df = readWorksheet(wb, sheet = "data")
newExport = readWorksheet (wb,"Data",1, 4, 1000, header = TRUE)
summary(newExport)

А теперь попробуем записать данные в файл простой командой writeWorksheetToFile("C:/Users/Downloads/testfile.xlsx",data=iris,sheet="iris2"), и в общем то это все, тестовые данные iris2 появились в файле.

Если возникают проблемы с зависимостями Java, то вместо XLconnect можно использовать openxlsx.

install.packages("openxlsx", dependencies=TRUE
readWorkbook('C:/Users/Downloads/testfile.xlsx')

Но порой нужно засучить рукава и работать с более «шумными» данными. Например, выкачать целую страницу сайта. Для выкачивания сайта из интернета достаточно объявить в переменную адрес сайта, указать папку для загрузки, и задать команду на скачивание. Таким же способом можно скачивать pdf, архивы. После выполнения работы скачанный сайт/файл можно удалить командой file.remove(paste(myfolder ,"filename.html ",sep="")). Ниже я приведу базовый пример, но есть библиотека downloader, которая позволяет более гибко работать с протоколом https. Во втором примере файл будет закачан в папку по умолчанию, у меня это C:\Users\user\Documents.

url <- "https://your-scorpion.ru/" 
myfolder  <- "E:/donwload/"
download.file(url,paste(myfolder,"filename.html",sep=""))
download_url <- "http://insight.dev.schoolwires.com/HelpAssets/C2Assets/C2Files/C2ImportSchoolSample.csv"
download.file(download_url, "./C2ImportSchoolSample.csv")

Это интересно, но что, если нам нужно получить лишь маленький кусочек информации с сайта? Вернемся к изначальной задаче выдирания ключевой информации со страницы. Для решения понадобятся библиотеки XML и rvest,

install.packages("rvest")
install.packages("XML")
install.packages("openxlsx") 
library(rvest)
library(openxlsx)
url <- read_html("https://your-scorpion.ru")
 
names <- url %>%
    html_nodes("h1") %>%
    html_text()
 
names1 <- url %>%
    html_nodes("article") %>%
    html_text()
 
newOne <- html_children(img)
lineOne <- html_attr(newOne, "href")
linetwo <- html_attr(newOne, "div")
 
myTableForBoss <- data.frame(title = names1, datam = names)
write.xlsx(myTableForBoss, file = 'C:/Users/checkInOnMonday.xlsx')

На выходе получаем таблицу со спарсенными заголовками статей и описанием. Очень часто именно так парсят данные о товарах в интернет-магазинах. В примере мы спарсили только одну страницу, и для парсинга всех страниц сайта придется спарсить сначала все нужные ссылки. Либо сгененировать их, так как URL формируются по определенным алгоритмам из движка.

В зависимости от сложности структуры сайта код может усложниться и не удастся избежать NA (Not Available, отсутствие значения). Получим данные о рейтинге фильма со стороннего сайта:

library(rvest) 
movie2 <- read_html("https://www.kinopoisk.ru/film/840372/") 
rating <- movie2 %>%    html_nodes(xpath = '//*[@id="block_rating"]/div[1]/div[1]/a/span[1]') %>%   html_text() %>%   as.numeric()
print (rating)
revenue <- c("rating_ball")
companiesData <- data.frame(revenue, rating)
write.csv(companiesData, "E:/Downloads/data.csv", row.names=FALSE, na="")
 
//либо с NA в данных
rating <- movie2 %>%    html_nodes("a span") %>%   html_text() %>%   as.numeric()
print (rating)
library(data.table)
result <- data.table(rating)[, lapply(.SD, function(x) x[order(is.na(x))])]
result <- result[!result[, Reduce(`&`, lapply(.SD, is.na))]]
revenue <- c("rating_ball","ratingCount","Expectations")
companiesData <- data.frame(revenue, result)

В результате мы получили рейтинг фильма и даже удалили все NA. В реальном мире все не так радужно, сайты защищаются капчей от парсинга на уровне OAuth, если не указать сертификат. Для решения проблемы вам могут посоветовать антикапчи, но не предупредят, что вас забанят еще до того, как вы получите результат. Особенно, если вы будете отправлять паралельные запросы для выкачивания тяжелый данных, вроде картинок или видео. И это правильно. Для работы с OAuth существуют пакеты, вроде httr и RCurl.

OAuth включает в себя учетные данные клиента, временные данные для авториации и токен. Все проблемы с OAuth лечатся указанием корректного сертификата. После регистрации мы обычно получаем учетные данные. Временные учетные данные доказывают, что запрос приложения на токены доступа выполняется авторизованным клиентом (например, нашей собственной учетной записи Twitter). После получения временных учетных данных их можно обменять на учетные данные токена.

Так как сайты работают по клиент-серверной архитектуре, то каждый изолированный запрос/ответ было бы сложно анализировать, поэтому запросы от одного пользователя группируются в сессию. Ниже пример, который активирует сессию и отправляет комментарий через форму.

library(rvest)
box_office <- read_html("https://your-scorpion.ru/")
box_office %>% html_node("form") %>% html_form()
 
session <- html_session("https://your-scorpion.ru/printing-for-dummies/")
form <- html_form(session)[[1]]
form <- set_values(form, author = "Иван Иванов", comment = "Мой комментарий заключается в том, что эта идея достаточно здравая.")
submit_form(session,form)

Теперь давайте спарcим API, на выходе это обычный JSON. Если вы можете получить нужные данные по API , то это предпочтительный способ по сравнению с парсингом страниц. Иначе вам предстоит долгая и упорная работа с регулярными выражениями. Способы можно миксовать. Самое простое и очевидное по JSON:

install.packages("rjson")
library("rjson")
json_file <- "C:/Users/25.4.2019-Untitled.json"
jsonclass <- fromJSON(file = json_file)

Сделаем пример чуть больше похожим на реальную задачу и используем регулярные выражения. На самом деле любая строка это регулярное выражение, даже один символ. Но основной плюс регулярных выражений в их сложности.

install.packages("tidyverse")
library(tidyverse)
datalist <- c("Facebook.com", "Twitter.de", "Wikipedia.org", "Google.at", "Vc.ru",
"Dribbble.cn", "Instagram.fi", " Vk.cc", "Yandex.ru", "Your-scorpion.ru",
"Linkedin.com", "Wordpress.org", "Pinterest.kz")
 
pattern <- "(G|F)"
pattern2 <- "[a-zA-Z]*$"
str_subset(datalist, pattern)
sum(str_detect(datalist, pattern2))

Существует множество интересных возможностей по работе со строками через регулярные выражения, например, str_extract(datalist, pattern2) покажем все символы с определенными вхождениями, а если использовать регулярное выражение "[a-zA-Z]*$", то удастся вычленить из строки все домены. При этом есть важный нюанс, нужно использовать / .csv, так как просто .csv обозначает любое значение. Например, str_match(c('abcsv', 'a.csv'), '.csv') вернет вхождение bcsv, а оно нам не надо. str_match(c('abcsv', 'a.csv'), '/.csv') сработает так, как ожидается.

Комбинируя все перечисленные способы сбора данных, можно решить большинство повседневных задач. А любая автоматизация рутинных задач экономит ваше время и деньги вашему работодателю. И еще раз напомню, что нужно четко понимать разницу между частыми запросами к сайту и парсингом. Второе — плохо и запрещено.

9 комментариев

  1. Виталий Паникой

    Здравствуйте, а есть ли способы узнать, что в моих мобильных приложениях работают одни и те же пользователи? Допустим, Вася Пупкин работает в приложении 1 и он же в приложении 2?

    • Цветков Максим (Author)

      По идентификатору устройства, в любой системе аналитики идентификатор устройства будет одинаковым, поэтому вам нужен простой join по идентификатору.

  2. Виктор Маникович

    Здравствуйте, как вы порекомендуете хранить данные?

    • Цветков Максим (Author)

      Технически, надо копить логи веб-сервисов и микросервисов, особенно clickstream. Все это хранить в своей базе данных, но по началу можно жить со всякими Amazon Kinesis, Apache Kafka, Presto, Impala, Hadoop + Amazon s3. А потом уже, когда понадобится глубокая поведенческая аналитика и появятся деньги на DevOps, поднимать какой-нибудь ClickHouse и писать транспорт данных. Результаты опросов можно сразу хранить в БД. В итоге должно получиться нечто похожее на Kafka -> Spark Streaming -> Cassandra.

      Разработчик + аналитик придумывают алгоритм сбора данных, сисадмин + аналитик решают, как данные хранить и менеджер + аналитик принимают решения на основе данных.

  3. Вильсон SHop

    Здравствуйте, есть ли способ сразу понять, насколько окупился клиент за определенный срок? Мы привлекаем пользователей через таргетинговую рекламу, каждый лид стоит достаточно дорого. И он должен окупаться в течении года-двух, так как регулярно возвращается за техническим обслуживанием. Каким способом вы порекомендуете такое реализовывать, желательно на Python.

    • Цветков Максим (Author)

      Вам нужно по каждому пользователю подсчитать выручку и количество заказов. Звучит, как стандартный когоротный анализ. Берете dataframe с данными:

      import matplotlib
      matplotlib.use('TkAgg')
      import pandas as pd
       
      df = pd.DataFrame({"id": ["user1", "user2", "user2", "user3", "user2", "user1", "user3"],
                        "order_date": [5/25/14, 5/25/14, 4/14/14, 3/12/14, 5/25/14, 5/25/14, 5/25/14],
                        "customer_id": [454332, 564333, 454333, 754211, 454332, 433135, 564333],
                        "sales": [43.0, 23.0, 53.0, 10.5, 7.5, 33.3, 3],
                        "first_orders":[5/25/14, 5/25/14, 5/25/14, 15/25/14, 5/25/14, 5/25/14, 5/25/14]})
       
      df['order_date'] = pd.to_datetime(df['order_date'])

      Считаете общую выручку и количество заказов:

      print (df['sales'].sum())
      print (df['sales'].count())

      И считаете аналогичные значения для каждого пользователя:

      print (df.groupby('customer_id')['sales'].agg(['sum', 'count']))
  4. Артем Юшков

    Существуют ли системы аналитики для сбора данных по браузерным играм? спасибо. И для мобильных игр что используете?

    • Цветков Максим (Author)

      Для аналитики браузерных игр подойдут любые системы, которые работают с POST-запросами, просто по ним слать ивенты на сервер через JSON. Game analytics еще посмотрите, её можно встроить хоть в холодильник.

      Для мобильных игр Amplitude хорошо подходит, а если не устроит их ценовая политика то AppMetrica и DeltaDNA тоже ничего. Или firebase для сырых данных. На самом деле надо смотреть на тарифный план системы игровой аналитики и на тип вашей игры. Если у вас будет множество юзеров с короткими сессиями, то лучше взять систему с оплатой за объем данных. Или по оплате за DAU, если у вас будут мегабайтные JSON с логированием каждого действия пользователя. Также, можно посмотреть на Mixpanel, Localytics и Appsee, последний я люблю за heatmaps и записи экрана пользователя. Если упор на ASO (тесты иконок, скриншотов), то можно посмотреть SplitMetrics. Ну и от тройки AppsFlyer, Firebase, Facebook Analytics все равно не уйти.

      Если игра на Unity, то не брезгуйте и Unity Analytics. Она бесплатная, простая, умеет отслеживать падения приложения и отдавать сырые данные.

«Взаимодействуя с данным сайтом, вы, как пользователь, автоматически даете согласие согласие на обработку персональных данных» Согласие

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.