Python之MongoDB数据分析及其Highcharts可视化

近期使用requests把内部bugziila上的bug数据爬取了一遍,并存入了本地的MongoDB数据库,想着对数据做些简单的可视化处理,将所有产品的bug数做一个统计和可视化,于是便有了这篇简短的文章。

MongoDB中的数据存储格式

之前爬取数据后,存入MongoDB的数据格式如下,而我要分析的仅仅是其中的metadata.Product

{
    "_id" : ObjectId("5c3ea1d3fbf7884588041acc"),
    "id" : 1,
    "title" : "\"Show interface dot11radio advanced\" command doesn't work",
    "metadata" : {
        "Status" : "已解決 (RESOLVED) 已修復 (FIXED)",
        "Product" : "AP54GTSW",
        "Component" : "Baseline",
        "Version" : "0.1",
        "Platform" : "PC Linux",
        "Importance" : "P2 normal",
        "Assigned" : "Wallace Peng",
        "Reported_author" : "happy Hu",
        "Reported" : "2004-05-11 20:32 CST",
        "Modified" : "2010-08-27 15:42 CST"
    },
    "comments" : {
        "描述" : {
            "user" : "happy Hu",
            "time" : "2004-05-11 20:32:24 CST",
            "text" : "\"Show interface dot11radio advanced\" command doesn't work -- Happy"
        },
        "意見1" : {
            "user" : "Wallace Peng",
            "time" : "2004-05-12 21:01:56 CST",
            "text" : "image 0.1rc8 fixed this bug. -- Wallace"
        }
    }
}

读取MongoDB数据

获取所有产品名称

首先获取所有产品名称,可以通过pymongo库的find函数获取metadata.Product段数据。

from pymongo import MongoClient
import os

MONGOSERVER = 'localhost:27017'
DATABASE = 'bugzilla'
COLLECTION = 'bugs'

class Analysis(object):
    def __init__(self):
        client = MongoClient(MONGOSERVER)
        db = client[DATABASE]
        self.col = db[COLLECTION]

    def getAllProductsName(self):
        records = self.col.find({}, {'metadata.Product':1, '_id':0})
        prdoucts = [ record['metadata']['Product'] for record in records ]
        prdoucts = list(set(prdoucts))
        prdoucts = sorted(prdoucts, key=lambda s: s.lower())
        return prdoucts

MongoClient用来打开一个client去连接数据库,通过指定databasecollection可以定位到数据源,再用find函数检索出产品名称信息。以上的getAllProductsName后面通过set集合的方式去重,最后通过sorted进行排序,即可获得有序的产品名称products

获取产品bug数

当然,我们想要的其实是产品bug数量,对于单个产品,可以通过pymongo库的count_documents函数获得该指定产品bug数。

    def countOneProduct(self, product):
        return self.col.count_documents({'metadata.Product':product})  

想要获取所有的产品bug数,简单的想法当然是循环调用countOneProduct函数,但这样会需要大量的时间,9万多条数据量需24s左右的时间。为什么这么久,因为每次执行count_documents都需要重新遍历一遍数据库,这显然是不合适的。更好的方法是只遍历一遍数据库,然后对不同产品的bug数进行区分统计。

    def countAllProducts(self):
        counts = {}
        records = self.col.find({}, {'metadata.Product':1, '_id':0})
        prdoucts = [ record['metadata']['Product'] for record in records ]
        for product in prdoucts:
            if product in counts:
                counts[product] += 1
            else:
                counts[product] = 1
        counts = sorted(counts.items(), key=lambda item: item[0].lower())
        return counts

以上代码段首先找出数据库每条记录的metadata.Product信息,然后循环判别product名称,对不同产品的出现次数进行计数,然后存入相应的字典记录counts[product]中,总耗时约1s。

