Гостевой пост

Как с помощью языка R и MongoDB выгрузить данные из источника со сложной структурой

Всем привет. Я работаю веб-аналитиком и часто строю отчеты для руководства, показываю прибыль и расходы в разных срезах, визуализирую данные и тому подобное. Сегодня расскажу о том, как решил классическую задачу: организовать регулярную выгрузку данных из CRM системы и внести их в базу данных. В моем случае речь идет о RetailCRM.

Задача

Первоначальный план заключался в том, чтобы получить все записи в R, объединить их в таблицу и загрузить в базу.

По ходу выполнения задачи столкнулся с такими проблемами:

  1. Записи заказов были представлены в виде сложной древовидной структуры с вложенными полями и списками.
  2. Пустое поле не видно в ответе. То есть у каждого заказа ряд общих полей, но при этом количество полученных полей у каждого заказа разное. Из-за этого оказалось сложно что-либо делать с полученным массивом.
  3. Скорость обработки всех заказов (а их около 47 000) могла составить от семи до девяти часов.

В поисках способов оптимизации решения наткнулся на упоминания о MongoDB.

MongoDB — документоориентированная система управления базами данных (СУБД) с открытым исходным кодом, не требующая описания схемы таблиц. Классифицирована как NoSQL, использует JSON-подобные документы и схему базы данных.

Решение

После того, как ознакомился с документацией и гайдом для R, решил попробовать. Установил базу, а затем — пакет mongolite и приступил.

  1. Записал первые 100 заказов.
library(jsonlite)
library(httr)
library(mongolite)
host <- "http://<YOUR_DOMINE>.retailcrm.ru" # свой домен от RetailCRM
token <- "<YOUR_TOKEN>" # свой токен от RetailCRM
order_number <- 1
a <- 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/'

сформированная таблица

  1. Получил сформированную таблицу. Запись заняла чуть меньше пяти секунд, получение прошло ещё быстрее. При этом важно помнить: проблема заключалась в том, что не совпадало количество полей в каждом заказе. Здесь этой проблемы не было. Дело в том, что если в документе (в заказе) подобное поле отсутствует, возвращается NULL.

MongoDB позволяет фильтровать поля и количество возвращаемых строк на этапе запроса. Это делает работу намного удобнее и быстрее. Важная особенность — подобный формат значений в таблице:

Формат значений в таблице

Перед записью в реляционную базу необходимо перевести данные в общий тип. Если не проверять каждый столбец на тип данных, такой функции будет достаточно:

for(i in 1:length(colnames(test))){
test[[i]] <- as.character(test[[i]])
}

Что делать со сложной структурой?

Для решение первой проблемы (древовидная структура) пришлось повозится чуть больше. Под сложной структурой я понимаю записи такого вида:

Сложная структура

Список покупок в рамках заказа

Для парсинга этих полей я пошел следующим путем:

  1. Написал функцию, которая получает на входе таблицу с полями «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)
}

  1. Получил из 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)

  1. Результат записал в реляционную базу данных.

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

Теперь можно строить модель данных из вложенных табличек

Эту модель можно использовать для отчетов или других манипуляций.

модель можно использовать для отчетов или других манипуляций

Используя описанный метод, общее время получения данных (47к заказов), обработки и записи в реляционную базу сократилось до 30 минут. Поэтому рекомендую MongoDB (с использованием пакета mongolite) — точно упростите для себя парсинг записей со сложной структурой. Важно: в MongoDB просто писать данные, при этом система остается быстрой для CRUD-операций.

19
2
0
Обнаружили ошибку? Выделите ее и нажмите Ctrl + Enter.

Комментарии (0 )

Последние комментарии

    Чтобы оставить комментарий, нужно войти

    Подписаться

    на самую полезную рассылку по интернет-маркетингу

    Самое

    обсуждаемое популярное читаемое

    Этот сайт использует куки-файлы и другие технологии, чтобы помочь вам в навигации, а также предоставить лучший пользовательский опыт, анализировать использование наших продуктов и услуг, повысить качество рекламных и маркетинговых активностей.