首页 > 技术文章 > PHP实现站点pv,uv统计(三)

kudosharry 2014-08-11 14:51 原文

数据分析脚本如下:

//error_reporting(0);
date_default_timezone_set("PRC");
$environment = get_cfg_var('app_flow_develop') ? get_cfg_var('app_flow_develop') : 3;
define('BASEPATH',dirname(dirname(__FILE__)));
switch($environment){
    case 1 :
        require BASEPATH . DIRECTORY_SEPARATOR . 'conf' . DIRECTORY_SEPARATOR . 'config.product.php';
        break;
    case 2 :
        require BASEPATH . DIRECTORY_SEPARATOR . 'conf' . DIRECTORY_SEPARATOR . 'config.product.php';
        break;
    default:
        require BASEPATH . DIRECTORY_SEPARATOR . 'conf' . DIRECTORY_SEPARATOR . 'config.develop.php';
        break;
}
//删除其他的配置项
unset($tjdomain);unset($config);unset($interval);unset($replace_a);
require BASEPATH . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'logger.class.php';
//0//cc : 是否支持写数据到浏览器(cookie,sessionStorage,localStorage,userData)
//1//ck : 是否支持cookie
//2//cl : 浏览器颜色位数
//3//ds : 浏览器分辨率
//4//fl : flash版本
//5//ja : 是否支持java
//6//ln : 浏览器语言
//7//si : 统计id,站点标示
//8//su : 来源url,只取了域名
//9//kd : 关键词
//10//tt : 站点title
//11//cf : m点 1,站点0
//12//uid : 会员id
//13//pt : 平台
//14//ocu : 操作系统
//15//ua : 浏览器类型
//16//tm : 请求时间
//17//ip : 请求ip
//18//u  : 站点url
//19//uuid:唯一用户标识
//20//basesu:原始su
//21//baseu:原始u
//22//basekd:原始关键词
//23//u1:一级连接
//24//u2:二级连接
//25//u3:三级连接
//26//u4:四级连接

//获得时间节点
$time = time();
$Ymd = date("Ymd",$time);
$H = date("G",$time);
$i = floor(date("i",$time)/5);

//处理昨天的最后一个五分钟区间
if(0 == $i && 0 == $H){
    $Ymd = date("Ymd",strtotime('-1 days'));
    $H = 23;
    $i = 11;
}
//处理今天上一个小时的最后一个五分钟区间
elseif(0 == $i && 0 != $H){
    $H--;
    $i = 11;
}
//处理上一个五分钟区间
else{
    $i--;
}

//手动控制脚本的执行目录
if(isset($_SERVER['argv'][1]) && $_SERVER['argv'][1]){
    if(isset($_SERVER['argv'][2]) && isset($_SERVER['argv'][3]) &&
        $_SERVER['argv'][2] >=0 &&
        $_SERVER['argv'][2] < 24 &&
        $_SERVER['argv'][3] >=0 &&
        $_SERVER['argv'][3] <12)
    {
        $Ymd = strval($_SERVER['argv'][1]);
        $H = strval($_SERVER['argv'][2]);
        $i = strval($_SERVER['argv'][3]);
        $handd0 = true;
    }else{
        echo 'parma error';
    }
}

//设置redis的key与操作目录地址
$LOG_PATH = $LOG_BASE . DIRECTORY_SEPARATOR . $Ymd . DIRECTORY_SEPARATOR . $H . DIRECTORY_SEPARATOR . $i;
$HOUR_PATH = dirname($LOG_PATH);
$REDIS_DAY_KEY = $Ymd;
$REDIS_HOUR_KEY = "{$Ymd}:{$H}";

$log_base = new Logger($LOG_BASE . DIRECTORY_SEPARATOR  . 'error.log');

