图书推荐系统(六)之配置应用index
1、url配置
(1)在Book/Book/urls.py下添加路径
url(r'index/', include('index.urls'), name='index'),
(2)在index
目录添加urls.py
里面填充内容
from django.conf.urls import url
from index.views import home
urlpatterns = [
url(r'^home/$',home),
]
2、index应用目录结构
├── index
│ ├── __init__.py # 配置应用
│ ├── admin.py # 提供django.contrib.admin 模块管理我们创建的数据模型
│ ├── apps.py
│ ├── migrations # 是一个文件夹,用于数据模型与数据库结构的同步
│ ├── models.py # 用于定义数据模型
│ ├── tests.py # 供单元测试使用
│ └── views.py # 是写主要的业务逻辑的地方
│ └── urls.py
2.1 init.py
from django.apps import AppConfig
default_app_config = 'index.IndexConfig'
class IndexConfig(AppConfig):
name="index" #app名称
verbose_name = u"图书" #后台显示的名称
2.2 admin.py
# -*- coding: utf-8 -*-
from django.contrib import admin
from index.models import Book,BookScore
class AdminBook(admin.ModelAdmin):
# 配置要显示的字段的
list_display = ('bookid', 'bookname', 'bookimg', 'bookscore', 'booktag')
# 搜索功能
search_fields = ('bookid', 'bookname', 'bookscore')
# 筛选功能
list_filter = ('bookscore',)
# 排序
ordering = ('-bookscore',)
admin.site.register(Book, AdminBook)
class AdminBookScore(admin.ModelAdmin):
list_display = ('userid', 'bookid', 'bookscore','actiontime',)
search_fields = ('bookscore',)
list_filter = ('bookscore',)
ordering = ('-actiontime',)
admin.site.register(BookScore, AdminBookScore)
2.3 views.py
(1)导入包
# -*- condign:utf-8 -*-
# 加载模板、填充 context 、将经解析的模板结果返回为 HttpResponse
from django.shortcuts import render_to_response
from index.models import BookScore,Book
import json,os
# 获取数据
from Book.settings import user_rec_result_path, item_sim_path, online_weight, offline_weight
在Book/settings.py下添加
# 相关配置
# 用户离线计算的推荐结果数据路径
user_rec_result_path = './../BookRec/z_data/data/user_rec_result.txt'
# 离线计算的图书相似度文件
item_sim_path = "./../BookRec/z_data/data/item_sim.json"
# 标签及其出现的次数
tags_path = "./../BookRec/z_data/data/tags.json"
# 实时计算和离线计算的推荐结果权重比例
online_weight = 1.0
offline_weight = 1.0
(2)建立一个函数home
1)判断用户是否登录,若未登录则返回登录界面
2)(_is_have_score(username)
函数)判断当前用户是否已经产生评分行为
3)(_load_rec_result(username)
函数)如果用户已经登录,则判断用户是新注册用户还是已经存在于系统中的用户
4)是否评分
若登录用户已经产生过评分,得到推荐列表( _cal_rec_result
函数),遍历bookid,从数据库Book表得到bookid的相关信息(图片、书名等),将用户民和图书信息返回index.html
;
若登录用户未产生评分行为,首先需要区分是新注册用户还是已经存在于系统中的用户。不能从离线数据rec_result
获取当前用户推荐集的是新注册用户,筛选评分大于9.5的图书,返回评分最高的20本书;如果能获取推荐结果,则直接返回离线计算好的推荐结果
另:_cal_rec_result
函数思路:
目的是综合离线计算的推荐结果和用户评分行为产生推荐结果集
1)查询当前用户评分的图书和对应的分数
2)加载离线计算好的图书相似度矩阵
3)rec_result代表推荐集,
若为空表示新注册用户,遍历用户评过分的图书id,判断该本图书是不是用户评价过的图书,若是,则忽略,继续执行,遍历相似度矩阵,从相似度矩阵中找到相似图书,加权计算,推荐度=产生过行为的图书评分乘以产生过行为的图书评分与bid的相似度,排序,推荐前20本图书。
若不为空表示存在于系统中的用户,遍历用户评过分的图书id,判断该本书是否在离线计算好的相似矩阵中,若在,继续执行,遍历相似度矩阵,从相似度矩阵中找到相似图书,加权计算,推荐度=产生过行为的图书评分乘以产生过行为的图书与bid的相似度得到推荐度,若推荐的图书已经在离线被推荐的图书列表里,最终评分加上离线的评分数据,进行综合排序。
index/views.py
# -*- condign:utf-8 -*-
from django.shortcuts import render_to_response
from index.models import BookScore,Book
import json,os
from Book.settings import user_rec_result_path, item_sim_path, online_weight, offline_weight
def home(request):
# 访问者在第一次访问服务器时,服务器在其cookie中设置一个唯一的ID号——会话ID(session)。
# 登陆是将用户写入session(服务端),此时若获取不到,则返回错误
# 获取当前登录用户
username = request.COOKIES.get('name')
# 如果用户未登录,则返回登录界面
if not username:
return render_to_response('login.html',{
'error':"用户未登录,请先登录",
'user_name':"",
'user_pwd':''
})
# 判断当前用户是否已经产生评分行为
is_action = _is_have_score(username)
# 如果用户已经登录,则判断用户是新注册用户还是已经存在于系统中的用户
rec_result = _load_rec_result(username)
# is_action 为True表示已经产生过评分行为,借鉴cf-based-item的思想进行推荐
# 这里不需要区分是新注册用户还是已经存在于系统中的用户,在_cal_rec_result 函数中进行判断
if is_action:
# 得到的推荐列表
book_scores = _cal_rec_result(username, rec_result)
bookids = [one[0] for one in book_scores]
# 字段查询:bookid__in=bookids相当于select * from Book where bookid in bookids
books = Book.objects.filter(bookid__in=bookids).order_by("-bookscore")
new_books = list()
for book in books:
_dict = dict()
_dict["bookimg"] = book.bookimg
_dict["bookid"] = book.bookid
_dict["bookscore"] = book.bookscore
_dict["booktag"] = book.booktag.split(",")
_dict["bookname"] = book.bookname
_dict["bookrescore"] = book.bookscore
new_books.append(_dict)
return render_to_response("index.html",{
"user_name":username,
"books":new_books,
})
# is_action 为False表示没有产生评分行为,这里区分是新注册用户还是已经存在于系统中的用户
else:
# 如果从离线结果中不能获取当前用户的推荐结果集,则为新注册用户,返回评分最高的20本图书
if not rec_result:
# 筛选评分大于9.5的图书
books = Book.objects.filter(bookscore__gt=9.5).order_by("-bookscore")[:20]
# 拼接推荐指数
new_books = list()
for book in books:
_dict = dict()
_dict["bookimg"] = books.bookimg
_dict["bookid"] = book.bookid
_dict["bookscore"] = book.bookscore
_dict["booktag"] = book.booktag.split(",")
_dict["bookname"] = book.bookname
_dict["bookrecscore"]=book.bookscore
new_books.append(_dict)
# 如果能获取推荐结果,则直接返回推荐结果
else:
bookids = rec_result.keys()
books = Book.objects.filter(bookid_in=bookids).order_by("-bookscore")[:20]
new_books = list()
for book in books:
_dict = dict()
_dict["bookimg"] = book.bookimg
_dict["bookid"] = book.bookid
_dict["bookscore"] = book.bookscore
_dict["booktag"] = book.booktag.split(",")
_dict["bookname"] = book.bookname
_dict["bookrecscore"] = rec_result[book.bookid]
new_books.append(_dict)
new_books.sort(key=lambda k:k["bookrecdcore"],reverse=True)
return render_to_response("index.html",{
"user_name":username,
"books":new_books,
})
# 判断当前用户是否是首次进入为你推荐模块
def _is_have_score(user):
actions = BookScore.objects.filter(userid=user)
# 加载当前登陆用户的离线推荐结果
def _load_rec_result(user):
result = dict()
for line in open(user_rec_result_path,"r",encoding="utf-8").readlines():
# 一列以user开头
if line.startswith(user):
# '_'表示忽略
_, bid, score = line.strip().split(",")
# 保留两位小数
result[bid] = round(float(score),2)
return result
# 综合离线计算的推荐结果和用户评分行为产生推荐结果集
def _cal_rec_result(user,rec_result):
# 查询当前用户评分的图书和对应的分数
actions = { one.bookid:one.bookscore for one in BookScore.objects.filter(userid=user)}
# 加载离线计算好的图书相似度矩阵
if os.path.exists(item_sim_path):
itemSim = json.load(open(item_sim_path, "r"))
else:
print("图书相似度文件不存在,请检查!")
# rec_result为空表示新注册用户,直接计算其评分图书的相似度
if not rec_result:
last_result = dict()
# 遍历用户有行为的每一本书
for bookid in actions.keys():
# 判断该本书是否在离线计算的相似key中
if bookid not in itemSim.keys():
continue
# sim_books={booid:相似度}
sim_books = itemSim[bookid]
# bid是要对用户推荐的图书
for bid in sim_books.keys():
# 判断该本图书是不是用户评价过的图书,若是,则忽略,继续执行
if bid in actions.keys():
continue
last_result.setdefault(bid, 0.0)
# 推荐度=产生过行为的图书评分*产生过行为的图书评分与bid的相似度
last_result[bid] += sim_books[bid] * actions[bookid]
# 排序,推荐前20本图书
# new_last_result = {bid:score}
new_last_result = sorted(last_result.items(), key=lambda k:k[1], reverse=True)[:20]
return new_last_result
# 已经存在于系统的用户
else:
last_result = dict()
# 遍历用户有行为的每一本书
for bookid in actions.keys():
# 判断该本书是否在离线计算的相似key中
if bookid not in itemSim.keys():
continue
sim_books = itemSim[bookid]
# bid是要推荐的书
for bid in sim_books.keys():
if bid in actions.keys():
continue
last_result.setdefault(bid,0.0)
last_result[bid] += sim_books[bid] * actions[bookid] * online_weight
# 若推荐的图书已经在离线被推荐的图书列表里
for bookid in rec_result.keys():
# 若图书已经被评分
if bookid in actions.keys():
continue
last_result.setdefault(bookid, 0.0)
# 加上之前的评分
last_result[bid] += rec_result[bookid] * offline_weight
new_last_result = sorted(last_result.items(), key=lambda k:k[1],reverse=True)[20]
return new_last_result
3、templates
(1)在Book/Book/setting.py里添加
'DIRS': [os.path.join(BASE_DIR, 'templates')],
(2)静态文件路径的配置
STATIC_URL = '/static/'
# os.path.dirname(__file__) 得到文件所在目录,再来一个os.path.dirname()就是目录的上一级
# 配置静态文件的路径
STATIC_ROOT = os.path.join(os.path.dirname(__file__), '../static')
STATICFILES_DIRS = (
('css', os.path.join(STATIC_ROOT, 'css').replace('\\', '/')),
('js', os.path.join(STATIC_ROOT, 'js').replace('\\', '/')),
('img', os.path.join(STATIC_ROOT, 'img').replace('\\', '/')),
('bookimg', os.path.join(STATIC_ROOT, 'bookimg').replace('\\', '/')),
('uploads', os.path.join(STATIC_ROOT, 'uploads').replace('\\', '/')),
)
(3)添加index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>图书推荐系统</title>
<link rel="shortcut icon" href="/static/img/favicon.ico"/>
<link rel="bookmark" href="/static/img/favicon.ico"/>
<link href="/static/css/bootstrap/css/bootstrap.min.css" type="text/css" rel="stylesheet"/>
<link href="/static/css/index.css" type="text/css" rel="stylesheet"/>
<script src="/static/js/jquery-1.10.2.min.js"></script>
<script src="/static/css/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
<div class="head">
<div class="top">
<img src="/static/img/icon.jpg">
<span><a href="/user/action/">{{ user_name }}</a> | <a href="/user/logout/">退出</a> </span>
</div>
<ul class="menavs">
<li><a class="active" href="/index/home/">为你推荐</a></li>
<li class="dropdown">
<!--data-toggle="dropdown"是下拉菜单-->
<a href="/rank/score/" class="dropdown-toggle" data-toggle="dropdown">排行榜<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="/rank/score/">高分榜</a></li>
<li><a href="/rank/tag/">标签云</a></li>
<li><a href="/rank/all/">所有图书</a></li>
</ul>
</li>
<li><a href="/user/action/">我的足迹</a></li>
</ul>
</div>
<div class="main">
<ul class="recomcan">
<li class="recomtitle">
<span>封面</span>
<div>
<span>书名</span>
<span>推荐指数</span>
<span>图书评分</span>
<span>查看详情</span>
<span>相关标签</span>
</div>
</li>
<!--数据循环部分-->
{% for book in books %}
<li class="recomitem">
<img src="/static/bookimg/{{ book.bookid }}.jpg">
<div>
<span>{{ book.bookname }}</span>
<span>{{ book.bookrecscore }} °C</span>
<span>{{ book.bookscore }}分</span>
<span><a href="https://book.douban.com/subject/{{ book.bookid }}" target="_blank">查看详情</a></span>
<span class="sign">
{% for tag in book.booktag %}
<a class="badge badge-default" >{{ tag }}</a>
{% endfor %}
</span>
</div>
</li>
{% endfor %}
<!--数据循环部分end-->
</ul>
</div>
<div class="foot">
<span>@2019 Publish by Smile</span>
</div>
</body>
</html>
(4)添加login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
<!--图标-->
<link rel="shortcut icon" href="/static/img/favicon.ico"/>
<link rel="bookmark" href="/static/img/favicon.ico"/>
<link rel="stylesheet" href="/static/css/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/check.css">
</head>
<body class="loginbody">
<!--从views.py里获取error变量,alert表示弹出窗口"用户未登录"-->
<!--弹出窗口,内容是{{ login_error }}里的内容-->
<!--返回当前显示的文档的完整url-->
<script language="JavaScript">
{ % if error % }
alert("{{ error }}");
{% endif %}
{% if login_error %}
var r = confirm("{{ login_error }}");
if(r==true){
window.location.href = '/user/login/'
}else{
window.location.href = '{{ form }}'
}
{% endif %}
</script>
<div class="login-main">
<h3><center>欢迎登录图书推荐系统</center></h3>
<form role="form" method="post" action="/user/login/">
<input class="incon" name="username" type="text" placeholder="用户名" value="{{ user_name }}">
<input class="incon" name="password" type="password" placeholder="密码" value="{{ uer_pwd }}">
<input class="btnincon" type="submit" value="登录">
<hr style="height:1px;border:none;border-top:1px solid darkgray;" />
<a href="/user/register" class="fl acon"> 新用户?去注册</a>
<!--target="_blank":在新窗口中打开被链接文档-->
<a class="fr acon" href="/admin/login/" target="_blank">管理员登录</a>
</form>
</div>
</body>
</html>