利用PHP如何统计Nginx日志的User Agent数据


Posted in PHP onMarch 06, 2019

前言

即将用到爬虫,于是打算收集一下User Agent(UA)数据。接着马上想到自己网站的访问日志不就是现成的优质数据源吗?于是愉快的决定写个脚本统计一下Nginx访问日志中的UA信息。

这类简单操作,用脚本语言就足够,毫无疑问肯定要用最熟悉的PHP。打开vim就开撸,十几分钟下来,功能简单的统计脚本就搞定了。

脚本目前有三个功能:

1. 找出所有的UA信息并排序; 2. 统计操作系统数据; 3. 统计浏览器数据。

程序运行截图如下:

1、UA信息

利用PHP如何统计Nginx日志的User Agent数据

2、操作系统信息

利用PHP如何统计Nginx日志的User Agent数据

3、浏览器

利用PHP如何统计Nginx日志的User Agent数据

用脚本统计最近一个月的访问日志,得到以下结果:

  • 搜索引擎爬虫比较频繁,每天有好几千次数据访问;
  • Windows仍是份额最大的操作系统,Linux桌面依然份额很小;
  • Chrome目前是浏览器领域的霸主,其次是Firefox,Opera已经很小众了。

最后附上PHP脚本的代码,也可以从本人的Github里找到:https://github.com/tlanyan/Scripts/blob/master/statUA.php

#!/usr/bin/php
<?php
/**
 * @brief stat UA in access log
 *
 * @author tlanyan<tlanyan@hotmail.com>
 * @link http://tlanyan.me
 */