//小时路径下的记录句柄,先检查有没有小时目录,没有的话报错
if(file_exists($HOUR_PATH)){
    $log_hour = new Logger($HOUR_PATH . DIRECTORY_SEPARATOR  . 'error.log');
}else{
    if(isset($handd0) && true === $handd0){
        echo $HOUR_PATH . ' is not exists please check the dir and retry';
        $log_base -> error($HOUR_PATH . ' is not exists by hand');
    }else{
        $log_base -> error($HOUR_PATH . ' is not exists ');
    }
    exit;
}
//如果有日志
//这里的代码可以封装成一个类,当做一种对最小单元的一个处理方法,或者可以换成其他语言的脚本的调用,也方便单独手动处理数据
//优化可以使用popen,proc_open,pcntl等方法多进程来处理
if(file_exists($LOG_PATH)){
    //分钟路径下的记录句柄
    $log_do = new Logger($LOG_PATH . DIRECTORY_SEPARATOR  . 'do.txt');
    //切换目录,不然window下报错
    chdir($LOG_PATH);
    //判断是否完成这个区间
    if(!file_exists($LOG_PATH . '/done.txt')){
        //生成合并文件
        if(!file_exists($LOG_PATH . '/all.log')){
            $log_do -> notice(" combine files starts ");
            if(PHP_OS == 'WINNT'){
                system("copy *.log all.log");
            } else {
                system("cat *.log >> all.log");
            }
            //再次判断
            if(!file_exists($LOG_PATH . '/all.log')){
                $log_hour -> error(" No.{$i} all log combine error");
                exit;
            }
            $log_do -> notice(" combine files ends ");
        }
        //读取文件写redis缓存
        $log_do -> notice(" read all_log starts ");
        
        if($fp  =  fopen ($LOG_PATH . '/all.log',  "r")){
            if(flock($fp , LOCK_SH )) {
                //初始化
                $redis = new Redis();
                $re = $redis->pconnect($REDIS_IP,$REDIS_PORT);
                if(!$re){
                    $REDIS_FLAG = FALSE;
                }else{
                    $REDIS_FLAG = TRUE;
                    //全站新的ip,uv,uid
                    $uv_new = $uid_new = $ip_new = array();
                    //u的新uv,su的新uv,kw的新uv
                    $u_uv = $su_uv = $kw_uv = array();

                }
                $ii=1;
                //记录这段时间的pv
                $si_array = $uv_array = $uid_array = $ip_array = $u_array = $su_array = $kw_array = array();
                
                //读取文件,填充数组
                while($str = fgets($fp)){
                    //移除回车符
                    $str = trim($str,PHP_EOL);
                    $a = explode('~^~',$str);
                    if(27 != count($a)){
                        $log_do -> notice(" No.{$ii} data is bad type ");
                        continue;
                    }
                    //填充数组
                    if($a[7]){
                        //处理si,根据si再次初始化数组
                        //记录每个站点这段时间的pv总数
                        /*
                        $si_array(
                            '站点一'=>10,
                            '站点二'=>'20'
                        );
                        */
                        if(isset($si_array[$a[7]])) {
                            $si_array[$a[7]] += 1;
                        } else {
                            $si_array[$a[7]] = 1;
                            $uv_new[$a[7]] = $uid_new[$a[7]] = $ip_new[$a[7]] = array();
                            $u_uv[$a[7]] = $su_uv[$a[7]] = $kw_uv[$a[7]] = array();
                            $uv_array[$a[7]] = $uid_array[$a[7]] = $ip_array[$a[7]] = $kw_array[$a[7]] = $su_array[$a[7]] = $u_array[$a[7]] = array();
                            $si_array[$a[7]] = 1;
                        }
                        

                        //处理uv
                        if($a[19]){
                            //记录uv,记录后得到每个站点,每个uuid这段时间的pv数量
                            /*
                            $uv_array = array(
                                '站点一'=>array(
                                    'uuid1'=>10,
                                    'uuid2'=>20,
                                )
                            )
                            */
                            isset($uv_array[$a[7]][$a[19]]) ? $uv_array[$a[7]][$a[19]] += 1 : $uv_array[$a[7]][$a[19]] = 1;
                            //记录每个站点,这段时间的新的uv数量
                            /*
                            $uv_new = array(
                                '站点一'=>array(
                                    'uuid1','uuid2'
                                )
                            )
                            */
                            if($REDIS_FLAG){
                                //不在这个时间段记录的数据里
                                if(!in_array($a[19],$uv_new[$a[7]])){
                                    $exist = $redis->HEXISTS("{$REDIS_DAY_KEY}:{$a[7]}:UV:HASH",$a[19]);
                                    //不在今天的历史数据里(即新的uuid)
                                    if(!$exist){
                                        $uv_new[$a[7]][]= $a[19];
                                    }
                                }
                            }
                        }
                        
                        //处理uid
                        if($a[12]){
                            //记录uid,记录后得到每个站点,每个uid这段时间的pv数量
                            /*
                            $uid_array = array(
                                '站点一'=>array(
                                    'uid1'=>10,
                                    'uid2'=>20,
                                )
                            )
                            */
                            isset($uid_array[$a[7]][$a[12]]) ? $uid_array[$a[7]][$a[12]] += 1 : $uid_array[$a[7]][$a[12]] = 1;
                            //记录每个站点,这段时间的新的uid数量
                            /*
                            $uid_new = array(
                                '站点一'=>array(
                                    'uid1','uid2'
                                )
                            )
                            */
                            if($REDIS_FLAG){
                                if(!in_array($a[12],$uid_new[$a[7]])){
                                    $exist = $redis->HEXISTS("{$REDIS_DAY_KEY}:{$a[7]}:UID:HASH",$a[12]);
                                    if(!$exist){
                                        $uid_new[$a[7]][]= $a[12];
                                    }
                                }
                            }
                        }
                        
                        //处理ip
                        if($a[17]){
                            //记录ip,记录后得到每个站点,每个ip这段时间的pv数量
                            /*
                            $ip_array = array(
                                '站点一'=>array(
                                    'ip1'=>10,
                                    'ip2'=>20,
                                )
                            )
                            */
                            isset($ip_array[$a[7]][$a[17]]) ? $ip_array[$a[7]][$a[17]] += 1 : $ip_array[$a[7]][$a[17]] = 1;
                            //记录每个站点,这段时间的新的ip数量
                            /*
                            $ip_new = array(
                                '站点一'=>array(
                                    'ip1','ip2'
                                )
                            )
                            */
                            if($REDIS_FLAG){
                                if(!in_array($a[17],$ip_new[$a[7]])){
                                    $exist = $redis->HEXISTS("{$REDIS_DAY_KEY}:{$a[7]}:IP:HASH",$a[17]);
                                    if(!$exist){
                                        $ip_new[$a[7]][]= $a[17];
                                    }
                                }
                            }
                        }
                        
                        
                        //特殊处理的项
                        //处理u
                        if($a[18]){
                            //处理一级Url
                            reg_u_common_op(23);
                            //处理二级Url
                            reg_u_common_op(24);
                            //处理三级Url
                            reg_u_common_op(25);
                            //处理四级Url
                            reg_u_common_op(26);
                        }
                        
                        //处理kw
                        if($a[9]){
                            //记录kw,记录后得到每个站点,每个kw这段时间的pv数量
                            /*
                            $kw_array = array(
                                '站点一'=>array(
                                    'kw1'=>10,
                                    'kw2'=>20,
                                )
                            )
                            */
                            isset($kw_array[$a[7]][$a[9]]) ? $kw_array[$a[7]][$a[9]] += 1 : $kw_array[$a[7]][$a[9]] = 1;
                            //记录每个站点,这段时间kw的新的uv数量
                            /*
                            $kw_new = array(
                                '站点一'=>array(
                                    'kw1'=>array(
                                        'uuid1','uuid2'
                                    )
                                )
                            )
                            */
                            if($REDIS_FLAG){
                                if(!isset($kw_uv[$a[7]][$a[9]]) || !in_array($a[19],$kw_uv[$a[7]][$a[9]])){
                                    $exist = $redis->HEXISTS("{$REDIS_DAY_KEY}:{$a[7]}:{$a[9]}:KW:UV:HASH",$a[19]);
                                    if(!$exist){
                                        $kw_uv[$a[7]][$a[9]][]= $a[19];
                                    }
                                }
                            }
                        }
                        
                        
                        //处理su
                        if($a[8]){
                            //记录su,记录后得到每个站点,每个su这段时间的pv数量
                            /*
                            $su_array = array(
                                '站点一'=>array(
                                    'su1'=>10,
                                    'su2'=>20,
                                )
                            )
                            */
                            isset($su_array[$a[7]][$a[8]]) ? $su_array[$a[7]][$a[8]] += 1 : $su_array[$a[7]][$a[8]] = 1;
                            //记录每个站点,这段时间su的新的uv数量
                            /*
                            $kw_new = array(
                                '站点一'=>array(
                                    'kw1'=>array(
                                        'uuid1','uuid2'
                                    )
                                )
                            )
                            */
                            if($REDIS_FLAG){
                                if(!isset($su_uv[$a[7]][$a[8]]) || !in_array($a[19],$su_uv[$a[7]][$a[8]])){
                                    $exist = $redis->HEXISTS("{$REDIS_DAY_KEY}:{$a[7]}:{$a[8]}:SU:UV:HASH",$a[19]);
                                    if(!$exist){
                                        $su_uv[$a[7]][$a[8]][]= $a[19];
                                    }
                                }
                            }
                        }
                        

                    }else{
                        $log_do -> notice(" No.{$ii} data empty si ");
                        continue;
                    }

                    $ii++;
                    unset($a);
                }
                $log_do -> notice(" read all_log is complete ");
                
                //填充redis
                //记录redis记录开始
                if($REDIS_FLAG){
                    $log_do -> notice(" set redis start ");
                    //记录站点的pv总数
                    foreach($si_array as $k => $v){
                        $redis->HINCRBY("{$REDIS_DAY_KEY}:{$k}:QZ:HASH","PV",$v);
                        $redis->HINCRBY("{$REDIS_HOUR_KEY}:{$k}:QZ:HASH","PV",$v);
                    }
                    
                    //记录站点的uv总数
                    foreach($uv_new as $k => $v){
                        $uv_num = count($v);
                        $redis->HINCRBY("{$REDIS_DAY_KEY}:{$k}:QZ:HASH","UV",$uv_num);
                        $redis->HINCRBY("{$REDIS_HOUR_KEY}:{$k}:QZ:HASH","UV",$uv_num);
                        //插入当天的uv唯一hash
                        $flip_uv = array_flip($v);
                        $redis->HMSET("{$REDIS_DAY_KEY}:{$k}:UV:HASH",$flip_uv);
                    }
                    
                    //记录站点的uid总数
                    foreach($uid_new as $k => $v){
                        $uid_num = count($v);
                        $redis->HINCRBY("{$REDIS_DAY_KEY}:{$k}:QZ:HASH","UID",$uid_num);
                        $redis->HINCRBY("{$REDIS_HOUR_KEY}:{$k}:QZ:HASH","UID",$uid_num);
                        //插入当天的uv唯一hash
                        $flip_uid = array_flip($v);
                        $redis->HMSET("{$REDIS_DAY_KEY}:{$k}:UID:HASH",$flip_uid);
                    }
                    
                    //记录站点的ip总数
                    foreach($ip_new as $k => $v){
                        $ip_num = count($v);
                        $redis->HINCRBY("{$REDIS_DAY_KEY}:{$k}:QZ:HASH","IP",$ip_num);
                        $redis->HINCRBY("{$REDIS_HOUR_KEY}:{$k}:QZ:HASH","IP",$ip_num);
                        //插入当天的uv唯一hash
                        $flip_ip = array_flip($v);
                        $redis->HMSET("{$REDIS_DAY_KEY}:{$k}:IP:HASH",$flip_ip);
                    }
                    
                    //记录站点u的pv,uv
                    foreach($u_uv as $k => $v){
                        foreach($v as $kk => $vv){
                            $u_uv_num = count($vv);
                            $u_pv_num = $u_array[$k][$kk];
                            $redis->ZINCRBY("{$REDIS_DAY_KEY}:{$k}:U:PV:ZSET",$u_pv_num,$kk);
                            $redis->ZINCRBY("{$REDIS_HOUR_KEY}:{$k}:U:PV:ZSET",$u_pv_num,$kk);
                            
                            $redis->ZINCRBY("{$REDIS_DAY_KEY}:{$k}:U:UV:ZSET",$u_uv_num,$kk);
                            $redis->ZINCRBY("{$REDIS_HOUR_KEY}:{$k}:U:UV:ZSET",$u_uv_num,$kk);
                            //插入当天的uv唯一hash
                            $flip_u_uv = array_flip($vv);
                            $redis->HMSET("{$REDIS_DAY_KEY}:{$k}:{$kk}:U:UV:HASH",$flip_u_uv);
                        }
                    }
                    
                    //记录站点kw的pv,uv
                    foreach($kw_uv as $k => $v){
                        foreach($v as $kk => $vv){
                            $kw_uv_num = count($vv);
                            $kw_pv_num = $kw_array[$k][$kk];
                            $redis->ZINCRBY("{$REDIS_DAY_KEY}:{$k}:KW:PV:ZSET",$kw_pv_num,$kk);
                            $redis->ZINCRBY("{$REDIS_HOUR_KEY}:{$k}:KW:PV:ZSET",$kw_pv_num,$kk);
                            
                            $redis->ZINCRBY("{$REDIS_DAY_KEY}:{$k}:KW:UV:ZSET",$kw_uv_num,$kk);
                            $redis->ZINCRBY("{$REDIS_HOUR_KEY}:{$k}:KW:UV:ZSET",$kw_uv_num,$kk);
                            //插入当天的uv唯一hash
                            $flip_kw_uv = array_flip($vv);
                            $redis->HMSET("{$REDIS_DAY_KEY}:{$k}:{$kk}:KW:UV:HASH",$flip_kw_uv);
                        }
                    }
                    
                    //记录站点su的pv,uv
                    foreach($su_uv as $k => $v){
                        foreach($v as $kk => $vv){
                            $su_uv_num = count($vv);
                            $su_pv_num = $su_array[$k][$kk];
                            $redis->ZINCRBY("{$REDIS_DAY_KEY}:{$k}:SU:PV:ZSET",$su_pv_num,$kk);
                            $redis->ZINCRBY("{$REDIS_HOUR_KEY}:{$k}:SU:PV:ZSET",$su_pv_num,$kk);
                            
                            $redis->ZINCRBY("{$REDIS_DAY_KEY}:{$k}:SU:UV:ZSET",$su_uv_num,$kk);
                            $redis->ZINCRBY("{$REDIS_HOUR_KEY}:{$k}:SU:UV:ZSET",$su_uv_num,$kk);
                            //插入当天的uv唯一hash
                            $flip_su_uv = array_flip($vv);
                            $redis->HMSET("{$REDIS_DAY_KEY}:{$k}:{$kk}:SU:UV:HASH",$flip_su_uv);
                        }
                    }
                    $log_do -> notice(" set redis is complete ");
                }
                //记录redis记录结束
                
                
                //记录统计数据
                //记录统计记录开始
                $log_do -> notice(" write analyse data starts ");
                if($REDIS_FLAG){
                    file_put_contents($LOG_PATH . '/uv_new.log', var_export(json_encode($uv_new),true), FILE_APPEND);
                    
                    file_put_contents($LOG_PATH . '/uid_new.log', var_export(json_encode($uid_new),true), FILE_APPEND);
                    
                    file_put_contents($LOG_PATH . '/ip_new.log', var_export(json_encode($ip_new),true), FILE_APPEND);
                    
                    file_put_contents($LOG_PATH . '/u_uv.log', var_export(json_encode($u_uv),true), FILE_APPEND);
                    
                    file_put_contents($LOG_PATH . '/su_uv.log', var_export(json_encode($su_uv),true), FILE_APPEND);
                    
                    file_put_contents($LOG_PATH . '/kw_uv.log', var_export(json_encode($kw_uv),true), FILE_APPEND);
                }
                
                file_put_contents($LOG_PATH . '/uv_array.log', var_export(json_encode($uv_array),true), FILE_APPEND);
                
                file_put_contents($LOG_PATH . '/uid_array.log', var_export(json_encode($uid_array),true), FILE_APPEND);
                
                file_put_contents($LOG_PATH . '/ip_array.log', var_export(json_encode($ip_array),true), FILE_APPEND);
                
                file_put_contents($LOG_PATH . '/kw_array.log', var_export(json_encode($kw_array),true), FILE_APPEND);
                
                file_put_contents($LOG_PATH . '/su_array.log', var_export(json_encode($su_array),true), FILE_APPEND);
                
                file_put_contents($LOG_PATH . '/u_array.log', var_export(json_encode($u_array),true), FILE_APPEND);
                
                file_put_contents($LOG_PATH . '/si_array.log', var_export(json_encode($si_array),true), FILE_APPEND);
                //记录统计记录完毕
                $log_do -> notice(" write analyse data is complete ");
                
                //释放变量
                if($REDIS_FLAG){
                    unset($ip_new);
                    unset($uv_new);
                    unset($uid_new);
                    unset($u_uv);
                    unset($su_uv);
                    unset($kw_uv);
                }
                unset($uv_array);
                unset($uid_array);
                unset($ip_array);
                unset($kw_array);
                unset($su_array);
                unset($u_array);
                
                $log_do -> notice(" release var is complete ");
                flock ( $fp , LOCK_UN);
            } else {
                $log_do -> notice(" all.log flock failed ");
            }
            fclose ( $fp );
        }else{
            $log_hour -> error(" No.{$i} all.log fopen error ");
            exit;
        }
        //记录完成
        $now = date("Y-m-d H:i:s");
        $log_do -> notice(" No.{$i} job done at {$now} ");
        file_put_contents($LOG_PATH . DIRECTORY_SEPARATOR . 'done.txt', $now . " No.{$i} job is done" . PHP_EOL, FILE_APPEND);
        $log_hour -> notice(" No.{$i} job done at {$now} ");
        if(isset($handd0) && true === $handd0){
            echo " No.{$i} job done at {$now} ";
        }
    }else{
        $log_hour -> notice(" No.{$i} alread done ");
    }
}else{
    $log_hour -> error(" No.{$i} dir not exist ");
}

