在自然界之中,蛇的眼睛有夜視功能,即便是茫茫黑夜,它也能輕而易舉的找到獵物,這是因為任何物體都會輻射熱紅外,且輻射的高低和溫度成正比,由於生命體的體溫會明顯高於周圍環境的溫度,所以在蛇眼面前便無處遁形。熱紅外成像被廣泛應用於軍事領域,士兵帶上能識別熱紅外的眼鏡后能輕而易舉的發現藏匿的敵人。
嘮叨了半天,聽上去似乎有點跑題了,其實不然,對互聯網從業者而言,同樣需要有火眼金睛,以便識別網友的喜好,此時的衡量標准是點擊,點擊越多則表示越喜歡,此技術被稱作Heatmap,已經有網站提供此類服務,如:clickdensity,clicktale,crazyegg等等,甚至還有類似clickheat項目提供源代碼供你直接使用。
不過最靈活的方案莫過於自己搞定,下面大概說說Heatmap的實現:
捕捉點擊
當然,這需要Javascript來實現。為了不陷入瀏覽器兼容的泥潭,我們選擇JQuery:
<script> jQuery(document).ready(function() { $(document).mousedown(function(e) { if (e.clientX >= $(window).width() || e.clientY >= $(window).height()) { return; } $.get("/path/to/a/empty/html/file", { page_x : e.pageX, page_y : e.pageY, screen_width : screen.width, screen_height: screen.height }); }); }); </script>
客戶端使用Ajax通過GET方法觸發一個空HTML頁面,當然,還可以更簡單點:
<script> var image = new Image(); image.src = "..."; </script>
之所以要記錄屏幕分辨率是因為有的情況下需要修正點擊坐標。比如說,一個居中顯示的定寬的頁面,其同一個位置在不同分辨率下的坐標是不同的,當渲染圖片的時候,坐標需要以一個分辨率為准進行修正。
另外,如果用戶正在拖動滾動條,是不應該記錄的。
分析日志
客戶端使用Ajax通過GET方法觸發一個空HTML頁面,如此就會在服務端留下日志:
page_x=...&page_y=...&screen_width=...&screen_height=...
不同的日志格式,結果會有所不同,這里僅僅以此為例來說明問題,本文采用AWK來解析日志,當然你也可以使用Perl或別的你熟悉的語言:
#!/usr/bin/awk -f BEGIN { FS="&"; } NF == 4 { param["page_x"] = "0"; param["page_y"] = "0"; param["screen_width"] = "0"; param["screen_height"] = "0"; split($0, query, "&"); for (key in query) { split(query[key], item, "="); if (item[1] in param) { param[item[1]] = item[2]; } } print "page_x:" , param["page_x"]; print "page_y:" , param["page_y"]; print "screen_width:" , param["screen_width"]; print "screen_height:", param["screen_height"]; print "\n"; }
至於數據的持久化,是使用MongoDB或者別的,自己定奪,這里就不多說了。
渲染圖片
出於演示方便的考慮,我使用了一些隨機生成的數據,以Imagick為例,代碼如下:
<?php $coordinates = array(); for ($i = 0; $i < 1000; $i++) { $coordinates[] = array(rand($i, 1000), rand($i, 1000)); } $max_repeat = max( array_count_values( array_map(function($v) { return "{$v[0]}x{$v[1]}"; }, $coordinates) ) ); $opacity = 1 - 1 / $max_repeat; $heatmap_image = new Imagick(); $heatmap_image->newImage(1000, 1000, new ImagickPixel('white')); $heatmap_image->setImageFormat('png'); $plot_image = new Imagick('plot.png'); $iterator = $plot_image->getPixelIterator(); foreach($iterator as $row) { foreach ($row as $pixel) { $colors = $pixel->getColor(); foreach (array('r', 'g', 'b') as $channel) { $color = $colors[$channel]; if ($color !== 255) { $colors[$channel] = $color + ((255 - $color) * $opacity); } } $pixel->setColor("rgb({$colors['r']},{$colors['g']},{$colors['b']})"); } $iterator->syncIterator(); } $plot_size = $plot_image->getImageGeometry(); foreach ($coordinates as $pair) { $heatmap_image->compositeImage( $plot_image, Imagick::COMPOSITE_MULTIPLY, $pair[0] - $plot_size['width'] / 2, $pair[1] - $plot_size['height'] / 2 ); } $color_image = new Imagick('clut.png'); $heatmap_image->clutImage($color_image); $heatmap_image->writeImage('heatmap.png'); ?>
代碼雖然很多,但並不復雜,其中用到了兩個圖片,分別是:plot.png和clut.png。實際應用時,有時候點擊量會非常大,此時沒有必要把所有的點擊都渲染出來,而應該采取隨機取樣的策略,如果采用MongoDB持久化的話,可以參考:The Random Attribute。
備注:代碼參考image-tempest。
最終展示
形象一點來說,其實就是通過CSS+Javascript把生成的圖片蓋在網頁上,並調節圖片透明度來達到合二為一的效果,篇幅所限,具體代碼留給大家自己實現,例子效果可參考下圖:
BTW:熱點可能會隨着時間改變,為了能對照某個時間的網頁,可以使用CutyCapt截屏。順手再貼一個相關的項目:smt2(simple mouse tracking)。
有關Heatmap的詳細介紹,還可以參考
收工!Heatmap雖然不是很復雜的技術,但涉及的方面卻很繁雜,希望本文能幫到大家。