我的网站搭建(第41天) 基于关键字的智能推荐

  • 发布时间:2017年1月14日 16:04
  • 作者:杨仕航

上次开发了随机推荐,为了提高更多博文的曝光率。

但随机推荐只能让藏在深处的博文多露几次脸,不一定可以让访客点击。

那么可以推荐一些访客喜欢的、感兴趣的博文,提高点击率,即智能推荐功能(或叫猜你喜欢)。


为了让博文之间有关联,我上一篇博文就写了新增关键字功能。利用关键字让博文之间有关联性。

接下来需要把访客阅读过的博文记录下来。

由于访客不一定是我博客注册并登录的用户。所以不太需要把这些数据记录到服务器。可以把数据已cookie的形式保存在客户端。

此处有个小细节:我们应该需要保存多少条历史阅读记录。

全部?不需要,也不合适。因为随着不断写博客,博文只会越来越多。而且全部记录的话,无法突出最新的阅读记录。访客的阅读喜好和博客的内容是有时效性。也就是说,最新的阅读记录权重最高,依次递减。

综合考虑,我决定只记录前10条的阅读记录。这样可以确保时效性。


第1步、先记录阅读历史记录

打开Blog应用的views.py文件,添加如下方法:

#记录最新阅读的前10篇博文
def record_read(request, current_blog_id, response):
    #获取最近阅读的10篇博客id列表
    cookie_name = "new_readed"
    cookie_value = request.COOKIES.get(cookie_name, '')

    #该cookie的形式 "1|2|3|4|5"
    read_list = cookie_value.split('|')

    #写入当前博客的id
    current_blog_id = str(current_blog_id)
    if read_list.count(current_blog_id) == 0:
        read_list.append(current_blog_id)

        #判断是否有超过10个,若超过,则移除
        if len(read_list)>10:
            read_list.pop(0)

    #写入可保留长时间的cookie
    d = datetime.datetime.now() + datetime.timedelta(days=1000)
    t = time.mktime(d.timetuple())
    response.set_cookie(cookie_name, '|'.join(read_list), t, httponly=True)
    return True

该方法需要传递3个参数:request请求对象、当前博客id和response响应对象。

将最近阅读的博客ID用'|'隔开写入一个可以保留长时间的cookie中。


每次打开每篇博文就执行一下该方法。在响应打开每篇博文的方法内调用该方法:

#coding:utf-8
from django.shortcuts import render_to_response
from django.template import RequestContext

def blog_show(request,id):
    #省略其他不相关的代码
    response = render_to_response("blog/blog_single.html", data, context_instance=RequestContext(request))
    record_read(request, id, response) #写入最近阅读记录的cookie
    return response

先获取response对象,调用record_read方法,写入cookie信息之后,再返回给前端页面。


第2步、根据最近阅读的记录获取推荐列表

该部分是这整个智能推荐最复杂也是最核心的部分。由于该功能计算时间可能会比较长,采用ajax的方式比较合适。

首先,先获取最近的阅读记录和这些博客包含的关键字。在Blog应用的views.py文件中,添加如下方法:

def like_blogs(request):
    #读取最近阅读的博客id列表
    cookie_name = "new_readed"
    cookie_value = request.COOKIES.get(cookie_name, '')

    #把字符串转成数字
    value_list = cookie_value.split('|')
    read_list = []
    keywords = {}

    for value in value_list:
        try:
            value = int(value)
            blog = Blog.objects.get(id=value)

            #若上面两步没有报错,则写入列表
            read_list.append(value)

            #获取对应的关键字,并累计计数
            for keyword in blog.keywords.all():
                keyword_id = keyword.id
                keywords[keyword_id] = keywords.get(keyword_id, 0) + 1
        except:
            pass

通过该方法获取到最近阅读列表read_list和关键字字典keywords。该字典键名是关键字的id,键值是该关键字出现的次数。

关键字出现的次数可作为访客阅读喜欢的权重。某些类型的博客阅读越多,就说明该访客偏好这些内容。


