zoukankan      html  css  js  c++  java
  • [py]access日志入mysql-通过flask前端展示

    pymysql组装sql入库日志

    pymysql模块的用法

    采集这些指标(metirc)都是linux环境,会用到mysql,做为数据的存储,我用docker来启动

    docker run  
    -p 3306:3306 
    -v /data/mysql:/var/lib/mysql 
    -v /etc/localtime:/etc/localtime 
    --name mysql5 
    --restart=always 
    -e MYSQL_ROOT_PASSWORD=123456 
    -d mysql:5.6.23 
    --character-set-server=utf8 
    --collation-server=utf8_general_ci
    
    create database mem;
    create table mem_used(used int, time int)
    
    
    - 时间格式
    >>> import time
    >>> time.time()
    1516860432.386474
    
    - 入库后是整形,db自动处理, 也可以入库前int(time).
    MySQL [mem]> select * from mem_used;
    +-----------+------------+
    | used      | time       |
    +-----------+------------+
    | 628658176 | 1516842082 |
    | 628813824 | 1516842083 |
    | 628936704 | 1516842084 |
    | 628936704 | 1516842085 |
    ...
    
    
    

    一个用法实例, 获取指标(通过psutil模块直接获取到已使用的内存) 也可以自己算出.

    import psutil
    import time
    import pymysql as ms
    
    con = ms.connect(host='127.0.0.1', user='root', passwd='123456', db='mem')
    con.autocommit(True)
    cur = con.cursor()
    
    while True:
        used = psutil.virtual_memory().used
        sql = 'insert into mem_used values(%s,%s)' % (used / 1024 / 1024, int(time.time()))  ## Mb
        cur.execute(sql)
        time.sleep(1)
    
    
    - %s可以接受任意类型的值
    

    后面会将db操作封装成模块.

    说下本次的任务

    • 本次的任务是将apache的access.log的(ip+状态)+出现次数, 入库到mysql里做分析. 分析网站访问的top10
    • 日志样式如下, 以空格分隔 arr[0] arr[8] 分别是ip和状态码
    61.159.140.123 - - [23/Aug/2014:00:01:42 +0800] "GET /favicon.ico HTTP/1.1" 404  "-" "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36 LBBROWSER" "-"
    
    • 通过pymysql模块操作mysql数据库

    • 将结果放在res={}一个大的字典里. 核心统计思路如下. 如果字典无key,则value初始化为1; 如果字典有key,则 以key对应的value +1

    - 为何是这种格式,取决于echarts数据格式所需. 通常是找到一个合适的图,对图中数据胸有程序,即可以看到它需要一些什么数据,在看下echarts代码,看下需要什么格式的数据, 然后后端组装, json.dumps后返回.
    
    res[(ip, status)] = res.get((ip, status), 0) + 1
    
    • 对字典的值排序
    - 遍历日志文件,获取到一个大的字典如下
    res = {('182.145.101.123', '200'): 302, ('139.205.220.131', '404'): 1, ('119.146.75.13', '304'): 10, ('59.39.103.85', '200'): 56, ... }
    
    - 对字典排序 转成 列表  后插入
    print sorted(res.items(), key=lambda x: x[1], reverse=True)
    
    [(('123.174.51.164', '200'), 6930), (('117.63.146.40', '200'), 1457), (('118.112.143.148', '404'), 1336), (('111.85.34.165', '200'), 1325), ...]
    
    
    - 对排序后的列表(字典),字符串拼接sql,并插入数据库
    for i in sorted(res.items(), key=iambda x: x[1], reverse=True):
        sql = "insert into iog vaiues ('%s','%s','%s')" % (i[0][0], i[0][1], i[1])
        db.execute(sql)
    

    代码组织

    入库代码

    log2db.py

    #!/usr/bin/env python
    # coding=utf-8
    
    from dbutils import DB
    
    # 连接mysql
    db = DB(
        host="127.0.0.1",
        user="root",
        passwd="",
        db="logtest"
    )
    
    # 日志处理成一个大的指定格式的字典(已统计好出现的次数) ((ip, status), count)
    res = {}
    with open('log.txt') as f:
        for line in f:
            if line == "
    ":
                continue
            arr = line.split(" ")
            ip = arr[0]
            status = arr[8]
            res[(ip, status)] = res.get((ip, status), 0) + 1
    
    # 组合sql,执行sql入库日志
    for l in sorted(res.items(), key=lambda x: x[1], reverse=True):
        # {('192.168.1.1',404): 1000,('192.168.1.1',403): 3000,('192.168.1.1',200): 2000,}
        sql = "insert into log values ('%s','%s','%s')" % (l[0][0], l[0][1], l[1])
        db.execute(sql)
    
    

    dbutils.py

    #!/usr/bin/env python
    # coding=utf-8
    
    import pymysql as ms
    
    
    class DB:
        def __init__(self, host, user, passwd, db):
            self.host = host
            self.user = user
            self.passwd = passwd
            self.db = db
            self.connect()
    
        def connect(self):
            self.conn = ms.connect(
                host=self.host,
                user=self.user,
                passwd=self.passwd,
                db=self.db
            )
            self.conn.autocommit(True)
            self.cursor = self.conn.cursor()
    
        def execute(self, sql):
            try:
                self.cursor.execute(sql)
            except Exception as e:
                self.cursor.close()
                self.conn.close()
                self.connect()
                return self.execute(sql)
            else:
                return self.cursor
    
        # def query(self,tables):
        #     sql = 'select * from users'
        #     self.cursor.execute(sql)
        #     return self.cursor.fetchall()
    
    

    下载实例日志: https://github.com/lannyMa/flask_info/blob/master/demo5/log.txt

    执行脚本入库

    python log2db.py
    

    结果查看

    入库完成后, flask前端展示+echarts

    将入库的日志通过flask前端展示

    难点是找图形的数据模型,即json数据格式. 后端返回. 所以当一幅图出来后,要对其上面有啥数据,啥数据是后端返回的,要胸有成竹, 通过代码+浏览器f12conson.log打印出来.

    echarts官网上去看最简单实例的教程,用flask展示出来

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>ECharts</title>
        <!-- 引入 echarts.js -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/4.0.2/echarts.min.js"></script>
    </head>
    <body>
    <!-- 为ECharts准备一个具备大小(宽高)的Dom -->
    <div id="main" style=" 600px;height:400px;"></div>
    <script type="text/javascript">
        // 基于准备好的dom,初始化echarts实例
        var myChart = echarts.init(document.getElementById('main'));
    
        // 指定图表的配置项和数据
        var option = {
            title: {
                text: 'ECharts 入门示例'
            },
            tooltip: {},
            legend: {
                data: ['销量']
            },
            xAxis: {
                data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
            },
            yAxis: {},
            series: [{
                name: '销量',
                type: 'bar',
                data: [5, 20, 36, 10, 10, 20]
            }]
        };
    
        // 使用刚指定的配置项和数据显示图表。
        myChart.setOption(option);
    </script>
    </body>
    </html>
    

    echarts.min.js路径替换下

    flask启动展示

    from flask import Flask, render_template
    
    app = Flask(__name__)
    
    
    @app.route('/')
    def index():
        return render_template('demo.html')
    
    if __name__ == '__main__':
        app.run(debug=True)
    

    先找一个合适的echarts图,观察它所需的接口数据格式,后设计接口

    计划使用echarts这个饼图展示

    它的源码如下

    option = {
        title : {
            text: '某站点用户访问来源',
            subtext: '纯属虚构',
            x:'center'
        },
        tooltip : {
            trigger: 'item',
            formatter: "{a} <br/>{b} : {c} ({d}%)"
        },
        legend: {
            orient: 'vertical',
            left: 'left',
            data: ['直接访问','邮件营销','联盟广告','视频广告','搜索引擎']
        },
        series : [
            {
                name: '访问来源',
                type: 'pie',
                radius : '55%',
                center: ['50%', '60%'],
                data:[
                    {value:335, name:'直接访问'},
                    {value:310, name:'邮件营销'},
                    {value:234, name:'联盟广告'},
                    {value:135, name:'视频广告'},
                    {value:1548, name:'搜索引擎'}
                ],
                itemStyle: {
                    emphasis: {
                        shadowBlur: 10,
                        shadowOffsetX: 0,
                        shadowColor: 'rgba(0, 0, 0, 0.5)'
                    }
                }
            }
        ]
    };
    
    

    下载后,修改echarts-pie.html源码

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>ECharts</title>
    
        <!-- 引入 echarts.js -->
        <script src="/static/jquery.min.js"></script>
        <script src="/static/echarts.min.js"></script>
    </head>
    <body>
    
    <!-- 为ECharts准备一个具备大小(宽高)的Dom画布 -->
    <div id="main" style=" 600px;height:400px;"></div>
    
    <script type="text/javascript">
        // 基于准备好的dom,初始化echarts实例
        var myChart = echarts.init(document.getElementById('main'));
    
        // 指定图表的配置项和数据
        var option = {
            title: {
                text: '某站点用户访问来源',
                {#            subtext: '纯属虚构',#}
                x: 'center'
            },
            tooltip: {
                trigger: 'item',
                formatter: "{a} <br/>{b} : {c} ({d}%)"
            },
            toolbox: {
                feature: {
                    saveAsImage: {
                        show: true
                    }
                }
            },
            legend: {
                orient: 'vertical',
                left: 'left',
                {#            data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']#}
            },
            series: [
                {
                    name: '访问来源',
                    type: 'pie',
                    radius: '55%',
                    center: ['50%', '60%'],
                    {#                data: [#}
                    {#                    {value: 335, name: '直接访问'},#}
                    {#                    {value: 310, name: '邮件营销'},#}
                    {#                    {value: 234, name: '联盟广告'},#}
                    {#                    {value: 135, name: '视频广告'},#}
                    {#                    {value: 1548, name: '搜索引擎'}#}
                    {#                ],#}
                    itemStyle: {
                        emphasis: {
                            shadowBlur: 10,
                            shadowOffsetX: 0,
                            shadowColor: 'rgba(0, 0, 0, 0.5)'
                        }
                    }
                }
            ]
        };
    
        //使用jq去访问api获取得到数据.
        $.getJSON('/piedata', function (res) {
            option.legend.data = res.legend
            option.series[0].data = res.data
    
            // 使用刚指定的配置项和数据显示图表-绑定数据
            myChart.setOption(option);
        })
    </script>
    </body>
    </html>
    

    我们知道了前端js需要的数据所需要的数据格式后,就从后端构造这样的api,供前端调用

    - api需返回数据的格式
    {"data": [{"name": 200, "value": 49691}, {"name": 206, "value": 32}, {"name": 301, "value": 2}, {"name": 304, "value": 7584}, {"name": 403, "value": 1}, {"name": 404, "value": 3858}], "legend": [200, 206, 301, 304, 403, 404]}
    
    - js访问接口方法
    $.getJSON('/piedata', function (res) {
        option.legend.data = res.legend
        option.series[0].data = res.data
        myChart.setOption(option);  //获取后直接绑定
    })
    

    设计top10状态码的api

    • 目标是
    即访问
    http://127.0.0.1:5000/piedata
    
    返回json数据:
    
    {"data": [{"name": 200, "value": 49691}, {"name": 206, "value": 32}, {"name": 301, "value": 2}, {"name": 304, "value": 7584}, {"name": 403, "value": 1}, {"name": 404, "value": 3858}], "legend": [200, 206, 301, 304, 403, 404]}
    
    • 查库后得到的结果
    - 用sql语句做数据汇聚,查出status 和 对应的总数, 按照状态码总数来排序
    select status,sum(count) from log group by status;
    

    查询结果如下

    • flask写api
    
    - cur.fetchall返回的结果,需对其遍历,重组一定格式的数据
    ((200, Decimal('49691')), (206, Decimal('32')), (301, Decimal('2')), (304, Decimal('7584')), (403, Decimal('1')), (404, Decimal('3858')))
    
    
    @app.route("/piedata")
    def piedata():
        sql = "select status,sum(count) from log group by status";
        cur = db.execute(sql)
        # 构造前端所需的数据结构
        res = {
            'legend': [],
            'data': []
        }
        for c in cur.fetchall():
            code = c[0]
            count = int(c[1])
            res['legend'].append(code)
            res['data'].append({
                'name': code,
                'value': count
            })
        print(res)
        return json.dumps(res)
    
    
    - 重组后数据格式如下
    {'data': [{'name': 200, 'value': 49691}, {'name': 206, 'value': 32}, {'name': 301, 'value': 2}, {'name': 304, 'value': 7584}, {'name': 403, 'value': 1}, {'name': 404, 'value': 3858}], 'legend': [200, 206, 301, 304, 403, 404]}
    
    - 通过json模块处理后返回给将这批数据返回给前端
    

    最终访问

    实现前端html展示

    @app.route("/")
    def index():
        return render_template("echarts-pie.html")
    
    

    最终效果:

    小结

  • 相关阅读:
    Codeforces 1255B Fridge Lockers
    Codeforces 1255A Changing Volume
    Codeforces 1255A Changing Volume
    leetcode 112. 路径总和
    leetcode 129. 求根到叶子节点数字之和
    leetcode 404. 左叶子之和
    leetcode 104. 二叉树的最大深度
    leetcode 235. 二叉搜索树的最近公共祖先
    450. Delete Node in a BST
    树的c++实现--建立一棵树
  • 原文地址:https://www.cnblogs.com/iiiiiher/p/8244145.html
Copyright © 2011-2022 走看看