我的网站搭建(第26天) 阅读统计分析

  • 发布时间:2016年8月15日 10:48
  • 作者:杨仕航
* 该文是基于Python2.7开发的,最新Python3.x和Django2.x视频教程可以前往 >> Django2.0视频教程

前几天优化了阅读计数模式,现在就可以拿数据来统计分析。有了明细阅读数据,可以分析出哪些博文相对比较热门、阅读变化等等。我就简单分析一下前7天的数据。

前7天的每天阅读量数据可以统计并展示出来,这个用图表展示比较清晰。对比了一些js的图表插件,决定使用HighChart.js,因为这个图表数据可以用json加载,帮助文档也很完善,使用简单方便。具体帮助可以参考HighChart中文网。先给大家看看图表最终效果图:

8月11号之前是没有数据的,因为8月11号才上线阅读明细记录的功能(具体细节参考博文《我的网站搭建(第24天) 阅读计数优化》)。把前7天的数据制作成折线图,方便分析趋势变化。

由于HighChart.js使用到的数据是json,所以这里我打算用ajax去获取数据,再加载图表。


为了讲清楚(也为了应付没有看第24天的那篇博文),先交待清楚一些东西。我那个阅读计数应用叫view_record,记录明细的模型是Recorder。具体模型如下:

  1. #coding:utf-8
  2. from django.db import models
  3. from django.contrib.contenttypes.models import ContentType
  4. from django.contrib.contenttypes.fields import GenericForeignKey
  5. from django.contrib.auth.models import User
  6.  
  7. class Recorder(models.Model):
  8.     """阅读明细记录"""
  9.     #ContentType关联字段
  10.     content_type = models.ForeignKey(ContentType)
  11.     object_id = models.PositiveIntegerField()
  12.     content_object = GenericForeignKey(
  13.         ct_field="content_type",
  14.         fk_field="object_id"
  15.     )
  16.  
  17.     #普通字段
  18.     ip_address = models.CharField(max_length=15)
  19.     user = models.ForeignKey(User, blank=True, null=True)
  20.     view_time = models.DateTimeField(auto_now=True)

下面会用得比较多的是 view_time 日期时间字段,主要围绕它统计分析。


打开该应用的view.py文件,写入如下代码:

  1. #coding:utf-8
  2. from django.http import HttpResponse
  3. from django.contrib.contenttypes.models import ContentType
  4.  
  5. from view_record.models import Recorder
  6. from blog.models import Blog
  7.  
  8. import datetime
  9. import json
  10.  
  11. def get_seven_days_data(request):
  12.     """获取7天内的数据"""
  13.     #得到7天的日期
  14.     now = datetime.datetime.now() #now还包含小时,分钟等,需要去掉
  15.     end_date = datetime.datetime(now.year, now.month, now.day, 0, 0) 
  16.     start_date = end_date - datetime.timedelta(7)
  17.     days = map(lambda x: end_date - datetime.timedelta(x), range(7, 0, -1))
  18.     
  19.     #得到前7天的阅读量
  20.     oneday = datetime.timedelta(1)
  21.     obj_type = ContentType.objects.get_for_model(Blog)
  22.     counts = map(lambda x: Recorder.objects.filter(content_type = obj_type, view_time__range=(x,+ oneday)).count(), days)
  23.  
  24.     #获取HighChart图表设置
  25.     chart = {}
  26.  
  27.     #图表设置
  28.     chart['chart'] = {"type":"line",  #设置为折线图
  29.                     "borderColor": '#dfdfdf', #设置边框
  30.                     "borderWidth": 1,
  31.                     "borderRadius":5,
  32.                     "margin": 35,
  33.                     "marginBottom":70
  34.                     }
  35.     #去掉图表标题,太大不好看
  36.     chart['title'] = {"text":""}
  37.     #X轴设置
  38.     chart['xAxis'] = {"categories": map(lambda x: datetime.datetime.strftime(x, '%m-%d'), days),
  39.                     "tickmarkPlacement": 'on',
  40.                     "title":{
  41.                         "enabled":True,
  42.                         "text":u"前7日阅读量变化"
  43.                     }}
  44.     #Y轴(不写标题,隐藏Y轴)
  45.     chart['yAxis'] = {"title":{"text":""}, "labels":{"enabled":False}}
  46.     #数据标签
  47.     chart['plotOptions'] = {"line":{"dataLabels":{"enabled":True}}, "enableMouseTracking": False}
  48.     #数据系列
  49.     chart['series'] = [{"name":"view nums","data":counts}]
  50.     #图例不显示
  51.     chart['legend'] = {"enabled":False}
  52.     #右下角版权不显示
  53.     chart['credits'] = {"enabled":False, "text":"yshblog.com","href":"http://yshblog.com/"}
  54.  
  55.     return HttpResponse(json.dumps(chart), content_type="application/json")