保存数据至json文件

    def saveProductsIssues(self, path):
        counts = self.countAllProducts()
        with open(path, 'w', encoding='utf-8') as f:
            f.write('{\n')
            f.write(',\n'.join(['"{}":{}'.format(key, val) for key,val in counts]))
            f.write('\n}')

保存数据时,需要使用utf-8格式,否则可能出现后续可视化时的中文乱码问题。

Highcharts数据可视化

Highcharts是一款超级棒的用于web端数据可视化的js库,图表样式及其丰富,使用简单,强烈推荐!!!

我们要做的就是把highcharts的js库文件下载到本地,然后拷贝一个例程,最后修改一下数据来源即可,下面是针对bug数选择的一个图表样式对应的html文件

<!DOCTYPE HTML>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Highcharts Example</title>

        <style type="text/css">

        </style>
    </head>
<body>
<!-- 下面是需要引入的库,包含JQuery及highcharts库 -->
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.3.1.min.js"></script>
<script src="./code/highcharts.js"></script>
<script src="./code/modules/exporting.js"></script>
<script src="./code/modules/export-data.js"></script>

<!-- 根据数据量大小修改下方的显示范围 -->
<div id="container" style="min-width: 310px; max-width: 800px; height: 600px; margin: 0 auto"></div>
<script type="text/javascript">
// 使用$.ajax导入数据,并配置Highcharts参数
$.ajax({
    url: 'products.json',
    dataType: 'json',
    contentType: 'application/json; charset=utf-8',
    success: function (src) {
        var categories = new Array();
        var data = new Array();
        for (item in src){
            categories.push(item)
            data.push(src[item])
        }

        Highcharts.chart('container', {
            chart: {
                type: 'bar'
            },
            title: {
                text: 'All products issues number on bugzilla'
            },
            subtitle: {
                text: 'Source: bugzilla'
            },
            xAxis: {
                // 更改分类
                categories: categories,
                title: {
                    text: null
                }
            },
            yAxis: {
                min: 0,
                title: {
                    text: 'Issues',
                    align: 'high'
                },
                labels: {
                    overflow: 'justify'
                }
            },
            tooltip: {
                valueSuffix: ''
            },
            plotOptions: {
                bar: {
                    dataLabels: {
                        enabled: true
                    }
                }
            },
            legend: {
                layout: 'vertical',
                align: 'right',
                verticalAlign: 'top',
                x: -40,
                y: 80,
                floating: true,
                borderWidth: 1,
                backgroundColor: ((Highcharts.theme && Highcharts.theme.legendBackgroundColor) || '#FFFFFF'),
                shadow: true
            },
            credits: {
                enabled: false
            },
            series: [{
                name: 'Issues Number',
                // 更改数据来源
                data: data
            }]
        });
    }
}
)
</script>
</body>
</html>

以上最主要的一段代码如下,通过JQuery的ajax读取web server中的json文件,同时将json数据的key和value分离至categoriesdata

$.ajax({
    url: 'products.json',
    dataType: 'json',
    contentType: 'application/json; charset=utf-8',
    success: function (src) {
        var categories = new Array();
        var data = new Array();
        for (item in src){
            categories.push(item)
            data.push(src[item])
        }

可视化效果展示

为了方便展示,我仅仅截取了一段数据,product名称做了随机替换处理,毕竟主要还是看可视化效果嘛。

Product bugs

随机替换字符串

上面提到对product名称进行随机替换处理,其代码如下:

import json
import secrets
import string
import numpy as np

with open('products.json','r',encoding='utf-8') as f:
    data = json.load(f)

new = {}
for key, val in data.items():
    N = np.random.randint(5,20)
    key = ''.join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(N))
    new[key] = val

with open('products_new.json','w',encoding='utf-8') as f:
    json.dump(new, f, indent=4)

该替换方法参考以下文章:

小结

本文介绍了如何检索MongoDB中的数据,如何将数据存入json文件,以及如何使用highcharts实现数据可视化,最后给出了随机替换字符串的一种实现方法。