//处理各级url的通用方法
function reg_u_common_op($u_no){
    //引入全局变量
    global $a,$u_array,$REDIS_DAY_KEY,$redis,$REDIS_FLAG,$u_uv;
    if($a[$u_no]){
        //记录u,记录后得到每个站点,每个url这段时间的pv数量
        /*
        $u_array = array(
            '站点一'=>array(
                'u1'=>10,
                'u2'=>20,
            )
        )
        */
        isset($u_array[$a[7]][$a[$u_no]]) ? $u_array[$a[7]][$a[$u_no]] += 1 : $u_array[$a[7]][$a[$u_no]] = 1;
        //记录每个站点,这段时间的u新的uv数量
        /*
        $u_uv = array(
            '站点一'=>array(
                'URL一'=>array(
                    'uuid1','uuid2'
                )
            )
        )
        */
        if($REDIS_FLAG){
            if(!isset($u_uv[$a[7]][$a[$u_no]]) || !in_array($a[19],$u_uv[$a[7]][$a[$u_no]])){
                $exist = $redis->HEXISTS("{$REDIS_DAY_KEY}:{$a[7]}:{$a[$u_no]}:U:UV:HASH",$a[19]);
                if(!$exist){
                    $u_uv[$a[7]][$a[$u_no]][]= $a[19];
                }
            }
        }
    }
}
exit;