接着,再通过关键字字典获取对应的博客列表。该博客列表为候选列表。

#获取关键字对应的博文,作为推荐候选
blogs = {}
for keyword_id, k_times in keywords.items():
    keyword = Keyword.objects.get(id=keyword_id)
    blogs_sub = keyword.blog_set.all()

    #累计统计次数
    for blog in blogs_sub:
        blog_id = blog.id

        #排除最近阅读列表中存在的
        if not blog_id in read_list:
            blogs[blog_id] = blogs.get(blog_id, 0) + k_times #1*k_times

最后一句代码,加上k_times原本是1*k_times。原本加1即可,但关键字是有权重。需要乘以该关键字出现的次数,省略为k_times。同样该候选列表是一个字典,键名是博客id,键值是权重。该权重越大,说明访客偏好越大。但访客可能已经阅读过了这个字典中的博文,所以需要剔除去掉。


另外,我想获取的智能推荐列表需要10篇文章。可能上面的候选列表不足10个。不足10个就使用之前随机推荐的方法产生。

也可能该候选列表超过10个,那么需要根据权重数排序,获取最高的前10个。

引用numpy库:

import numpy as np

代码如下:

#把数据和键值分离
blog_ids = blogs.keys()
blog_times = blogs.values()
list_num = 10

if len(blog_ids)<list_num:
    #随机推荐个数 = 候选列表个数 + 最近阅读个数 + 10
    rand_count = list_num + len(blog_ids) + len(read_list)
    rand_list = Blog.objects.order_by('?')[:rand_count]

    #随机列表剔除候选列表和最近阅读的(随机列表与候选列表和最近阅读列表的并集取差集)
    rand_ids = map(lambda x:x.id, rand_list)
    rand_ids = list(set(rand_ids) - (set(read_list) | set(blog_ids)))

    #补全推荐列表
    like_list = blog_ids + rand_ids
    like_list = like_list[:list_num]
elif len(blog_ids)==list_num:
    #刚好10个,直接使用该列表
    like_list = blog_ids
else:
    #大于10个,取比重最高的前10个
    dataset = np.array(blog_times) * -1 #每个元素乘以-1
    sorted_dataset = dataset.argsort()  #升序排序(相当于乘-1之前倒序排序)

    like_list = []
    for i in range(list_num):
        like_list.append(blog_ids[sorted_dataset[i]])

这部分代码,有两个地方需要注意:

1)不足10个采用集合运算,快速剔除重复的部分。

20170114/20170114155459008.png

2)大于10个,用numpy排序,快速获取前10个。有兴趣可以了解argsort方法。


最后,再把这些数组组成json,返回给前端。该like_blogs方法全部代码如下。

引用部分:

#coding:utf-8
from django.http import HttpResponse
from django.core.urlresolvers import reverse #url逆向解析

import numpy as np
import json
import datetime, time

代码部分:

#智能推荐
def like_blogs(request):
    data = {}
    try:
        #读取最近阅读的博客id列表
        cookie_name = "new_readed"
        cookie_value = request.COOKIES.get(cookie_name, '')

        #把字符串转成数字
        value_list = cookie_value.split('|')
        read_list = []
        keywords = {}

        for value in value_list:
            try:
                value = int(value)
                blog = Blog.objects.get(id=value)

                #若上面两步没有报错,则写入列表
                read_list.append(value)

                #获取对应的关键字,并累计计数
                for keyword in blog.keywords.all():
                    keyword_id = keyword.id
                    keywords[keyword_id] = keywords.get(keyword_id, 0) + 1
            except:
                pass

        #获取关键字对应的博文,作为推荐候选
        blogs = {}
        for keyword_id, k_times in keywords.items():
            keyword = Keyword.objects.get(id=keyword_id)
            blogs_sub = keyword.blog_set.all()

            #累计统计次数
            for blog in blogs_sub:
                blog_id = blog.id

                #排除最近阅读列表中存在的
                if not blog_id in read_list:
                    blogs[blog_id] = blogs.get(blog_id, 0) + k_times #1*k_times

        #把数据和键值分离
        blog_ids = blogs.keys()
        blog_times = blogs.values()
        list_num = 10

        if len(blog_ids)<list_num:
            #随机推荐个数 = 候选列表个数 + 最近阅读个数 + 10
            rand_count = list_num + len(blog_ids) + len(read_list)
            rand_list = Blog.objects.order_by('?')[:rand_count]

            #随机列表剔除候选列表和最近阅读的(随机列表与候选列表和最近阅读列表的并集取差集)
            rand_ids = map(lambda x:x.id, rand_list)
            rand_ids = list(set(rand_ids) - (set(read_list) | set(blog_ids)))

            #补全推荐列表
            like_list = blog_ids + rand_ids
            like_list = like_list[:list_num]
        elif len(blog_ids)==list_num:
            #刚好10个,直接使用该列表
            like_list = blog_ids
        else:
            #大于10个,取比重最高的前10个
            dataset = np.array(blog_times) * -1 #每个元素乘以-1
            sorted_dataset = dataset.argsort()  #升序排序(相当于乘-1之前倒序排序)

            like_list = []
            for i in range(list_num):
                like_list.append(blog_ids[sorted_dataset[i]])

        #返回数据
        data['code'] = 0
        data['message'] = "OK"

        items = []
        for blog_id in like_list:
            blog = Blog.objects.get(id = blog_id)

            item = {}
            item['caption'] = blog.caption
            item['url'] = reverse('detailblog', args = [blog_id,])
            items.append(item)
        
        data['items'] = items
    except Exception as e:
        data['code'] = 400
        data['message'] = e.message
    finally:
        return HttpResponse(json.dumps(data), content_type = 'application/json')


再加上urls路由设置:

url(r'^get_like_blogs$', like_blogs, name='get_like_blogs'),


第3步、修改模版页面

我采用ajax访问该地址,获取数据:

<div class="side-list">
    <h4>
        <span class="glyphicon glyphicon-book"></span>
        猜你喜欢
    </h4>
    <ul id="like_blogs_list">
        <li>猜测中,请稍等...</li>
    </ul>  
</div>  

<script type="text/javascript">
    //获取猜测列表
    $.ajax({
        type:"GET",
        url:"{%url 'get_like_blogs'%}",
        cache:false,
        dataType:'json',
        success:function(data){
            $("#like_blogs_list").children().remove(); //移除子节点
            if(data['code']==0){
                for (var i = 0; i < data['items'].length; i++) {
                    var url = data['items'][i]['url'];
                    var caption = data['items'][i]['caption'];
                    var str = '<li><a href="'+url+'" target=_blank>'+caption+'</a></li>';
                    $("#like_blogs_list").append(str);
                };
            }else{
                $("#like_blogs_list").append('<li>'+data['message']+'</li>');
            }
        },
        error:function(XMLHttpRequest, textStatus, errorThrown){
            $("#like_blogs_list").children().remove(); //移除子节点
            $("#like_blogs_list").append('<li>猜测卡壳了,猜不了T_T</li>');
        }
    });
</script>


第4步、开发完毕,测试效果

首先,先不点击打开任何博文,猜你喜欢的列表都是随机推荐的。

20170114/20170114160227574.jpg

接着,再打开其中一篇博文。我打开一篇Excel的(当然,大部分博文的关键字我已经添加上去了)。猜你喜欢的列表发生了变化,前面几个都是和Excel相关的文章。

20170114/20170114160359931.jpg

接着再多点击其他博文,总体测试效果还是相当不错。

上一篇:Django的views.py文件拆分

下一篇:我的网站搭建(第40天) 关键字和标签输入插件

评论列表

智慧如你,不想发表一下意见吗?

新的评论

清空

猜你喜欢

  • 猜测中,请稍等...