/* vim: set ts=4; set sw=4; set ss=4; set expandtab; */
function getFileList(string $path) : array {
 return glob(rtrim($path, "/") . "/*access.log*");
}
function statFiles(array $files) : array {
 $stat = [];
 echo PHP_EOL, "start to read files...", PHP_EOL;
 foreach ($files as $file) {
  echo "read file: $file ...", PHP_EOL;
  $contents = getFileContent($file);
  foreach ($contents as $line) {
   $ua = getUA($line);
   if (isset($stat[$ua])) {
    $stat[$ua] += 1;
   } else {
    $stat[$ua] = 1;
   }
  }
 }
 echo "stat all files done!", PHP_EOL, PHP_EOL;
 return $stat;
}
function getFileContent(string $file) : array {
 if (substr($file, -3, 3) === ".gz") {
  return gzfile($file);
 }
 return file($file);
}
function getUA(string $line) : ?string {
 // important! Nginx log format determins the UA location in the line!
 // You may have to refactor following codes to get the right result
 // UA starts from fifth double quote 
 $count = 0; $offset = 0;
 while ($count < 5) {
  $pos = strpos($line, '"', $offset);
  if ($pos === false) {
   echo "Error! Unknown line: $line", PHP_EOL;
   return null;
  }
  $count ++;
  $offset = $pos + 1;
 }
 $end = strpos($line, '"', $offset);
 return substr($line, $offset, $end - $offset);
}
function usage() {
 echo "Usage: php statUA.php [option] [dir]", PHP_EOL;
 echo " options:", PHP_EOL;
 echo " -h: show this help", PHP_EOL;
 echo " -v: verbose mode", PHP_EOL;
 echo "-n NUM: UA list number", PHP_EOL;
 echo " dir: directory to the log files", PHP_EOL;
 echo PHP_EOL;
}
function filterUA(array& $stat, array $UAFilters) {
 $filterCount = 0;
 foreach ($UAFilters as $filter) {
  foreach ($stat as $ua => $count) {
   if (stripos($ua, $filter) !== false) {
    $filterCount += $count;
    unset($stat[$ua]);
   }
  }
 }
 echo "filter $filterCount records!", PHP_EOL;
}
function printCount(array $stat) {
 $sum = array_sum($stat);
 foreach ($stat as $key => $count) {
  echo $key, " : ", $count, ", percent: ", sprintf("%.2f", 100*$count/$sum), PHP_EOL;
 }
}
function statOS(array $UAs) : array {
 global $debug;
 echo PHP_EOL, "stat OS...", PHP_EOL;
 $os = ["Windows", "MacOS", "Linux", "Android", "iOS", "other"];
 $stat = array_fill_keys($os, 0);
 foreach ($UAs as $key => $count) {
  if (strpos($key, "Windows") !== false) {
   $stat["Windows"] += $count;
  } else if (strpos($key, "Macintosh") !== false) {
   $stat["MacOS"] += $count;
  // must deal Android first, then Linux
  } else if (strpos($key, "Android") !== false) {
   $stat["Android"] += $count;
  } else if (strpos($key, "Linux") !== false) {
   $stat["Linux"] += $count;
  } else if (strpos($key, "iPhone") !== false || strpos($key, "iOS") !== false || strpos($key, "like Mac OS") !== false || strpos($key, "Darwin") !== false) {
   $stat["iOS"] += $count;
  } else {
   if ($debug) {
    echo "other: $key, count: $count", PHP_EOL;
   }
   $stat["other"] += $count;
  }
 }
 return $stat;
}
function statBrowser(array $UAs) : array {
 global $debug;
 echo PHP_EOL, "stat brwoser...", PHP_EOL;
 $browsers = ["Chrome", "Firefox", "IE", "Safari", "Edge", "Opera", "other"];
 $stat = array_fill_keys($browsers, 0);
 foreach ($UAs as $key => $count) {
  if (strpos($key, "MSIE") !== false) {
   $stat["IE"] += $count;
  } else if (strpos($key, "Edge") !== false) {
   $stat["Edge"] += $count;
  } else if (strpos($key, "Firefox") !== false) {
   $stat["Firefox"] += $count;
  } else if (strpos($key, "OPR") !== false) {
   $stat["Opera"] += $count;
  // first Chrome, then Safari
  } else if (strpos($key, "Chrome") !== false) {
   $stat["Chrome"] += $count;
  } else if (strpos($key, "Safari") !== false) {
   $stat["Safari"] += $count;
  } else {
   if ($debug) {
    echo "other: $key, count: $count", PHP_EOL;
   }
   $stat["other"] += $count;
  }
 }
 return $stat;
}
function parseCmd() {
 global $debug, $num, $path, $argc, $argv;
 $optind = null;
 $options = getopt("hvn:", [], $optind);
 if ($argc > 2 && empty($options)) {
  usage();
  exit(1);
 }
 if (isset($options['h'])) {
  usage();
  exit(0);
 }
 if (isset($options['v'])) {
  $debug = true;
 }
 if (isset($options['n'])) {
  $num = intval($options['n']);
  if ($num <= 0) {
   $num = 10;
  }
 }
 if ($argc === 2 && empty($options)) {
  $path = $argv[1];
 }
 if ($argc > $optind) {
  $path = $argv[$optind];
 }
 if (!is_dir($path)) {
  echo "invalid directory: $path", PHP_EOL;
  exit(1);
 }
 if ($debug) {
  echo "num: $num", PHP_EOL;
  echo "verbose: ", var_export($debug, true), PHP_EOL;
  echo "path: $path", PHP_EOL;
 }
}
if (version_compare(PHP_VERSION, "7.1") < 0) {
 exit("scripts require PHP >=7.1");
}
$path = ".";
$debug = false;
$num = 10;
$UAFilters = [
 "spider",
 "bot",
 "wget",
 "curl",
];
parseCmd();
$files = getFileList($path);
if (empty($files)) {
 echo '"' . realpath($path) . '" does not contain access log files.', PHP_EOL;
 exit(0);
}
$allUA = statFiles($files);
if (empty($allUA)) {
 echo "no data", PHP_EOL;
 exit(0);
}
filterUA($allUA, $UAFilters);
// sort array with count
uasort($allUA, function ($a, $b) {
 return $b - $a;
});
if ($debug) {
 print_r($allUA);
}
echo PHP_EOL, "---- top $num UA ----", PHP_EOL;
printCount(array_slice($allUA, 0, $num));
echo "-------------------", PHP_EOL;
$os = statOS($allUA);
echo PHP_EOL, "os count:", PHP_EOL;
printCount($os);
$browser = statBrowser($allUA);
echo PHP_EOL, "browser count:", PHP_EOL;
printCount($browser);

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
用PHP连接Oracle数据库
Oct 09 PHP
PHP程序61条面向对象分析设计的经验小结
Nov 12 PHP
php面向对象全攻略 (十六) 对象的串行化
Sep 30 PHP
Zend的AutoLoad机制介绍
Sep 27 PHP
去掉destoon资讯内容页keywords关键字自带的文章标题的方法
Aug 21 PHP
php中常量DIRECTORY_SEPARATOR用法深入分析
Nov 14 PHP
php程序员应具有的7种能力小结
Nov 27 PHP
php实现根据词频生成tag云的方法
Apr 17 PHP
PHP获取文件行数的方法
Jun 10 PHP
PHP设计模式之PHP迭代器模式讲解
Mar 22 PHP
php实现QQ小程序发送模板消息功能
Sep 18 PHP
使用laravel和ajax实现整个页面无刷新的操作方法
Oct 03 PHP
浅谈php://filter的妙用
Mar 05 #PHP
PHP实现微信小程序用户授权的工具类示例
Mar 05 #PHP
统计PHP目录中的文件数方法
Mar 05 #PHP
PHP常见字符串操作函数与用法总结
Mar 04 #PHP
php+Ajax处理xml与json格式数据的方法示例
Mar 04 #PHP
php+Ajax无刷新验证用户名操作实例详解
Mar 04 #PHP
实例介绍PHP删除数组中的重复元素
Mar 03 #PHP
You might like
用PHP制作静态网站的模板框架
2006/10/09 PHP
利用Memcached在php下实现session机制 替换PHP的原生session支持
2010/08/21 PHP
PHP取二进制文件头快速判断文件类型的实现代码
2013/08/05 PHP
php环境套包 dedeampz 伪静态设置示例
2014/03/26 PHP
PHP实现的QQ空间g_tk加密算法
2015/07/09 PHP
javascript new fun的执行过程
2010/08/05 Javascript
jQuery ajax应用总结
2016/06/02 Javascript
对javascript继承的理解
2016/10/11 Javascript
移动端手指放大缩小插件与js源码
2017/05/22 Javascript
Jquery中attr与prop的区别详解
2017/05/27 jQuery
zTree异步加载展开第一级节点的实现方法
2017/09/05 Javascript
JS实现的将html转为pdf功能【基于浏览器端插件jsPDF】
2018/02/06 Javascript
Vue组件教程之Toast(Vue.extend 方式)详解
2019/01/27 Javascript
vuex入门最详细整理
2020/03/04 Javascript
js简单实现自动生成表格功能示例
2020/06/02 Javascript
[02:07]DOTA2超级联赛专访BBC:难忘网吧超神经历
2013/06/09 DOTA
[39:52]2018DOTA2亚洲邀请赛 4.3 突围赛 EG vs Newbee 第一场
2018/04/04 DOTA
采用Psyco实现python执行速度提高到与编译语言一样的水平
2014/10/11 Python
在Python编程过程中用单元测试法调试代码的介绍
2015/04/02 Python
Python中threading模块join函数用法实例分析
2015/06/04 Python
浅谈python编译pyc工程--导包问题解决
2019/03/20 Python
python3连接kafka模块pykafka生产者简单封装代码
2019/12/23 Python
python 装饰器的实际作用有哪些
2020/09/07 Python
Python confluent kafka客户端配置kerberos认证流程详解
2020/10/12 Python
基于第一个PhoneGap(cordova)的应用详解
2013/05/03 HTML / CSS
Debenhams爱尔兰:英国知名的百货公司
2017/01/02 全球购物
Dockers鞋官网:Dockers Shoes
2018/11/13 全球购物
泰国国际航空公司官网:Thai Airways International
2019/12/04 全球购物
幼儿园教师自我鉴定
2014/03/20 职场文书
小学生操行评语大全
2014/04/22 职场文书
银行求职自荐信
2014/06/30 职场文书
求职自我评价怎么写
2015/03/09 职场文书
交通事故被告代理词
2015/05/23 职场文书
2019求职信大礼包
2019/05/15 职场文书
最新的离婚协议书范本!
2019/07/02 职场文书
关于党风廉政建设宣传教育月的活动总结!
2019/08/08 职场文书