深入對比數據科學工具箱:Python和R 非結構化數據的結構化

概述


在現實場景中,由於數據來源的異構,數據源的格式往往是難以統一的,這就導致大量具有價值的數據通常是以非結構化的形式聚合在一起的。對於這些非結構化數據,最常見的數據結構就是JSON,而對應的數據庫就是MongoDB。

利用MongoDB這樣的NoSQL數據庫,我們可以把異構的數據源整合到若干個collection中,通過key-value的形式對數據進行增刪改查。雖然MongoDB在數據聚合上有天然的優勢,但是在事務處理(OLTP)與數據分析(OLAP)上的表現卻不盡人意。由於MongoDB自身是一個文檔型數據庫,一方面,MongoDB 並沒有事務的概念,所以在需要保證數據一致性的場景下並不好用。另一方面,MongoDB的join查詢也沒有RDBMS來得直觀方便,所以在需要多表關聯查詢的場景下也非常捉急。

通常,對於小數據集,我們都會將數據導入到類似MySQL這樣的RDBMS中做進一步的結構化處理,對於大數據集則可以通過Hive導入到HDFS上。那麼,將MongoDB的非結構化數據導入到RDBMS中的最優方案又是什麼呢?本文將對非結構化數據與結構化數據的管道構建做詳細的討論。


從 Mongo 到 MySQL

iPython


從Mongo遷移數據到MySQL,我首先想到了用Python從Mongo讀取數據到內存中然後再批量寫入MySQL。

這裏,我選擇了使用pymongo。

%% bash
pip install pymongo
# 這裏不需要安裝 Mongo的client

首先是讀取Mongo數據

from pymongo import MongoClient
client = MongoClient('192.168.1.100', 27017)
db = client['tesedb']
posts = db.test_collection
condition = {'_id':'harryzhu'}
result_set = posts.find(condition)
for i in result_set:
    print(i)

這裏由於python會有中文的問題,我自己定義了一個將unicode轉爲utf-8的函數:

def getMongoData(data,field):
    if data[field] is None:
        return("")
    else:
        if isinstance(data[field], unicode):
            return(data[field].encode("utf-8"))
        else:
            return(data[field])

接着,準備往MySQL中導入數據

%% bash
pip install MySQL-python
# 這裏需要安裝 MySQL的client
import MySQLdb
db = MySQLdb.connect("192.168.1.100","root","harryzhu","testdb" )
cursor = db.cursor()

values = r"(\'{id}\',\'{value}\',\'{datetime}\',\'{stock_code}\',\'{share}\')".format(id=getMongoData(i,"_id"),value=getMongoData(i,"value"),datetime=getMongoData(i,"datetime"),stock_code=getMongoData(i,"stock_code"),share=getMongoData(i,"share"))

sql = r"INSERT INTO `FinanceR` (`id`,`value`,`datetime`,`stock_code`,`share`) VALUES " + values

try:
   cursor.execute(sql)
   db.commit()
except:
   db.rollback()

db.close()

從Mongo中讀取的JSON需要在這裏拼接SQL語句是一件用戶體驗非常糟糕的事情。在嘗試拼接sql 2個小時後,我果斷放棄了用Python導數據的想法。

R


由於拼接SQL是非常蛋疼的一件事情,我想到了利用R中的data frame直接完成數據的插入黑魔法。

首先,同樣是需要將數據從Mongo中讀取出來.在嘗試使用RMongo,rmongodb以及mongolite之後,我依然選擇了比較古老的RMongo。在使用的過程中,這三個包都有各自的問題。

rmongodb的教程含糊不清,看了很久都沒有找到調用遠程mongo數據庫的case,而出於jeroenooms大人之手的後起之秀mongolite則在導入數據的時候果斷的丟失了非常關鍵的_id字段,在安裝最新包之後會出現jsonlite依賴包的異常。RMongo則在讀取數據時將兩個中文字段混淆成了一個字段,導致整個數據結構錯亂。

三條路子全軍覆沒,這讓我情何以堪,好在使用R的經驗頗豐,通過中文的轉換和切割就輕鬆解決了這個問題。

下面演示一下如何使用RMongo解決Mongo數據的讀取:

install.packages("RMongo")
Sys.setenv("JAVA_HOME"="/usr/bin/java")
library(RMongo)
# 這裏不需要安裝 Mongo的client
library(dplyr)
# 設置數據庫
FinanceR <- RMongo::mongoDbConnect(host="192.168.1.100")
dbShowCollections(FinanceR)

# 設置查詢語句
condition = "{}"
# 得到返回結果
output <- RMongo::dbGetQuery(FinanceR, collection="portfolio_20160619", condition, skip=0, limit=2)

input <- output %>%
         dplyr::mutate(stock_name1 = iconv(strsplit(iconv(stock_name,from = "utf-8", to = "gbk"),"\100")[[1]][1],from = "gbk",to = "utf-8"))%>%
         dplyr::mutate(portfolio_name = iconv(strsplit(iconv(stock_name,from = "utf-8", to = "gbk"),"\100")[[1]][2],from = "gbk",to = "utf-8"))%>%
         dplyr::select(-stock_name)%>%
         rbind(cum_value="",risk="")

dbDisconnect(FinanceR)

現在,我們已經把Mongo中的數據成功導入到了內存中,下面將講解如何使用黑魔法直接將整個data frame壓入數據庫。

install.packages("RMySQL")
library(RMySQL)
# 這裏不需要安裝 MySQL的client

con <- RMySQL::dbConnect(RMySQL::MySQL(),
         user="FinanceR", password="FinanceR",
         dbname="FinanceR", host="192.168.1.100")

# 選擇追加,而不是重寫的方式來添加數據,否則數據庫的schema會被重寫         
RMySQL::dbWriteTable(con,input,row.names = FASLE, overwrite = FALSE,
  append = TRUE)
on.exit(dbDisconnect(con))

Shell


Python 和 R 的方式各有利弊,對於Python而言目前簡單粗暴的方式就是SQL拼接,而R則在流水化上比較困難。所以最後轉向尋求Shell命令的方式,通過調用 Mongo client 和 MySQL client 的 API 來完成整個數據的轉化操作。

經過研究發現 Mongo Client 提供了將數據轉成 csv 格式的接口,之後mysql則通過load命令可以將數據加載到數據庫中。

結論


通過將非結構化數據轉化爲 data frame 後直接壓入 RDBMS,一方面省去了枯燥的SQL拼接,一方面操作又直觀清晰不易出錯,對於小數據集合,直接用這樣的方法增量導入數據不失爲一種好方法,對於批量數據的流水化則採用Shell腳本調用Mongo和MySQL客戶端命令較爲合適。

轉載自作者HarryZhu的FinanceR專欄:https://segmentfault.com/blog/harryprince

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章