附上nginx的server配置:

server {
    listen 80;
    server_name 18touch.tongji.com;
    index index.php index.html index.htm;
    root /home/tongji/webroot;
    
    rewrite ^/h\.js$ /js.php last;
    rewrite ^/m\.gif$ /hm.php last;
    
    include global/restrictions.conf;
    
    location ~ \.php$ { 
        fastcgi_pass  php78;
        fastcgi_index index.php; 
        fastcgi_param SCRIPT_FILENAME /home/tongji/webroot$fastcgi_script_name; 
        include fastcgi_params; 
    }
    
    location ~ /888/$ { 
        empty_gif;
        fastcgi_pass  php78;
        fastcgi_index index.php; 
        fastcgi_param SCRIPT_FILENAME /home/tongji/webroot/hm.php; 
        include fastcgi_params;
    }
}

剩下从redis每日获取昨天的统计数据,然后存储到文本或者mysql。

PS:对目录的操作在win与linux下的函数效果不同,注意文件夹的权限,写文本也有不同。

统计数据从文本返回到php的脚本:

$fp  =  fopen ('uv_array.log',  "r");
while($str = fgets($fp)){
    $str = "\$str = " . trim($str,PHP_EOL) .";";
    eval($str);
    $a = json_decode($str,true);
    var_dump($a);exit;
}

 

推荐阅读