在第一篇文章中,重点介绍了脚本需要搜集的数据,而本篇主要介绍的是服务器端如何处理客户端发送过来的请求和参数。

一、设备信息检测

  通过分析User-Agent请求首部(如下图红线框出的部分),可以得到相关的设备信息。

  Piwik系统专门有一套代码用来分析代理信息,还独立了出来,叫做DeviceDetector。它有一个专门的demo页面,可以展示其功能,点进去后可以看到下图中的内容。

  它能检测出浏览器名称、浏览器的渲染引擎、浏览器的版本、设备品牌(例如HTC、Apple、HP等)、设备型号(例如iPad、Nexus 5、Galaxy S5等)、设备类别(例如desktop、smartphone、tablet等),这6类数据中的可供选择的关键字,可以参考“List of segments”或插件的“readme”。顺便说一下,Piwik还能获取到访客的定位信息,在“List of segments”中,列举出了城市、经纬度等信息,其原理暂时还没研究。

  Piwik为大部分设备信息的关键字配备了一个icon图标,所有的icon图标被放置在“plugins\Morpheus\icons”中,包括浏览器、设备、国旗、操作系统等,下图截取的是浏览器中的部分图标。

二、IP地址

  在Piwik系统的后台设置中,可以选择IP地址的获取方式(如下图所示)。在官方博客的一篇《Geo Locate your visitors》博文中提到,3.5版本后可以在系统中嵌入MaxMind公司提供的IP地理定位服务(GeoIP2)。

  下面是一张看官方的产品介绍表,从描述中可看出这是一项非常厉害的服务。不过需要注意的是,这是一项付费服务。

三、日志数据和归档数据

  在官方发布的说明文档《How Matomo (formerly Piwik) Works》中提到,在Piwik中有两种数据类型:日志数据和归档数据。日志数据(Log Data)是一种原始分析数据,从客户端发送过来的参数就是日志数据,刚刚设备检测到的信息也是日志数据,还有其它的一些日志数据的来源,暂时还没细究。由于日志数据非常巨大,因此不能直接生成最终用户可看的报告,得使用归档数据来生成报告。归档数据(Archive Data)是以日志数据为基础而构建出来的,它是一种被缓存并且可用于生成报告的聚合分析数据。

  日志数据会通过“core\Piwik\Tracker\Visit.php”中的方法保存到数据库中,其中核心的方法如下所示,注释中也强调了该方法中的内容是处理请求的主要逻辑。该方法涉及到了很多对象,以及对象的方法,错综复杂,我自己也没有研究透,只是利用PHPStorm编辑器自动索引,查找出了一些关联,具体细节还有待考证。

