Как с помощью языка R и MongoDB выгрузить данные из источника со сложной структурой
Всем привет. Я работаю веб-аналитиком и часто строю отчеты для руководства, показываю прибыль и расходы в разных срезах, визуализирую данные и тому подобное. Сегодня расскажу о том, как решил классическую задачу: организовать регулярную выгрузку данных из CRM системы и внести их в базу данных. В моем случае речь идет о RetailCRM.
Задача
Первоначальный план заключался в том, чтобы получить все записи в R, объединить их в таблицу и загрузить в базу.
По ходу выполнения задачи столкнулся с такими проблемами:
- Записи заказов были представлены в виде сложной древовидной структуры с вложенными полями и списками.
- Пустое поле не видно в ответе. То есть у каждого заказа ряд общих полей, но при этом количество полученных полей у каждого заказа разное. Из-за этого оказалось сложно что-либо делать с полученным массивом.
- Скорость обработки всех заказов (а их около 47 000) могла составить от семи до девяти часов.
В поисках способов оптимизации решения наткнулся на упоминания о MongoDB.
MongoDB — документоориентированная система управления базами данных (СУБД) с открытым исходным кодом, не требующая описания схемы таблиц. Классифицирована как NoSQL, использует JSON-подобные документы и схему базы данных.
Решение
После того, как ознакомился с документацией и гайдом для R, решил попробовать. Установил базу, а затем — пакет mongolite и приступил.
- Записал первые 100 заказов.
library(jsonlite)library(httr)library(mongolite)host
<- "http://<YOUR_DOMINE>.retailcrm.ru" # свой домен от RetailCRMtoken
<- "<YOUR_TOKEN>" # свой токен от RetailCRMorder_number <- 1a
<- content(GET(paste0(host, "/api/v5/orders", "?apiKey=", token,
"&limit=100")))mdb <- mongo(collection = "test", url =
'mongodb://localhost:27017/') #подключение к MongoDB
'mongodb://username:password@localhost:27017/'for(s in
1:length(a$orders)){ifelse( a$orders[[s]]$payments ==
"named list()", 0, names(a$orders[[s]]$payments)
<- "paymants_info")mdb$insert(toJSON(a$orders[[s]]))order_number
<- order_number + 1}test <- mdb$find('{}')View(test)
Важно: если вы используете учетную запись в MongoDB, путь подключения указывайте так: 'mongodb://username:password@localhost:27017/'
- Получил сформированную таблицу. Запись заняла чуть меньше пяти секунд, получение прошло ещё быстрее. При этом важно помнить: проблема заключалась в том, что не совпадало количество полей в каждом заказе. Здесь этой проблемы не было. Дело в том, что если в документе (в заказе) подобное поле отсутствует, возвращается NULL.
MongoDB позволяет фильтровать поля и количество возвращаемых строк на этапе запроса. Это делает работу намного удобнее и быстрее. Важная особенность — подобный формат значений в таблице:
Перед записью в реляционную базу необходимо перевести данные в общий тип. Если не проверять каждый столбец на тип данных, такой функции будет достаточно:
for(i in 1:length(colnames(test))){test[[i]] <- as.character(test[[i]])}
Что делать со сложной структурой?
Для решение первой проблемы (древовидная структура) пришлось повозится чуть больше. Под сложной структурой я понимаю записи такого вида:
Список покупок в рамках заказа
Для парсинга этих полей я пошел следующим путем:
- Написал функцию, которая получает на входе таблицу с полями «id» и «вложенные листы», раскрывает вложения и присваивает вложению «id» из соседней строчки:
ltdf <- function(x , f_name, col_number) {st3
<- data.frame( "id" = as.character(x[[col_number]]))st3$row_id
<- as.character(rownames(st3))tryCatch({st4
<- ct(data.table::rbindlist(eval(parse(text = paste0("x$", f_name))),
idcol = TRUE, fill = T )) }, error = function(x){})colnames(st4)
[ colnames(st4) == '.id'] <- "row_id"st4$row_id
<- as.character(st4$row_id)results <- left_join(x =st4,
y = st3, by = c("row_id"))return(results)}
- Получил из Mongo только нужные мне поля и применил данную функцию:
items <- c$find( fields = '{"id" : true ,"items.id" :
true,"items.initialPrice" : true,"items.discountTotal" :
true,"items.vatRate" : true,"items.quantity" :
true,"items.status" : true,"items.purchasePrice" :
true,"items.isCanceld" : true,"_id" : false}')items
<- ltdf(items, "items", 1)
- Результат записал в реляционную базу данных.
По итогу, в базе появились таблички и возможность строить модель данных из вложенных таблиц.
Эту модель можно использовать для отчетов или других манипуляций.
Используя описанный метод, общее время получения данных (47к заказов), обработки и записи в реляционную базу сократилось до 30 минут. Поэтому рекомендую MongoDB (с использованием пакета mongolite) — точно упростите для себя парсинг записей со сложной структурой. Важно: в MongoDB просто писать данные, при этом система остается быстрой для CRUD-операций.
Свежее
История успеха приложения OkTalk: рост количества загрузок на рынке США и Франции
Мы получили увеличение установок в США на 130% и во Франции на 700%
Оптимизация страниц пагинации интернет-магазина — подробная инструкция
Как правильно оптимизировать страницы пагинации, чтобы не допустить проседание целевого трафика
Больше чем созвоны. Что такое услуга CMO on demand
Почти год назад сооснователь Netpeak Андрей Чумаченко начал разрабатывать новую услугу CMO on demand. Прочитайте, как это работает.