使用PHP+Shell+echarts分析nginx日志,统计各个地区的访问情况

统计 

2017.9.10日周末,被人问‘你们公司用户主要集中在个省市’。我当时懵了,因为确实没有统计过这个方面的数据。当时随口说句‘河南’,因为河南人口最多。哈哈哈,挺囧的。工作了2年多居然这个数据不清楚。反正周末闲着也是闲着,就着手统计一下这方面数据.

主要思路:

1、Ip数据的抓取收集
2、分析对比IP找到相应的省市
3、统计IP和省市,利用echarts绘制可视化视图

1、数据的抓取收集

首先创建表结构:

CREATE TABLE `access` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ip` varchar(100) DEFAULT NULL,
`count` int(10) DEFAULT NULL,
`province` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

(1)Python抓取数据

首先我考虑使用的是Python,而不是PHP,为啥那,因为python支持多进程(不怕数据量大)、操作shell简单点(PHP是世界上最好的语言这点我还是信的)

#!/usr/bin/python
#coding=utf-8
# 
# 首先导入os 模块
import os
import MySQLdb
# nginx日志的路径
file_path = "/root"

#ip 排序+统计个数和去重
data = os.popen("cat /var/log/nginx/access.log| awk '{print $14}'|sort|uniq -c")
array_data={}
array_data_value=0
for i in data:
    i = i.strip()
    array_data[array_data_value] = i.split(' ');
    array_data_value = array_data_value+1
print array_data

#建立和数据库系统的连接
conn = MySQLdb.connect(host='localhost', user='homestead',passwd='secret',db='test',charset='utf8',port = 3306)
#conn = MySQLdb.connect(host='localhost', user='root',passwd='V121maiaumediadb',database='new121dbproduce',charset='utf8')

#获取操作游标
cursor = conn.cursor()
#选择数据库
conn.select_db('test');

for x in array_data:
    value1 = (str(array_data[x][1]),str(array_data[x][0]))
    print value1
    try:
        # 我的表名叫access 执行SQL
        cursor.execute("INSERT INTO access (ip,count) VALUES (%s,%s)",value1)
        print "数据id:" + str(cursor.lastrowid)
    except Exception,e:
        print "插入失败,错误类型: " + str(Exception) + "错误原因: " + str(e)

#关闭连接,释放资源
cursor.close();
conn.commit()
conn.close()

(2)PHP抓取数据

大家可以看到上面Python我没有使用多进程,感觉效率还可以,那么这时候我就知道PHP也是可以做的 下面大家看看用PHP代码抓取数据代码

function ttt1Op(){
    set_time_limit(0);
    $fetch_link = "cat  /var/logdata/nginx/access.log| awk '{print $14}'|sort|uniq -c";
    // 执行shell 并将结果集放入到$res变量中
    exec($fetch_link,$res);
    $array_data = array();
    $pattern = '/\d+\.\d+\.\d+\.\d+/';
    foreach ($res as $key => &$value) {
        $value = trim($value);
        $tmp = explode(' ', $value);
        // 校验是合法的ip段
        if(count($tmp)>1 && preg_match($pattern,$tmp[1])){
            $array_data[] = [
                'ip'=>$tmp[1],
                'count'=>$tmp[0]
            ];
        }
    }
    // 本人使用框架代码批量插入调用  大家根据自己的实际情况改写下面的这段代码
    Model('access')->insertAll($array_data);
}

2、数据分析

(1)方法一:使用IP地址库
https://mall.ipplus360.com/product/detail?productId=5
下载埃文科技的ip地址库。根据其提供的手册,导入到mysql中。解压密码:www.ipplus360.com

我这里导入的时候将表名指定成了ipwen_free_district

// 处理ip数据
    // http://www.121mai.com/mobile/index.php?act=sql&op=showtttdata1
    function showtttdata1Op(){
        set_time_limit(0);
        // 这个请看下面的说明!!!
        $n = empty($_GET['n'])?0:$_GET['n'];
        $l = $n+10;

        // 本人使用框架代码声明数据model用来做进一步查询 
        //大家根据自己的实际情况改写下面的这段代码
        $m = Model('access');
        $m1 = Model('ipwen_free_district');
        $data = $m->where('id>'.$n.' and id<='.$l)->limit(1000)->select();
        foreach ($data as $key => $value) {

        /* 相当于sql :
        SELECT `multiarea` FROM `ipwen_free_district` WHERE INET_ATON(ip) BETWEEN `minip` AND `maxip` LIMIT 0,1;*/
            $w['exp'] = array('exp'," INET_ATON('{$value['ip']}') BETWEEN `minip` AND `maxip` ");
            $ip_data = $m1->where($w)->find();

            if($ip_data){
               $tmp = json_decode($ip_data['multiarea'],true);
                if($tmp[0]['p']){
                    $where =array();
                    $where['id']=$value['id'];
                    $update =array();
                    $update['province'] = $tmp[0]['p'];
                    $m->where($where)->update($update);
                }
            }
            sleep(2);
        }
    }

(2)方法二:使用第三方查询接口

function showtttdataOp(){
    set_time_limit(0);
    // 这个请看下面的说明!!!
    $n = empty($_GET['n'])?0:$_GET['n'];
    $l = $n+10;

    $m = Model('access');
    // $url = 'http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=';
    $url ='http://ip.taobao.com/service/getIpInfo.php?ip=';
    $data = $m->where('id>'.$n.' and id<='.$l)->limit(2000)->select();
    foreach ($data as $key => $value) {
        $tmp = $url.$value['ip'];
        sleep(2);
        $d = $this->request_get($tmp);
        $d_arr = json_decode($d,true);
        if($d_arr['data']){
            $where =array();
            $where['id']=$value['id'];
            $update =array();
            $update['province'] = $d_arr['data']['region'];
            $m->where($where)->update($update);
        }
    }
}

 /** 
 * 发送get请求 
 * @param string $url 
 * @return bool|mixed 
 */  
protected function request_get($url = '')  
{  
    if (empty($url)) {  
        return false;  
    }  
    $ch = curl_init();  
    curl_setopt($ch, CURLOPT_URL, $url);  
    curl_setopt($ch, CURLOPT_TIMEOUT, 500);  
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);  
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);  
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  
    $data = curl_exec($ch);  
    curl_close($ch);  
    return $data;  
}

说明:由于我在本地运行的上面代码,碍于性能的问题我一次只执行10条,用shell脚本来控制循环执行 shell脚本如下:

#!/bin/bash
COUNTER=0
#目前只允许最多2560条数据的分析(我是够用了)
while [ $COUNTER -lt 2550 ]
do
    COUNTER=`expr $COUNTER + 10`
    #执行一轮后 休息一分钟
    sleep 60
    #2个方法都掉用 大家可以选一个执行就行 当然性能没问题的话不用这么麻烦
    curl --connect-timeout 50 -m 300 "http://test.121mai.com/mobile/index.php?act=sql&op=showtttdata&n=${COUNTER}"

    curl --connect-timeout 50 -m 300 "http://test.121mai.com/mobile/index.php?act=sql&op=showtttdata1&n=${COUNTER}"
done

3、数据展示

下载echarts图表 http://echarts.baidu.com/demo.html#map-labels , 和相关地图数据,我这里使用的是js文件格式的中国地图,下载地址: http://echarts.baidu.com/download-map.html

注意:由于echarts出来的数据不带“省”“自治区”,所以要去掉,否则图表出不来数据。

PHP代码:

 function showttt1Op(){
        $m = Model('access');
        $data = $m->field('province as name,sum(count) as value')->limit(600)->group('province')->select();
        foreach ($data as $key => &$value) {
            // 处理数据 使得数据能和echarts对应起来
            $value['name'] = str_replace('中国', '', $value['name']);
            $value['name'] = str_replace('市', '', $value['name']);
            $value['name'] = str_replace('省', '', $value['name']);
            $value['name'] = str_replace('自治区', '', $value['name']);
        }
        // 分配数据
        Tpl::output('data',json_encode($data));
        // 指定模板
        Tpl::showpage('iplog');
    }

HTML代码:

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
<meta http-equiv="cache-control" content="no-cache" />

<!-- 引入相关的js -->
<script type="text/javascript" src="<?php echo MOBILE_RESOURCE_SITE_URL; ?>/js/echarts.js" charset="utf-8"></script>
<script type="text/javascript" src="<?php echo MOBILE_RESOURCE_SITE_URL; ?>/js/china.js" charset="utf-8"></script>

<!--创建展示区 -->
<div id="main" style="width: 80%;height:80%;margin:0 auto;"></div>

<script type="text/javascript">
#分配json数据到 data_for js变量中 (注意下面这里利用eval将json字符串 转化为了js对象)
var data_for = eval('(' + '<?php echo $output['data'];?>' + ')');

option = {
    title: {
        text: '121系统访问量',
        subtext: '访问量统计',
        left: 'center'
    },
    tooltip: {
        trigger: 'item'
    },
    legend: {
        orient: 'vertical',
        left: 'left',
        data:['访问量']
    },
    visualMap: {
        min: 0,
        max: 2500,
        left: 'left',
        top: 'bottom',
        text: ['高','低'],           // 文本,默认为数值文本
        calculable: true
    },
    toolbox: {
        show: true,
        orient: 'vertical',
        left: 'right',
        top: 'center',
        feature: {
            dataView: {readOnly: false},
            restore: {},
            saveAsImage: {}
        }
    },
    series: [
        {
            name: '访问量',
            type: 'map',
            mapType: 'china',
            roam: false,
            label: {
                normal: {
                    show: true
                },
                emphasis: {
                    show: true
                }
            },
            data:data_for

        },
    ]
};

var user = echarts.init(document.getElementById('main'));
 // 使用刚指定的配置项和数据显示图表。
 user.setOption(option);
</script>

效果:

file