/**
* Main algorithm to handle the visit.
*
* Once we have the visitor information, we have to determine if the visit is a new or a known visit.
*
* 1) When the last action was done more than 30min ago,
* or if the visitor is new, then this is a new visit.
*
* 2) If the last action is less than 30min ago, then the same visit is going on.
* Because the visit goes on, we can get the time spent during the last action.
*
* NB:
* - In the case of a new visit, then the time spent
* during the last action of the previous visit is unknown.
*
* - In the case of a new visit but with a known visitor,
* we can set the 'returning visitor' flag.
*
* In all the cases we set a cookie to the visitor with the new information.
*/
public function handle() {
foreach ($this->requestProcessors as $processor) {
Common::printDebug("Executing " . get_class($processor) . "::manipulateRequest()...");
$processor->manipulateRequest($this->request);
}
$this->visitProperties = new VisitProperties();
foreach ($this->requestProcessors as $processor) {
Common::printDebug("Executing " . get_class($processor) . "::processRequestParams()...");
$abort = $processor->processRequestParams($this->visitProperties, $this->request);
if ($abort) {
Common::printDebug("-> aborting due to processRequestParams method");
return;
}
}
$isNewVisit = $this->request->getMetadata('CoreHome', 'isNewVisit');
if (!$isNewVisit) {
$isNewVisit = $this->triggerPredicateHookOnDimensions($this->getAllVisitDimensions() , 'shouldForceNewVisit');
$this->request->setMetadata('CoreHome', 'isNewVisit', $isNewVisit);
}
foreach ($this->requestProcessors as $processor) {
Common::printDebug("Executing " . get_class($processor) . "::afterRequestProcessed()...");
$abort = $processor->afterRequestProcessed($this->visitProperties, $this->request);
if ($abort) {
Common::printDebug("-> aborting due to afterRequestProcessed method");
return;
}
}
$isNewVisit = $this->request->getMetadata('CoreHome', 'isNewVisit');
// Known visit when:
// ( - the visitor has the Piwik cookie with the idcookie ID used by Piwik to match the visitor
// OR
// - the visitor doesn't have the Piwik cookie but could be match using heuristics @see recognizeTheVisitor()
// )
// AND
// - the last page view for this visitor was less than 30 minutes ago @see isLastActionInTheSameVisit()
if (!$isNewVisit) {
try {
$this->handleExistingVisit($this->request->getMetadata('Goals', 'visitIsConverted'));
}
catch(VisitorNotFoundInDb $e) {
$this->request->setMetadata('CoreHome', 'visitorNotFoundInDb', true); // TODO: perhaps we should just abort here? }
}
// New visit when:
// - the visitor has the Piwik cookie but the last action was performed more than 30 min ago @see isLastActionInTheSameVisit()
// - the visitor doesn't have the Piwik cookie, and couldn't be matched in @see recognizeTheVisitor()
// - the visitor does have the Piwik cookie but the idcookie and idvisit found in the cookie didn't match to any existing visit in the DB
if ($isNewVisit) {
$this->handleNewVisit($this->request->getMetadata('Goals', 'visitIsConverted'));
}
// update the cookie with the new visit information
$this->request->setThirdPartyCookie($this->request->getVisitorIdForThirdPartyCookie());
foreach ($this->requestProcessors as $processor) {
Common::printDebug("Executing " . get_class($processor) . "::recordLogs()...");
$processor->recordLogs($this->visitProperties, $this->request);
}
$this->markArchivedReportsAsInvalidIfArchiveAlreadyFinished();
}

  最后了解一下Piwik的数据库设计,此处只分析与日志数据和归档数据有关的数据表。官方的说明文档曾介绍,日志数据有5张相关的数据表,我对于表的内在含义还比较模糊,因此下面所列的描述还不是很清晰。

(1)log_visit:每次访问都会生成一条访问者记录,表中的字段可参考“Visits”。

(2)log_action:网站上的访问和操作类型(例如特定URL、网页标题),可分析出访问者感兴趣的页面,表中的字段可参考“Action Types”。

(3)log_link_visit_action:访问者在浏览期间执行的操作,表中的字段可参考“Visit Actions”。

(4)log_conversion:访问期间发生的转化(与目标相符的操作),表中的字段可参考“Conversions”。

(5)log_conversion_item:与电子商务相关的信息,表中的字段可参考“Ecommerce items”。

  归档数据的表有两种前缀,分别是“archive_numeric_”和“archive_blob_”,表的字段可参考“Archive data”。通过对字段的观察可知,两种最大的不同就是value字段的数据类型。archive_numeric_* 表中的value能储存数值(数据类型是Double),而archive_blob_* 表中的value能储存出数字以外的其他任何数据(数据类型是Blob)。

  两种表都是动态生成的,因此前缀的后面都用“*”表示。生成规则可按年、月、周、天或自定义日期范围,不设置的话,默认是按月计算,例如archive_numeric_2018_09、archive_blob_2018_09。

参考资料:

开源网站分析软件Piwik的数据库表结构

Piwik运转原理

How Matomo (formerly Piwik) Works

Database schema

数据分析技术白皮书

What data does Matomo track?

Segmentation in the API

device-detector

Device Detector demo page

Geo Locate your visitors

开源网站流量统计系统Piwik源码分析——后台处理(二)的更多相关文章

  1. 开源网站流量统计系统Piwik源码分析——参数统计(一)

    Piwik现已改名为Matomo,这是一套国外著名的开源网站统计系统,类似于百度统计.Google Analytics等系统.最大的区别就是可以看到其中的源码,这正合我意.因为我一直对统计的系统很好奇 ...

  2. 开源网站访问统计系统Piwik

    http://www.piwik.cn/ http://www.piwik.org/ Piwik 是一套基于 Php+MySQL 技术构建,能够与 Google Analytics 相媲美的开源网站访 ...

  3. 网站流量统计系统 phpMyVisites

    phpMyVisites是一个网站流量统计系统,它能够提供非常详细的统计报告和高级图形报表.phpMyVisites不是一个Apache log分析工具,它建有自己的log.它的特点包括: 安装部署: ...

  4. 手机自动化测试:appium源码分析之bootstrap二

    手机自动化测试:appium源码分析之bootstrap二   在bootstrap项目中的io.appium.android.bootstrap.handler包中的类都是对应的指令类, priva ...

  5. [软件测试]网站压测工具Webbench源码分析

    一.我与webbench二三事 Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能.Webbench ...

  6. 【精】EOS智能合约:system系统合约源码分析

    系统合约在链启动阶段就会被部署,是因为系统合约赋予了EOS链资源.命名拍卖.基础数据准备.生产者信息.投票等能力.本篇文章将会从源码角度详细研究system合约. 关键字:EOS,eosio.syst ...

  7. ABP源码分析三十二:ABP.SignalR

    Realtime Realtime是ABP底层模块提供的功能,用于管理在线用户.它是使用SignalR实现给在线用户发送通知的功能的前提 IOnlineClient/OnlineClient: 封装在 ...

  8. Thinkphp源码分析系列(二)–引导类

    在上一章我们说到,ThinkPHP.php在设置完框架所需要的变量和调教好环境后,在最后调用了  Think\Think::start();  即Think命名空间中的Think类的静态方法start ...

  9. Netty源码分析 (十二)----- 心跳服务之 IdleStateHandler 源码分析

    什么是心跳机制? 心跳说的是在客户端和服务端在互相建立ESTABLISH状态的时候,如何通过发送一个最简单的包来保持连接的存活,还有监控另一边服务的可用性等. 心跳包的作用 保活Q:为什么说心跳机制能 ...

随机推荐

  1. js多维数组转一维数组

    1,使用map方法 var arr = [1,[2,[[3,4],5],6]]; function unid(arr){ var arr1 = (arr + '').split(',');//将数组转 ...

  2. 杨其菊201771010134《面向对象程序设计(Java)》第三周学习总结

    <面向对象程序设计(Java)>第三周学习总结 第一部分:理论知识 这周课程没有新进度,由于感觉对基础语法的不熟悉,复习了一遍前三章的细碎知识,学到一些之前不知道的原理: 1.计算机高级语 ...

  3. 设计模式 策略模式2 c++11

    根据需求的不同 选择不同的策略算法 之前是保存的各种策略类的指针 这里直接使用 function  bind 选择对应的算法 代码 // 005.cpp: 定义控制台应用程序的入口点. // #inc ...

  4. python环境问题(pycharm)

    一.问题 我们在使用python的时候会遇到环境配置问题.如何可以一劳永逸,是我们解决问题的基本思想. 二.解决1.新建环境: 2.添加环境:选择需要的环境,可以是conda,亦可以是virtual. ...

  5. 整理python小爬虫

    编码使我快乐!!! 我也不知道为什么,遇到自己喜欢的事情,就越想做下去,可以一个月不出门,但是不能一天没有电脑 掌握程度:对python有了一个更清晰的认识,自动化运维,也许可以用python实现呢, ...

  6. Codeforces828 B. Black Square

    B. Black Square time limit per test 1 second memory limit per test 256 megabytes input standard inpu ...

  7. PMP:3.项目经理角色

    成员角色:整合指挥者 在团队中的职责:负终责 知识技能:综合技能&沟通   定义: 职能经理专注于对某个职能领域或业务部门的管理监督. 运营经理负责保证业务运营的高效性. 项目经理是由执行组织 ...

  8. unidbgrid 设置 单元格颜色

    unidbgrid 设置 单元格颜色 2018年10月24日 11:32:41 ozhy111 阅读数:68   procedure TF_Resource2.UniDBGrid1DrawColumn ...

  9. bash编程-条件测试

    Shell脚本中经常需要判断某情况或者数据是否满足,需要由测试机制来实现. 测试方式 echo $?查看命令执行状态返回值 bash脚本中可以自定义返回值exit n(n为自己指定的状态码),shel ...

  10. 学习在dos下使用gcc来编译

    这两年里,断断续续的学习和使用c,平时都是在CodeBlocks里写代码,编译程序,点一下按钮就行了.对整个编译过程是一点儿都不了解.相比当年学习java,真的是选择了两个不同的路,当年学习java的 ...