Как с помощью языка 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-операций.
Свежее
Как оптимизировать конверсии для страниц приложения в App Store и Google Play
Какие поля и параметры имеют больше значения и как выжать из них все
Как справляться с перегрузкой на работе — советы и действенные инструменты
В этой статье поделюсь лайфхаками, как наконец-то разобраться с входящим потоком задач и не выгореть от усталости
Как выйти на ROMI 5477,3% в первый месяц сотрудничества — кейс PUMA по email-маркетингу
И возобновить коммуникацию с клиентами после полугодовой паузы