这里的图表相关设置我就不说了,自己看HighChart.js的帮助。此处重点是获取前7天的日期和对应的数据。

日期使用datetime加减得到7天的日期。Recorder筛选需要两个条件,一个是ContentType处理;另一个是日期的范围。这个需要在view_time后面加上__range,并指定范围即可。

再加入url路由设置:

  1. from django.conf.urls import include, url
  2.  
  3. #http://localhost:8000/view_record/
  4. #start with 'view_record/'
  5. urlpatterns = [
  6.     url(r'^get_seven_days_data$','view_record.views.get_seven_days_data',name='get_seven_days_data'),
  7. ]

总路由再添加应用的url路由:

  1. urlpatterns = [
  2.     #...其他路径设置就不显示出来了
  3.     url(r'^view_record/',include('view_record.urls')),
  4. ]


接着,修改前端页面显示图表。我把图表放在首页,所以打开对应的首页模版文件index.html(你就根据自己的情况修改),加入如下代码:

  1. {#我所有模版都会基于一个最底层的base.html文件#}
  2. {% extends "base.html" %}
  3.  
  4. {#此处是head头部分的拓展,加入HighChart.js的引用。我的base.html已经引用了jQuery#}
  5. {% block extra_head %}
  6.     <script src="/static/js/highcharts.js"></script>
  7. {% endblock %}
  8.  
  9. {% block content %}
  10.     {#这里还有其他内容我就没显示,此处是body部分#}
  11.     {#加入HighChart的容器#}
  12.     <div id="container" style="height:300px"></div>
  13. {% endblock %}
  14.  
  15. {#该blockbody部分的底部拓展,通常我是用来写js代码#}
  16. {% block extra_footer %}
  17.     <script type="text/javascript">
  18.         $.ajax({
  19.             type:"GET",
  20.             url:"{% url 'get_seven_days_data' %}",
  21.             cache:false,
  22.             dataType:'text',
  23.             success:function(result){
  24.                 //加载图表
  25.                 $('#container').highcharts(JSON.parse(result));
  26.             },
  27.             error:function(XMLHttpRequest, textStatus, errorThrown){
  28.                 //alert(textStatus);
  29.             }
  30.         });
  31.     </script>
  32. {% endblock %}

保存,重启服务,就可以看到前面截图的效果了。


若单单只有这个图表,显得有点空。所以我寻思在这个基础上加一些文字分析说明并显示出来。

这些文字分析说明就一并通过这个ajax获取和显示到前端页面中。json数据结构就需要改动成如下:

  1. {
  2.     "chart" : {图表设置},
  3.     "texts" : [文字分析数组]
  4. }

文字分析说明主要分析了阅读频率、阅读时间段、点击最多的博文等等。代码有点多,我还是一并贴出来。重点和难点是对模型的统计查询。若该部分弄不明白也可以使用SQL语句得到原始的统计查询。

  1. #coding:utf-8
  2. from django.http import HttpResponse
  3. from django.core.urlresolvers import reverse
  4.  
  5. from django.contrib.contenttypes.models import ContentType
  6. from django.db.models import Count, Max #Django模型统计函数
  7.  
  8. from view_record.models import Recorder
  9. from blog.models import Blog
  10.  
  11. import datetime, json
  12.  
  13. def get_seven_days_data(request):
  14.     """获取7天内的数据"""
  15.     #得到7天的日期
  16.     now = datetime.datetime.now()
  17.     end_date = datetime.datetime(now.year, now.month, now.day, 0, 0) 
  18.     start_date = end_date - datetime.timedelta(7)
  19.     days = map(lambda x: end_date - datetime.timedelta(x), range(7, 0, -1))
  20.     
  21.     #得到前7天的阅读量
  22.     oneday = datetime.timedelta(1)
  23.     obj_type = ContentType.objects.get_for_model(Blog)
  24.     counts = map(lambda x: Recorder.objects.filter(content_type = obj_type, view_time__range=(x,+ oneday)).count(), days)
  25.  
  26.     #7天内的Blog阅读全部明细
  27.     seven_data = Recorder.objects.filter(content_type = obj_type, view_time__range=(start_date, end_date))
  28.  
  29.     data = {}
  30.     data["chart"] = _get_chart_data(days, counts)
  31.     data['texts'] = _get_texts_data(days, counts, seven_data)
  32.  
  33.     return HttpResponse(json.dumps(data), content_type="application/json")
  34.  
  35. def _get_chart_data(days, counts):
  36.     """get the chart json data"""
  37.     chart = {}
  38.  
  39.     #图表设置
  40.     chart['chart'] = {"type":"line",
  41.                     "borderColor": '#dfdfdf',
  42.                     "borderWidth": 1,
  43.                     "borderRadius":5,
  44.                     "margin": 35,
  45.                     "marginBottom":70
  46.                     }
  47.     #标题
  48.     chart['title'] = {"text":""}
  49.     #X轴
  50.     chart['xAxis'] = {"categories": map(lambda x: datetime.datetime.strftime(x, '%m-%d'), days),
  51.                     "tickmarkPlacement": 'on',
  52.                     "title":{
  53.                         "enabled":True,
  54.                         "text":u"前7日阅读量变化"
  55.                     }}
  56.     #Y轴(不写标题,隐藏Y轴)
  57.     chart['yAxis'] = {"title":{"text":""}, "labels":{"enabled":False}}
  58.     #数据标签
  59.     chart['plotOptions'] = {"line":{"dataLabels":{"enabled":True}}, "enableMouseTracking": False}
  60.     #数据系列
  61.     chart['series'] = [{"name":"view nums","data":counts}]
  62.     #图例
  63.     chart['legend'] = {"enabled":False}
  64.     #右下角版权
  65.     chart['credits'] = {"enabled":False, "text":"yshblog.com","href":"http://yshblog.com/"}
  66.     return chart
  67.  
  68. def _get_texts_data(days, counts, seven_data):
  69.     """get analysis texts"""
  70.     texts = []
  71.  
  72.     #总点击数
  73.     __viewed_num(seven_data, texts)
  74.  
  75.     #时间段分析
  76.     __anaylsis_hour(seven_data, texts)
  77.  
  78.     #周末和工作日分析
  79.     __anaylsis_week(days, counts, texts)
  80.  
  81.     #阅读最多的博文
  82.     __max_viewed(seven_data, texts)
  83.  
  84.     return texts
  85.  
  86. def __viewed_num(seven_data, texts):
  87.     viewed_count = seven_data.count()
  88.     texts.append(u'前7日总阅读%s次,平均 %.2f次/天' % (viewed_count, viewed_count/7.))
  89.  
  90.     if viewed_count == 0:
  91.         texts.append(u'桑心!居然一次都没有,我要好好分析是什么原因 T_T')
  92.     elif viewed_count <= 7*2:
  93.         texts.append(u'好吧,阅读量有点少,可能宣传不够或者我最近博文写少了')
  94.     elif viewed_count <= 7*5:
  95.         texts.append(u'目前来说,还需努力,继续写出好的文章')
  96.     elif viewed_count <= 7*8:
  97.         texts.append(u'^_^ 朋友,若您觉得不错,帮忙宣传一下呗')
  98.     else:
  99.         texts.append(u'再接再厉,把我的博客弄得更好!')
  100.  
  101. def __anaylsis_hour(seven_data, texts):
  102.     hour_range = [(0,5), (5,8), (8,12), (12,14), (14,18), (18,24)]
  103.     hour_viewed_num = map(lambda x: seven_data.filter(view_time__hour__range=x).count(), hour_range)
  104.  
  105.     #获取最大值
  106.     max_num = max(hour_viewed_num)
  107.  
  108.     #判断最大值位置
  109.     if max_num == 0:
  110.         texts.append(u'没有被阅读,无法统计哪个时间段阅读次数最多')
  111.     else:
  112.         for i, value in enumerate(hour_viewed_num):
  113.             if max_num == value:
  114.                 hour_range_item = hour_range[i]
  115.                 if hour_range_item == (0,5):
  116.                     texts.append(u'凌晨0点到5点阅读最多,都是苦比的程序猿吗')
  117.                 elif hour_range_item == (5,8):
  118.                     texts.append(u'早上5点到8点阅读最多,我还在睡觉,时区不同吗')
  119.                 elif hour_range_item == (8,12):
  120.                     texts.append(u'早上8到12点阅读最多,正常工作时间')
  121.                 elif hour_range_item == (12,14):
  122.                     texts.append(u'中午12点到14点阅读最多,不用午休吗')
  123.                 elif hour_range_item == (14,18):
  124.                     texts.append(u'下午14点到19点阅读最多,正常工作时间')
  125.                 elif hour_range_item == (18,24):
  126.                     texts.append(u'晚上19点到24点阅读最多,要么自学要么加班中')
  127.  
  128. def __anaylsis_week(days, counts, texts):
  129.     week_day_nums = 0
  130.     work_day_nums = 0
  131.  
  132.     #获取周末和工作日的阅读量
  133.     for i, value in enumerate(days):
  134.         if value.isoweekday()>5:
  135.             week_day_nums += counts[i]
  136.         else:
  137.             work_day_nums += counts[i]
  138.  
  139.     texts.append(u'工作日阅读量:%s,周末阅读量:%s' % (work_day_nums, week_day_nums))
  140.  
  141.     #分析阅读量
  142.     avg_week = week_day_nums/2.
  143.     avg_work = work_day_nums/5.
  144.  
  145.     if avg_work > avg_week:
  146.         text = u'工作日平均%.2f次/天,周末平均%.2f次/天。大部分工作中学习' % (avg_work, avg_week)
  147.     elif avg_work < avg_week:
  148.         text = u'工作日平均%.2f次/天,周末平均%.2f次/天。大部分人挺宅的' % (avg_work, avg_week)
  149.     else:
  150.         if avg_work == 0:
  151.             text = u'什么数据都没有,无法分析工作日和周末情况'
  152.         else:
  153.             text = u'工作日平均%.2f次/天,周末平均%.2f次/天。难得平均数据一样' % (avg_work, avg_week)
  154.     texts.append(text)
  155.  
  156. def __max_viewed(seven_data, texts):
  157.     #对object_id分组计数,并按计数倒序排列。结果是一个数组字典
  158.     #获取最多点击次数的博文,这里弄不明白就用SQL语句查询
  159.     qs_count = seven_data.values("object_id").annotate(Count('id')).order_by('-id__count')
  160.     count_num = qs_count.count()
  161.  
  162.     if count_num>0:
  163.         blog_id = qs_count[0]['object_id']  #得到数量最大的id
  164.         blog = Blog.objects.get(id = blog_id)
  165.         args = [blog_id]
  166.         texts.append(u'点击最多:<a href="%s" target=_blank>%s</a>' % (reverse('detailblog', args=args), blog.caption))
  167.  
  168.     if count_num>1:
  169.         blog_id = qs_count[1]['object_id']  #得到数量次多的id
  170.         blog = Blog.objects.get(id = blog_id)
  171.         args = [blog_id]
  172.         texts.append(u'点击第二:<a href="%s" target=_blank>%s</a>' % (reverse('detailblog', args=args), blog.caption))

代码有点多,大家选择性看看就行。里面有几个地方需要注意的:

1、__anaylsis_hour方法中的filter筛选器使用到条件view_time__hour__range=x,view_time是日期时间字段,__hour是得到该字段值中的小时,__range是范围。组合起来则是得到指定小时范围的记录。

2、__max_viewed方法中的分组计数统计,seven_data.values("object_id").annotate(Count('id')),即得到对object_id字段分组,并对id字段计数。结果是得到一个查询字典,再排序。具体查询统计可以参考一下官方文档。


修改了后台方法之后,还需要对应修改一下前端页面:

  1. {#我所有模版都会基于一个最底层的base.html文件#}
  2. {% extends "base.html" %}
  3.  
  4. {#此处是head头部分的拓展,加入HighChart.js的引用。我的base.html已经引用了jQuery#}
  5. {% block extra_head %}
  6.     <script src="/static/js/highcharts.js"></script>
  7. {% endblock %}
  8.  
  9. {% block content %}
  10.     {#这里还有其他内容我就没显示,此处是body部分#}
  11.     <div class="row">
  12.         <div class="col-xs-12 col-md-5">
  13.             <div class="panel panel-default">
  14.                 <div class="panel-heading">
  15.                     <span>7日阅读分析</span>
  16.                 </div>
  17.                 <div class="panel-body" style="height:255px">
  18.                     {#加一个ul作为容器可被添加文字#}
  19.                     <ul id="analysis"></ul>
  20.                 </div>
  21.             </div>
  22.         </div>
  23.  
  24.         <div class="col-xs-12 col-md-6">
  25.             <div id="container" style="height:300px"></div>
  26.         </div>
  27.     </div>
  28. {% endblock %}
  29.  
  30. {#该blockbody部分的底部拓展,通常我是用来写js代码#}
  31. {% block extra_footer %}
  32.     <script type="text/javascript">
  33.         $.ajax({
  34.             type:"GET",
  35.             url:"{% url 'get_seven_days_data' %}",
  36.             cache:false,
  37.             dataType:'text',
  38.             success:function(result){
  39.                 data = JSON.parse(result);
  40.  
  41.                 //加载图表
  42.                 $('#container').highcharts(data['chart']);
  43.  
  44.                 //加载文字
  45.                 texts = data['texts'];
  46.                 if(texts.length > 0){
  47.                     $.each(texts, function(i, item){
  48.                         $('#analysis').append("<li>" + (i+1) + "、" + item + "</li>");
  49.                     });
  50.                 }else{
  51.                     $('#analysis').append("<li>暂无相关分析</li>");
  52.                 }                
  53.             },
  54.             error:function(XMLHttpRequest, textStatus, errorThrown){
  55.                 //alert(textStatus);
  56.             }
  57.         });
  58.     </script>
  59. {% endblock %}

这样就可以简单实现图表和文字分析了,效果如下:

当然,更强大的方法是使用机器学习进一步挖掘数据和分析数据。

上一篇:我的网站搭建(第27天) Django目录结构优化

下一篇:我的网站搭建(第25天) 反爬虫设置

评论列表

dfjk59@126.com

dfjk59@126.com

test😀

2018-04-17 11:31 回复

  • dfjk59@126.com

    test

    2018-04-17 11:31 回复

  • dfjk59@126.com 回复 dfjk59@126.com

    😁

    2018-04-17 11:32 回复

新的评论

清空

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

我把服务器搬到国内了,并做了一些优化,速度爆快

2021-09-22

了解更多