一、出租车数据分析
出租车是我们经常乘坐的交通工具,但是经常会遇到打车难的问题,给我们生活和工作带来诸多不便。本章介绍 Spark 在人们打车出行生活中的应用,该应用把某地区各出租车实时的并带有地理坐标的 GPS(Global Positioning System)点作为分析对象,使用 Kmeans 聚类方法把出租车轨迹点按簇分类,通过该分类方法找到出租车出现密集的地方,并用地图的方式进行可视化展示,为人们的出行提供新的思路。
本章主要内容如下。
(1)准备数据并对数据特点进行分析。
(2)从文本文件创建 Spark DataFrame。
(3)使用 Spark 的机器学习库 Kmeans 进行聚类。
(4)使用百度地图对聚类的结果进行可视化。
1.数据处理
一般情况下,在进行分析之前需要对数据的分布、状态等有一个整体的了解,从而确定数据使用哪种方法进行分析,进而对数据进行预处理。本章使用文本数据,打开数据观察发现,数据集合中存在缺失项或是 GPS 定位点坐标无效的情况,因此需要对此种情况进行处理。因为出租车点上传的速率非常快并且密度大,所以可以把其中缺失和无效的数据进行删除,对数据分析不会造成较大的影响。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QuzNjsmS-1623383617088)(images/image-20200519094316427.png)]
其中,tid:出租车编号;lat:纬度;lon:经度;time:时间戳;tid 的值相同表示相同的车在不同的时间所处的位置。
2.数据分析
在上一节整理的数据的基础上,使用 Spark 从文本创建 DataFrame,并结合 Kmeans 机器学习聚类方法,实现对出租车在空间位置上的聚类。Kmeans 聚类可根据设定的聚类个数找出若干个聚类中心,对于出租车来讲就是出租车经常出现位置点坐标。
2.1创建 DataFrame
from pyspark.sql import SparkSession
from pyspark import SparkContext
from pyspark.ml.linalg import Vectors
sc = SparkContext("local[4]", "taxi")
taxi_data = sc.textFile("datas/taxi.csv")
taxi_rdd = taxi_data.map(lambda line: line.split(','))
# 创建矢量 RDD,矢量的两个参数分别为纬度和经度。下面的聚类函数需要 RDD 进行聚类。
taxi_row = taxi_rdd.map(lambda x: (Vectors.dense(x[1], x[2]),))
#显示数据的前五行
print(taxi_row.collect()[:5])
sqlsc = SparkSession.builder.getOrCreate()
taxi_df = sqlsc.createDataFrame(taxi_row, ["features"])
2.2KMeans 聚类分析
KMeans 是最常用的聚类算法之一,它将数据点聚类成预定义的簇数。Spark MLlib 实现了包括一个并行化的 k-means++,称为 kmeans||.。
from pyspark.ml.clustering import KMeans # 引入聚类包
kmeans = KMeans(k=3, seed=1) # 聚成 3 类
model = kmeans.fit(taxi_df) # 注意,传入的 DataFrame 是矢量名称为 features 的集合
centers = model.clusterCenters() # 产生聚类集合
# 聚类的结果是一系列的点集,这些点集也就是出租车聚集的地区,上述代码中将数据聚类成 3 类,如图 9-2 所示。
print(centers)
3.百度地图可视化
通过 Spark 提供的 Kmeans 聚类方法已经找到了出租车聚集的地图坐标,但是并不能清楚地看到具体的位置,因此需要通过可视化的方法把数据在地图上进行展示。百度地图是国内顶级的地图服务提供商之一,在提供了基于位置服务的同时也提供了在不同平台下的对外开放接口,允许用户自定义地图并根据相应业务逻辑开发自己的地理信息应用。本节利用百度地图为开发者提供的第三方开发接口,并对聚类结果进行可视化,让结果的展现更直接。
3.1申请地图 key
在使用百度地图接口之前需要,通过百度地图的一个认证,用户要在百度地图开发平台中申请一个密钥 key。读者可以登录百度地图官网,注册个人账号,然后选择申请 key,申请界面如图:
申请地址:http://lbsyun.baidu.com/
地图Api控制台:http://lbsyun.baidu.com/apiconsole/center#/home
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xfsggRFk-1623383617089)(images/image-20200519102035742.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I6iHEozU-1623383617090)(images/image-20200519102105847.png)]
3.2聚类结果可视化
html文件要在apache或者ngnix服务器中运行,因为百度地图API中设置了白名单。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
body,
html,
#allmap {
width: 100%;
height: 100%;
overflow: hidden;
margin: 0;
font-family: "微软雅黑";
}
</style>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=Dv4X6zApF1yzMQNFoEypNfLX"></script>
<title>Kmeans 聚类可视化</title>
</head>
<body>
<div id="allmap"></div>
</body>
</html>
<script type="text/javascript">
// 百度地图API功能
var map = new BMap.Map("allmap"); // 创建Map实例
map.enableScrollWheelZoom(); // 允许滑轮进行放大缩小
map.centerAndZoom(new BMap.Point(104.01741982, 30.67598985), 13);// 初始位置与范围
map.addControl(new BMap.NavigationControl());// 添加平移缩放控件
map.addControl(new BMap.ScaleControl());// 添加比例尺控件
var myP1 = new BMap.Point(104.01741982, 30.67598985); // 声明点对象
var myP2 = new BMap.Point(103.65063611, 30.89504347);
var myP3 = new BMap.Point(104.07063581, 30.64562312);
map.clearOverlays(); // 清空地图中的对象
var marker1 = new BMap.Marker(myP1); // 定义点样式,默认为红色水滴形状
var marker2 = new BMap.Marker(myP2);
var marker3 = new BMap.Marker(myP3);
map.addOverlay(marker1); // 添加点到地图
map.addOverlay(marker2);
map.addOverlay(marker3);
</script>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lj3ygxcX-1623383617091)(images/image-20200519105430246.png)]
地图
map.addOverlay(marker2);
map.addOverlay(marker3);
</script>
[外链图片转存中…(img-Lj3ygxcX-1623383617091)]
二、图书推荐系统
注意:在使用源代码请把所有<% %>请替换为[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oxpYEXA0-1623383822799)(images/image-20200529210559026.png)],为jekyll编译的问题不能编译通过生成Github Page.
1.Django简介
参照Django菜鸟教程:https://www.runoob.com/django/django-tutorial.html
pip install django
1.1.ORM 模型
对象关系映射 ORM(Object Relational Mapping)是一种在面向对象编程语言里实现不同类型系统数据之间转换的程序技术。
class user(models.Model):
#name 字段 max_length 为字段长度, default 为默认值
name=model.CharFiled(max_length=50 , default=‘‘)
email=model.EmailField()
password=model.CharField(max_length=8 , default=‘‘)
1.2.Template 模板
Django 的模板引擎为定义应用程序面向用户的层提供了一种强大的迷你语言,使应用程序和前端逻辑分离。模板由 HTML 和模板语法关键词组成,并不需要用到 Python 的知识。
Django 模板采用前端模块化的思路,可以将模块的前端和后端进行封装,用继承和包含的方法实现模块的重用。HTML 之间可以通过 extends 关键字和 block 关键字进行相互嵌套。
1.base.html
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<!-- 头部导航区域 -->
<% block top %>
<% endblock %>
<!-- 中部内容区域 -->
<% block content %>
<% endblock %>
<!-- 底部区域 -->
<% block bottom %>
<% endblock %>
</body>
</html>
2.top.html
<% extends 'base.html' %>
<% block top %>
<div><h1> 此处为顶部导航界面 </h1></div>
<% endblock %>
3.content.html
<% extends 'base.html' %>
<% block content %>
<div><h1> 此处为中部内容界面 </h1></div>
<% endblock %>
4.bottom.html
<% extends 'base.html' %>
<% block bottom %>
<div><h1> 此处为底部内容界面 </h1></div>
<% endblock %>
接下来我们需要向Django说明模板文件的路径,修改settings.py,修改 TEMPLATES 中的 DIRS 为**[BASE_DIR+"/templates",]**,如:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR + "/templates", ],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
1.3 View 视图
Django 是一种基于 MVT(model,view,template)模型的 Web 开发框架。前端请求的后台响应是通过 view.py 文件中对应的处理函数进行接收请求进行处理。示例代码如下所示。
def index(request):
book_list= book.objects.all()
usr=request.session.get('user' , None) # 获取当前登录用户名称
userid=request.session.get('userid' , None) # 获取当前登录用户的唯一标识 id
return render(request , 'home/index.html' , locals()) # 渲染主页并向模板传递数据
2.Django 项目搭建
2.1 项目创建
django-admin.py startproject testdjango # startproject :创建项目 film :项目名称
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aqMWbrdi-1623383682886)(images/image-20200526185338773.png)]
- testdjango: 项目的容器。
- manage.py: 一个实用的命令行工具,可让你以各种方式与该 Django 项目进行交互。
- testdjango/init.py: 一个空文件,告诉 Python 该目录是一个 Python 包。
- testdjango/asgi.py: 一个 ASGI 兼容的 Web 服务器的入口,以便运行你的项目。
- testdjango/settings.py: 该 Django 项目的设置/配置。
- testdjango/urls.py: 该 Django 项目的 URL 声明; 一份由 Django 驱动的网站"目录"。
- testdjango/wsgi.py: 一个 WSGI 兼容的 Web 服务器的入口,以便运行你的项目。
python manage.py runserver 0.0.0.0:8000
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y02ZmFB6-1623383682887)(images/225A52EA-25EF-4BF1-AA5A-B91490CBF26D.jpg)]
2.2 视图和 URL 配置
在先前创建的 testdjango目录下的 testdjango目录新建一个 views.py 文件,并输入代码:
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello world ! ")
接着,绑定 URL 与视图函数。打开 urls.py 文件,删除原来代码,将以下代码复制粘贴到 urls.py 文件中:
from django.urls import path
from . import views
urlpatterns = [
path('hello/', views.hello),
]
2.3 创建应用
Django 规定,如果要使用模型,必须要创建一个 app。我们使用以下命令创建一个 TestModel 的 app:
django-admin.py startapp TestModel
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lVuTyBlF-1623383682889)(images/image-20200526190940213.png)]
2.4 创建模型
我们修改 TestModel/models.py 文件,代码如下:
from django.db import models
# Create your models here.
class user(models.Model):
name = models.CharField(max_length=50, default='') # 用户名
email = models.EmailField() # 邮箱地址
password = models.CharField(max_length=6, default='admin') # 密码
def __str__(self):
return self.name
class Meta:
verbose_name = "用户管理" # 修改 Admin 后台 App 名称
verbose_name_plural = "用户管理"
class hits(models.Model):
userid = models.IntegerField(default=0)
bookid = models.IntegerField(default=0)
hitnum = models.IntegerField(default=0)
def __str__(self):
return str(self.userid)
class Meta:
verbose_name = "单击量"
verbose_name_plural = "单击量"
class book(models.Model):
id = models.CharField(max_length=50, blank=False, verbose_name="ID", default='', primary_key=True)
name = models.CharField(max_length=50, blank=False, verbose_name="书名", default='')
price = models.CharField(max_length=50, blank=False, verbose_name="价格", default='')
cover = models.ImageField(verbose_name="封面", upload_to='upload', default='img/default.png')
introduction = models.TextField(verbose_name="介绍", blank=True, default='')
url = models.URLField(verbose_name='URL', blank=True, default='')
publish = models.CharField(verbose_name=' 出版社 ', max_length=50, default='', blank=True)
rating = models.CharField(verbose_name=' 评分 ', max_length=5, default='0')
def __str__(self):
return self.name
class Meta:
verbose_name = " 图书管理 "
verbose_name_plural = " 图书管理 "
接下来在 settings.py 中找到INSTALLED_APPS这一项,如下:
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'TestModel', # 添加此项
)
在命令行中运行:
$ python manage.py migrate # 创建表结构
$ python manage.py makemigrations TestModel # 让 Django 知道我们在我们的模型有一些变更
$ python manage.py migrate TestModel # 创建表结构
2.5 Admin 管理工具
Django 提供了基于 web 的管理工具。
Django 自动管理工具是 django.contrib 的一部分。你可以在项目的 settings.py 中的 INSTALLED_APPS 看到它:
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
通常我们在生成项目时会在 urls.py 中自动设置好,我们只需去掉注释即可。
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
你可以通过命令 python manage.py createsuperuser 来创建超级用户,如下所示:
# python manage.py createsuperuser
启动开发服务器,然后在浏览器中访问 http://127.0.0.1:8000/admin/,得到如下界面:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lH4VaHkr-1623383682890)(images/admin1.png)]
之后输入用户名密码登录,界面如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ChjvPt9Z-1623383682891)(images/A995340B-8F8C-4777-9B79-846B6A34508A.jpg)]
3.推荐引擎设计
3.1导入数据
本章实验数据来自 GitHub 共享者 moverzp 提供的 GoodBooks 数据集,读者可以在 GitHub中搜索 moverzp 即可找到该贡献者的仓库。 该数据集抓取自豆瓣图书网站,一共有 5 万多条,主要包含序号、书名、评分、价格、出版社、爬取链接的地址,数据格式如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MY1dN0ZY-1623383682892)(images/image-20200527171230075.png)]
为了在网页前端界面上进行展示,并对图书信息进行查看,需要先将图书信息导入到数据库中。该实验主要使用的 Django 自动生成的 SQLite 数据库进行数据存储,通过 Python 脚本把 csv格式文件导入到数据库中。其主要思路是通过在前端单击浏览按钮选择图书表格上传到后台,后台接收到 csv 文件后进行解析,通过 Python 代码打开文件并逐条将数据写入到数据库中,核心代码如下。
# 上传图书数据到服务器端并入库
def handle_upload_file(name, file):
path = os.path.join(settings.BASE_DIR, 'uploads') # 服务器上传地址
fileName = path + '/' + name # 文件名
with open(fileName, 'wb') as destination: # 接收数据并保存到服务器端
for chunk in file.chunks():
destination.write(chunk)
insertToSQL(fileName) # 将数据插入到数据库中
def insertToSQL(fileName):
txtfile = open(fileName, 'r', encoding='UTF-8')
for line in txtfile.readlines(): # 逐行读取数据
try:
bookinfo = line.split(',') # 数据按照逗号切分获取各个字段
id = bookinfo[0].decode().encode('utf-8') # 图书编号
name = bookinfo[1].decode().encode('utf-8') # 图书名称
rating = bookinfo[2].decode().encode('utf-8') # 图书评分得分
price = bookinfo[3].decode().encode('utf-8') # 图书价格
publish = bookinfo[4].decode().encode('utf-8') # 出版社
url = bookinfo[5].decode().encode('utf-8') # 豆瓣链接
try:
# 创建图书对象
bk_entry = book(name=name, price=price, url=url, publish=publish, rating=rating)
bk_entry.save() # 插入数据到数据库
except:
print('save error' + id)
except:
print('read error ' + id)
def importBookData(request):
if request.method == 'POST':
file = request.FILES.get('file', None) # 获取上传的文件信息
if not file:
return HttpResponse('None File uploads !')
else:
name = file.name
handle_upload_file(name, file)
return HttpResponse('success')
return render(request, 'upload.html')
3.2训练模型
因为用户在访问图书网站的时候不一定会对图书进行评分,所以不根据用户评分来进行训练模型,而根据用户的浏览记录进行相似性推荐。在项目的后台业务逻辑中会收集用户的单击事件,记录下用户 id、图书 id 以及单击的次数等信息,数据格式如表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pJWBnfPQ-1623383682893)(images/image-20200527194253126.png)]
因为不根据用户评分进行训练,所以在训练时候采用隐式评分模型,核心代码如下:
from pyspark import SparkContext
from pyspark.sql import SparkSession, Row
from pyspark.mllib.recommendation import ALS
import os
import shutil
sc = SparkContext() # 获取 Spark 上下文
txt = sc.textFile('datas/hit.txt') # 读取本地用户浏览记录文件
ratingsRDD = txt.flatMap(lambda x: x.split()).map(lambda x: x.split(',')) # 用户记录转换为 RDD
sqlContext = SparkSession.builder.getOrCreate() # 创建 sqlContext
user_row = ratingsRDD.map(lambda x: Row( # 将 RDD 转换成行数据
userid=int(x[0]), bookid=int(x[1]), hitnum=int(x[2])
))
user_df = sqlContext.createDataFrame(user_row)
user_df.registerTempTable('test') # 登录临时表
datatable = sqlContext.sql("select userid, bookid, sum(hitnum) as hitnum from test group by userid , bookid") # 统计用户单击过得图书次数
bookrdd = datatable.rdd.map(lambda x: (x.userid, x.bookid, x.hitnum))
model = ALS.trainImplicit(bookrdd, 10, 10, 0.01) # 训练模型
if os.path.exists('recommendModel'): # 判断是否存在模型文件夹
shutil.rmtree('recommendModel') # 递归删除文件夹
model.save(sc, 'recommendModel') # 保存模型到本地
3.3图书推荐
模型训练完毕后,可以通过 MatrixFactorizationModel 类的 load 方法加载模型,该方法有两个参数,第一个参数 spark 初始化上下文,第二个参数为用户的编号,可以通过 recommendProducts函数对用户进行图书推荐,该函数有两个参数,第一个参数为用户编号,第二个编号为推荐的图书数量,可以通过前端请求灵活设定推荐的数目。对用户推荐的结果存到 Redis 内存数据库中,后台定时执行该训练代码更新数据库,随着用户浏览量的增加和系统用户量的增加,推荐会越来越准确。
import redis
from pyspark import SparkContext
from pyspark.mllib.recommendation import MatrixFactorizationModel
pool = redis.ConnectionPool(host='localhost', port=6379)
redis_client = redis.Redis(connection_pool=pool)
sc = SparkContext()
def getRecommendByUserId(userid, rec_num):
try:
model = MatrixFactorizationModel.load(sc, 'recommendModel')
result = model.recommendProducts(userid, rec_num)
temp = ''
for r in result:
temp += str(r[0]) + ',' + str(r[1]) + ',' + str(r[2]) + '|'
redis_client.set(userid, temp)
print('load model success ---' + temp)
except Exception as e:
print('load model failed!' + str(e))
sc.stop()
getRecommendByUserId(1, 5)
4.系统设计与实现
4.1BootStrap介绍与使用
Bootstrap 是 Twitter 推出的一个用于前端开发的开源工具包。它由 Twitter 的设计师 Mark Otto和 Jacob Thornton 合作开发,是一个 CSS/HTML 框架。
BootStrap中文网:https://www.bootcss.com/
Bootstrap 教程:https://www.runoob.com/bootstrap/bootstrap-tutorial.html
Bootstrap 已经定义了包括按钮、表单、表格等基本元素在内的多种不同的样式,不需要开发者再去重新写 css 样式去控制外观。同时 Bootstrap 还提供了栅格布局结构,可以通过简单的 class设置即可实现响应式布局以适应不同屏幕尺寸的终端。
bootstrap除了包含一个css文件之外还包含有BootStrap.min.js文件,因为js文件是基于JQuery开发的,所以在引用 bootstrap 时候需要先引入 jquery,引用方式如下。
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
注:使用 cdn 时候,如果网络连接断开会导致样式访问不到,网站排版错乱。
4.2Redis数据库安装与使用
Redis教程:https://www.runoob.com/redis/redis-tutorial.html
Python redis 使用介绍:https://www.runoob.com/w3cnote/python-redis-intro.html
pip3 install redis
简单实例:
import redis # 导入redis 模块
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
r.set('name', 'runoob') # 设置 name 对应的值
print(r['name'])
print(r.get('name')) # 取出键 name 对应的值
print(type(r.get('name'))) # 查看类型
连接池:
import redis # 导入redis 模块
pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)
r = redis.Redis(connection_pool=pool)
r.set('name', 'runoob') # 设置 name 对应的值
print(r.get('name')) # 取出键 name 对应的值
4.3视图与路由设计
视图views.py需要应用的包:
from django.shortcuts import render
from django.http import HttpResponse, HttpResponseRedirect
import os
from testdjango import settings
from .models import *
from utils import tools
import redis
(1)创建用户登录视图
def login(request):
if request.method == 'POST': # 相应前台的 POST 事件
name = request.POST.get('name') # 接收前台传递的用户名
password = request.POST.get('password') # 接收前台传递的密码
userEntry = user.objects.filter(name=name, password=password) # 查询数据库
if userEntry.exists(): # 判断是否存在用户
request.session['user'] = name # 用户名写入到 session 会话
request.session['userid'] = userEntry[0].id
return HttpResponseRedirect('/')
return render(request, 'login.html')
(2)创建用户注册视图,核心代码如下
def register(request):
if request.method == 'POST':
name = request.POST.get('name')
password = request.POST.get('password')
userEntry = user(name=name, password=password)
userEntry.save()
return HttpResponseRedirect('/login/')
return render(request, 'register.html', locals())
(3)创建用户单击视图,核心代码如下
def getBookInfo(request):
id = request.GET.get('id')
bk = book.objects.get(id=id)
# 设置单击量
username = request.session.get('user', None)
currentuser = user.objects.get(name=username)
try:
hit = hits.objects.get(userid=currentuser.id, bookid=id)
hit.hitnum += 1
hit.save()
except:
hit = hits()
hit.bookid = id
hit.hitnum = 1
hit.userid = currentuser.id
hit.save()
data = str(currentuser.id) + ',' + str(id) + ',' + str(1)
tools.writeToLocal('datas/hit.txt', data)
return render(request, 'detail.html', locals())
(4)创建主页,核心代码如下
def index(request):
book_list = book.objects.all()
usr = request.session.get('user', None)
userid = request.session.get('userid', None)
return render(request, 'index.html', locals())
(5)创建推荐视图,核心代码如下
pool = redis.ConnectionPool(host='localhost', port=6379)
redis_client = redis.Redis(connection_pool=pool)
def getRecommendBook(request):
userid = request.session.get('userid', None)
recommendbook = redis_client.get(int(userid))
booklist = str(recommendbook).split('|')
bookset = []
for bk in booklist[:-1]:
bookid = bk.split(',')[1]
bk_entry = book.objects.get(id=bookid)
bookset.append(bk_entry)
return render(request, 'recommend.html', locals())
(6)路由设计,核心代码如下
from django.contrib import admin
from django.urls import path
from TestModel import views
urlpatterns = [
path('admin/', admin.site.urls),
path('hello/', views.hello),
path('/', views.index),
path('importBookData/', views.importBookData),
path('login/', views.login),
path('register/', views.register),
path('getBookInfo/', views.getBookInfo),
path('getRecommendBook/', views.getRecommendBook),
]
(7)在项目的根路径下新建utils包,并新建tools.py,内容如下:
def writeToLocal(filepath, str):
ft = open(filepath, "a+")
ft.writelines(str + '\n')
ft.close()
4.4界面设计
在项目的路径下新增templates目录,分别新建下面的文件:
(1)base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>图书推荐系统</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<!-- 中部内容区域 -->
<div class="container">
<div class="row" >
<div class="col-md-6">
<% block content %>
<% endblock %>
</div>
</div>
</div>
<script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script></body>
</html>
(2)login.html
<% extends 'base.html' %>
<% block content %>
<form action="/login/" method="post">
<% csrf_token %>
<div class="form-group">
<label for="name">用户名</label>
<input type="text" class="form-control" id="name" name="name" placeholder="请输入用户名">
</div>
<div class="form-group">
<label for="name">密码</label>
<input type="password" class="form-control" id="password" name="password" placeholder="请输入密码">
</div>
<button type="submit" class="btn btn-default">登录</button>
<a href="/register/" class="btn btn-primary">注册</a>
</form>
<% endblock %>
(3)register.html
<% extends 'base.html' %>
<% block content %>
<form action="/register/" method="post">
<% csrf_token %>
<div class="form-group">
<label for="name">用户名</label>
<input type="text" class="form-control" id="name" name="name" placeholder="请输入用户名">
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" id="password" name="password" placeholder="请输入密码">
</div>
<button type="submit" class="btn btn-default">注册</button>
</form>
<% endblock %>
(4)index.html
<% extends 'base.html' %>
<% block content %>
<table class="table">
<caption>图书列表</caption>
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>价格</th>
</tr>
</thead>
<tbody>
<% for book in book_list %>
<tr><td><a href="/getBookInfo/?id={{ book.id }}">{{ book.id }}</a></td><td>{{ book.name }}</td><td>{{ book.price }}</td></tr>
<% endfor %>
</tbody>
</table>
<a href="/getRecommendBook/" class="btn btn-primary">我的推荐列表</a>
<% endblock %>
(5)detail.html
<% extends 'base.html' %>
<% block content %>
<form>
<div class="form-group">
<label for="name">书名</label>
<input type="text" class="form-control" id="name" name="name" value="{{ bk.name }}" disabled>
</div>
<div class="form-group">
<label for="name">出版社</label>
<input type="text" class="form-control" id="publish" name="publish" value="{{ bk.publish }}" disabled>
</div>
<div class="form-group">
<label for="name">价格</label>
<input type="text" class="form-control" id="price" name="price" value="{{ bk.price }}" disabled>
</div>
<div class="form-group">
<label for="name">评分</label>
<input type="text" class="form-control" id="name" name="name" value="{{ bk.rating }}" disabled>
</div>
<div class="form-group">
<label for="name">URL</label>
<input type="text" class="form-control" id="url" name="url" value="{{ bk.url }}" disabled>
</div>
<a href="/" class="btn btn-primary">返回</a>
</form>
<% endblock %>
(6)recommend.html
<% extends 'base.html' %>
<% block content %>
<table class="table">
<caption>推荐列表</caption>
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>价格</th>
</tr>
</thead>
<tbody>
<% for book in bookset %>
<tr><td><a href="/getBookInfo/?id={{ book.id }}">{{ book.id }}</a></td><td>{{ book.name }}</td><td>{{ book.price }}</td></tr>
<% endfor %>
</tbody>
</table>
<a href="/" class="btn btn-primary">返回</a>
<% endblock %>
(7)upload.html
<% extends 'base.html' %>
<% block content %>
<form action="/importBookData/" method="post" enctype="multipart/form-data">
<% csrf_token %>
<label> 请选择上传的文件: </label>
<input class="form-control" type="file" name="file">
<button class="btn btn-primary" type="submit"> 上传 </button>
</form>
<% endblock %>
5.Issue
1.django.template.exceptions.TemplateDoesNotExist: upload.html
解决方案:
- 在项目的目录下面新增templates目录
- 在settings.py的TEMPLATES中修改DIRS设置,添加模板路径
2.RuntimeError: You called this URL via POST, but the URL doesn’t end in a slash and you have APPEND_SLASH set
解决方案:在settings.py中设置APPEND_SLASH = False
3.Forbidden (CSRF token missing or incorrect.)
解决方案:https://blog.csdn.net/weixin_42694291/article/details/86165884
确认settings.py中MIDDLEWARE的有:django.middleware.csrf.CsrfViewMiddleware
html中的form添加模板标签<% csrf_token %>
6.参考文档
- Django教程 https://www.runoob.com/django/django-tutorial.html
- 基于Spark ALS算法的个性化推荐:https://www.cnblogs.com/wheng/p/11795268.html
- Redis教程:https://www.runoob.com/redis/redis-tutorial.html
- BootStrap中文网:https://www.bootcss.com/
- Bootstrap 教程:https://www.runoob.com/bootstrap/bootstrap-tutorial.html
三、转载申明
本文转自csdn、博客园多个网站汇总所得,属于个人笔记,不与转载