1. 程式人生 > >開源網站流量統計系統Piwik原始碼分析——後臺處理(二)

開源網站流量統計系統Piwik原始碼分析——後臺處理(二)

  在第一篇文章中,重點介紹了指令碼需要蒐集的資料,而本篇主要介紹的是伺服器端如何處理客戶端傳送過來的請求和引數。

一、裝置資訊檢測

  通過分析User-Agent請求首部(如下圖紅線框出的部分),可以得到相關的裝置資訊。

 

  Piwik系統專門有一套程式碼用來分析代理資訊,還獨立了出來,叫做。它有一個專門的,可以展示其功能,點進去後可以看到下圖中的內容。

  它能檢測出瀏覽器名稱、瀏覽器的渲染引擎、瀏覽器的版本、裝置品牌(例如HTC、Apple、HP等)、裝置型號(例如iPad、Nexus 5、Galaxy S5等)、裝置類別(例如desktop、smartphone、tablet等),這6類資料中的可供選擇的關鍵字,可以參考“”或外掛的“”。順便說一下,Piwik還能獲取到訪客的定位資訊,在“”中,列舉出了城市、經緯度等資訊,其原理暫時還沒研究。

  Piwik為大部分裝置資訊的關鍵字配備了一個icon圖示,所有的icon圖示被放置在“plugins\Morpheus\icons”中,包括瀏覽器、裝置、國旗、作業系統等,下圖擷取的是瀏覽器中的部分圖示。

二、IP地址

  在Piwik系統的後臺設定中,可以選擇IP地址的獲取方式(如下圖所示)。在官方部落格的一篇《》博文中提到,3.5版本後可以在系統中嵌入MaxMind公司提供的IP地理定位服務()。

  下面是一張看官方的產品介紹表,從描述中可看出這是一項非常厲害的服務。不過需要注意的是,這是一項付費服務。

三、日誌資料和歸檔資料

  在官方釋出的說明文件《》中提到,在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:每次訪問都會生成一條訪問者記錄,表中的欄位可參考“”。

(2)log_action:網站上的訪問和操作型別(例如特定URL、網頁標題),可分析出訪問者感興趣的頁面,表中的欄位可參考“”。

(3)log_link_visit_action:訪問者在瀏覽期間執行的操作,表中的欄位可參考“”。

(4)log_conversion:訪問期間發生的轉化(與目標相符的操作),表中的欄位可參考“”。

(5)log_conversion_item:與電子商務相關的資訊,表中的欄位可參考“”。

  歸檔資料的表有兩種字首,分別是“archive_numeric_”和“archive_blob_”,表的欄位可參考“”。通過對欄位的觀察可知,兩種最大的不同就是value欄位的資料型別。archive_numeric_* 表中的value能儲存數值(資料型別是Double),而archive_blob_* 表中的value能儲存出數字以外的其他任何資料(資料型別是Blob)。

  兩種表都是動態生成的,因此字首的後面都用“*”表示。生成規則可按年、月、周、天或自定義日期範圍,不設定的話,預設是按月計算,例如archive_numeric_2018_09、archive_blob_2018_09。

參考資料: