Search For Free — 新闻资讯检索平台
一. 项目概述
基于第一个项目爬虫爬取的数据,完成数据展示网站。
1.1 基本要求
1、用户可注册登录网站,非注册用户不可登录查看数据
2、用户注册、登录、查询等操作记入数据库中的日志
3、爬虫数据查询结果列表支持分页和排序
4、用Echarts或者D3实现3个以上的数据分析图表展示在网站中
5、实现一个管理端界面,可以查看(查看用户的操作记录)和管理(停用启用)注册用户。
1.2 扩展要求
1、实现对爬虫数据中文分词的查询
2、实现查询结果按照主题词打分的排序
3、用Elastic Search+Kibana展示爬虫的数据结果
1.3 技术需求
-
python语言
-
Django框架
-
Jieba分词
-
Bootstrap前端页面搭建
-
Echarts数据分析展示
1.4 项目框架
网站总体设计为B/S架构,设计模式为MTV。由数据层、服务层和展示层组成。
二. demo展示
进入 project1 文件夹,输入python manage.py runserver,访问 http://127.0.0.1:8000/ 即可。
项目演示视频:
model
若上方视频无法观看,请进入这里 web 编程 demo 完整版 观看完整版演示视频~谢谢!
三. 数据库设计
日志表
日志表记录了系统中所有用户的注册,登陆,查询等操作,其包括操作id,具体操作operation,用户user,创建时间created,更新时间updated等属性。
class Userlog(models.Model):
id = models.UUIDField('id',primary_key=True,default=uuid.uuid4)
operation = models.CharField('操作',max_length=256,null=True,blank=True)
user = models.ForeignKey(User,on_delete=models.CASCADE)
created = models.DateTimeField('创建时间', auto_now_add=True)
updated = models.DateTimeField('更新时间', auto_now=True)
各字段属性及样例
用户表
用户表存储着注册过的用户信息和用户属性,用户信息包括:用户id,注册密码password,是否是superuser,用户名,邮箱email,加入系统时间。用户属性包括:用户最后一次登陆时间last_login,是否活跃is_active等属性。
数据表
数据表存储的是爬取下来的网页内容,包括网页url,网页source_name,编码方式,爬取时间,作者author,关键词keyword,具体内容content等。
-
id_fetches为唯一主键PRIMARY KEY,表示新闻的唯一标识id
-
url也限制为唯一键UNIQUE KEY,表示新闻的链接
-
source_name表示新闻网站来源,在此项目中source_name包括三种value:人民网,网易新闻,中国新闻网
-
source_encoding表示编码方式,此项目中编码方式为:人民网 GBK; 网易新闻 utf-8 ;中国新闻网 utf-8
-
title表示新闻的标题
-
keywords表示新闻的关键词
-
author表示新闻的作者,编辑
-
publish_date表示新闻的出版时间
-
crawltime表示新闻的爬取时间
-
content表示新闻的具体内容
-
create time存储当前的时间戳
class China(models.Model):
id_fetches = models.IntegerField(primary_key=True)
url = models.CharField('url',max_length=256,null=True,blank=True)
source_name = models.CharField(max_length=256,null=True,blank=True)
source_encoding = models.CharField(max_length=256,null=True,blank=True)
title = models.CharField(max_length=256,null=True,blank=True)
keywords = models.CharField(max_length=256,null=True,blank=True)
author = models.CharField(max_length=256,null=True,blank=True)
publish_date = models.CharField(max_length=256,null=True,blank=True)
crawltime = models.CharField(max_length=256,null=True,blank=True)
content = models.TextField(null=True,blank=True)
createtime = models.DateTimeField(auto_now_add=True)
各字段属性及样例
四. 网站设计
4.1 用户登陆
1. 前端设计
注册后的用户需要输入账号及其对应的密码,才可完成登陆系统。若输入账号在后端数据库无法找到,则提示用户注册,同样密码若与账号不匹配也无法完成登陆。
只有登陆成功的用户可以查看数据,非注册用户不可登录查看数据。
核心代码
<div id="login"
class="login loginpage " style="text-align: center;margin-top: 200px">
<h1 style="color: white">Search For Free — 新闻资讯检索平台</h1>
<form name="loginform" id="loginform" action="{% url 'dashboard-login' %}" method="post">{% csrf_token %}
<p style="margin-top: 50px">
<label for="user_login" style="color: white">账号
<input type="text" name="username" id="user_login" class="input" placeholder="请输入账号" style="width: 500px;height: 45px;border-radius: 20px;outline: none;color: black"/></label>
</p>
<p style="margin-top: 30px">
<label for="user_pass" style="color: white">密码
<input type="password" name="password" id="user_pass" class="input"
placeholder="请输入密码" style="width: 500px;height: 45px;border-radius: 20px;outline: none;color: black"/></label>
</p>
<p class="submit" style="color: black">
<input type="submit" name="wp-submit" id="wp-submit" class="btn btn-success btn-lg"
value="登录" style="width: 200px;margin-top: 20px;color: black"/>
</p>
</form>
<p id="nav" style="color: white">
如果您没有账号,请点击<a href="{% url 'dashboard-register' %}">注册</a>
</p>
</div>
实现效果
2. 后端设计
获取前端用户输入的username和password字段内容,在数据库中匹配具体字段,并向前端返回不同情况下的提示内容。
核心代码
def user_login(request):
username = None
password = None
if request.method == 'POST':
if not request.POST.get('username'):
messages.error(request, '请输入用户名')
else:
username = request.POST.get('username')
if not request.POST.get('password'):
messages.error(request, '请输入密码')
else:
password = request.POST.get('password')
if username is not None and password is not None:
try:
user = authenticate(username=username, password=password)
# user = User.objects.get(username=phone)
if user is not None:
if user.check_password(password):
login(request, user)
userlog = Userlog(operation='登录',user=user)
userlog.save()
return HttpResponseRedirect('/index')
else:
messages.error(request, '无效的账号')
else:
messages.error(request, '账号或密码错误,请您确认账号和密码')
except:
messages.error(request, '账号或密码错误,请您确认账号和密码')
return render(request, 'dashboard/login.html', )
4.2 用户注册
1. 前端设计
未注册过的用户需要进行注册,我们会要求用户输入的两次密码一致防止密码的错输。
核心代码
<div style="margin-top: 150px" id="register"
class="login loginpage col-lg-offset-4 col-lg-4 col-md-offset-3 col-md-6 col-sm-offset-3 col-sm-6 col-xs-offset-2 col-xs-8">
<h1 style="color: white">Search For Free — 新闻资讯检索平台</h1>
<form name="loginform" id="loginform" action="{% url 'dashboard-register' %}" method="post"
enctype="multipart/form-data" style="color: white;margin-top: 100px">{% csrf_token %}
<p>
<label for="phone">账号<br/>
<input type="text" class="form-control" placeholder="请输入账号"
style="height: 40px;width: 550px;opacity: .9 ;color: #0f0f0f" name="username">
</label>
</p>
<p>
<label for="password">密码<br/>
<input type="password" class="form-control" placeholder="请输入密码"
style="height: 40px;width: 550px;opacity: .9 ;color: #0f0f0f" name="password"></label>
</p>
<p>
<label for="confirm_password">确认密码<br/>
<input type="password" class="form-control" placeholder="请确认密码"
style="height: 40px;width: 550px;opacity: .9;color: #0f0f0f " name="confirm_password"></label>
</p>
<p class="submit">
<input type="submit" name="wp-submit" id="wp-submit" class="btn btn-orange"
value="注册" style="color: #0f0f0f"/>
</p>
</form>
<p id="nav">
<a class="pull-left" href="#" title="Password Lost and Found" style="color: white">忘记密码</a>
<a class="pull-right" href="{% url 'dashboard-login' %}" title="Sign Up" style="color: white">登录</a>
</p>
<div class="clearfix"></div>
</div>
实现效果
2. 后端设计
核心代码
获取前端内容,一方面确认改用户名是否已经被注册,另一方面确认两次输入的密码是否一致,并向前端返回提示信息。
def register(request):
username = None
password = None
confirm_password = None
flag = False
if request.method == 'POST':
if not request.POST.get('username'):
messages.error(request, '请输入用户名')
else:
username = request.POST.get('username')
if not request.POST.get('password'):
messages.error(request, '请输入密码')
else:
password = request.POST.get('password')
if not request.POST.get('confirm_password'):
messages.error(request, '请再次输入密码')
else:
confirm_password = request.POST.get('confirm_password')
if password is not None and confirm_password is not None:
if password == confirm_password:
flag = True
else:
messages.error(request, '两次输入的密码不一致,请重新输入')
filter_result = User.objects.filter(username=username)
if len(filter_result) > 0:
messages.error(request, '对不起,您输入的账号已被注册')
return HttpResponseRedirect('/register/')
if username is not None and password is not None and confirm_password is not None and flag:
user = User.objects.create_user(username=username, password=password, last_login=timezone.now())
user.is_active = True
user.save()
userlog = Userlog(operation='注册', user=user)
userlog.save()
messages.success(request, '注册成功,请登录')
return HttpResponseRedirect('/')
return render(request, 'dashboard/register.html')
4.3 分页排序检索
1. 前端设计
分项搜索功能,可根据 title, keywords, author 进行新闻的搜索,采用字符串匹配的方法,将查询结果以表格形式展现出来。相比较期中项目,本次项目增添了分页和排序功能,优化搜索的查询结果。
以搜索标题中含有人民的新闻为例,查询结果如下:
2. 后端设计
根据前端输入跳转到相应路由,构建sql语句进行mysql数据库查询,结果以json格式返回。关键词搜索结果列表支持分页和排序。
代码中加入sql语句order_by(“publish_date”)实现搜索结果按出版时间排序。
同时引入Paginator实现分页查询,并可以实现跳转到上下页。
def search_for_keyword(request):
keytype = None
keyword = None
if request.method == 'POST':
keytype = request.POST.get('keytype')
keyword = request.POST.get('keyword')
else:
keytype = request.GET.get('keytype')
keyword = request.GET.get('keyword')
user = request.user
if keyword:
if keytype == '1':
china = China.objects.filter(title__contains=keyword).order_by('-publish_date')
else:
china = China.objects.filter(content__contains=keyword).order_by('-publish_date')
paginator = Paginator(china, 10)
page = int(request.GET.get('page', 1))
try:
china = paginator.page(page)
except PageNotAnInteger:
china = paginator.page(1)
except EmptyPage:
china = paginator.page(paginator.num_pages)
userlog = Userlog(operation='关键字检索', user=user)
userlog.save()
return render(request, 'dashboard/search-for-keyword.html',
{'china': china, 'keytype': keytype, 'keyword': keyword})
return render(request, 'dashboard/search-for-keyword.html')
4.4 数据统计分析
利用 Echarts 可视化出不同新闻来源的新闻数量分布。
核心代码
user = request.user
china = China.objects.all()
source_name = []
data = []
for c in china:
if c.source_name is not None and c.source_name != 'None':
source_name.append(c.source_name)
source_name_new = list(set(source_name))
for s in source_name_new:
data.append(China.objects.filter(source_name=s).count())
userlog = Userlog(operation='数据统计查询', user=user)
userlog.save()
print(data,source_name_new)
return render(request,'dashboard/index.html',{'source_name':json.dumps(source_name_new),'data':data})
实现效果
4.5 时间序列分析
用 Echarts 可视化出随着时间各新闻网站新闻数量变化,并可以缩放进度条实现查看特定时间段内的新闻数量变化。
核心代码
def date_data(request):
user = request.user
china = China.objects.all().order_by('publish_date')
date_list = []
data = []
data_list = []
source_list = []
for c in china:
if c.publish_date:
date_list.append(c.publish_date.strftime('%Y-%m-%d'))
source_list.append(c.source_name)
date_list_new = sorted(set(date_list),key=date_list.index)
print(date_list_new)
source_list_new = list(set(source_list))
for s in source_list_new:
if s:
for d in date_list_new:
if d:
data.append(China.objects.filter(publish_date=datetime.strptime(d, '%Y-%m-%d')).count())
data_list.append(data)
userlog = Userlog(operation='数据时间序列统计查询', user=user)
userlog.save()
return render(request,'dashboard/date.html',{'date_list':json.dumps(date_list_new),'data':data,'source_list':json.dumps(source_list_new),'data_list':data_list})
实现效果
4.6 关键词热度可视化
时间热度分析即时间维度上的含特定语义或关键词的新闻规模数量的变化情况,统计每个时间点含特定关键词的新闻的数量,以折线图和饼状图的形式可视化在页面中。
所以思路是根据输入的value,构建sql语句查询含该关键词的新闻,并按出版时间publish_date排序。构建字典word_freq,对于排序好的新闻,遍历每条新闻,对keywords先用replace方法去除一些无用的字符,再用正则表达式匹配出publish_date,搜索出keywords数量,添加到publish_date对应的word_freq上。遍历完成即可得到含特定关键词的新闻数量随时间维度变化的数值表示。再用echarts做出折线图和饼状图,定义好option中title,grid,x轴,y轴等属性,得到可视化效果展示在前端。
核心代码
- 获取关键词随日期变化的出现次数
const regex_c = /[\t\s\r\n\d\w]|[\+\-\(\),\.。,!?《》@、【】"'::%-\/“”]/g;
var regex_d = /\w{3}\s(.*?) 2021/;
var freqchange = function(vals, keyword) {
var regex_k = eval('/'+keyword+'/g');
var word_freq = {};
vals.forEach(function (data){
var content = data["keywords"].replace(regex_c,'');
var publish_date = regex_d.exec(data['publish_date'])
if(publish_date!=null){
publish_date=publish_date[1];
var freq = content.match(regex_k).length;// 直接搜这个词。
word_freq[publish_date] = (word_freq[publish_date] + freq ) || 0;
}
});
console.log(word_freq);
return word_freq;
};
exports.freqchange = freqchange;
- Echarts 作图,对于查询结果 data 按照作图数据格式{name,value}进行解析,并将可视化结果同时以折线图和饼状图展示出来。
let newdata = [];
for (var key in data) {
newdata.push({name:key,value:data[key]});
};
console.log(newdata);
var myChart = echarts.init(document.getElementById("main1"));
option = {
title: {
text: '\"'+$("#analyze_text").val()+'\"'+'该词在新闻中的出现次数随时间变化图'+' '+'\"'+$("#analyze_text").val()+'\"'+'该词在新闻中的出现次数饼状图',
left:'6%'
},
grid:[
{x:'2%',y:'20%',width:'25%',height:'48%'},
{x2:'7%',y:'0%',width:'47%',height:'47%'},
],
xAxis: [
{
gridIndex:0,
type: 'category',
data: Object.keys(data)
},
],
yAxis: [
{
gridIndex: 0,
type: 'value',
}
],
series: [{
data: Object.values(data),
type: 'line',
itemStyle : { normal: {label : {show: true}}}
},
{
name: '访问来源',
type: 'pie', // 设置图表类型为饼图
radius: '55%', // 饼图的半径,外半径为可视区尺寸(容器高宽中较小一项)的 55% 长度。
data:newdata,
}],
};
if (option && typeof option === "object") {
myChart.setOption(option, true);
}
实现效果
4.7 分词词云检索
利用jieba分词作出新闻内容的wordcloud词云,对新闻内容用jieba的cut函数进行分词,并去除其中的停用词(stopwords),筛选掉无意义词,并对文本中出现频率较高的“关键词”予以视觉上的突出和可视化。
核心代码
@login_required
def hot(request):
user= request.user
data = China.objects.all()
r = []
for d in data:
content = d.content
r.append(content)
seg_list = jieba.cut(str(r))
c = Counter()
for x in seg_list:
if len(x) > 1 and x != '\r\n':
c[x] += 1
res = []
for (k, v) in c.most_common(200):
if all(map(lambda c: '\u4e00' <= c <= '\u9fa5', k)):
o = dict()
o['name'] = k
o['value'] = v
res.append(o)
print(res)
userlog = Userlog(operation='热词分析', user=user)
userlog.save()
return render(request, 'dashboard/hot.html',{'res':res})
@login_required
def hot_json(request):
data = China.objects.all()
r = []
for d in data:
content = d.content
r.append(content)
seg_list = jieba.cut(str(r))
c = Counter()
for x in seg_list:
if len(x) > 1 and x != '\r\n':
c[x] += 1
res = []
for (k, v) in c.most_common(300):
if all(map(lambda c: '\u4e00' <= c <= '\u9fa5', k)):
o = dict()
o['name'] = k
o['value'] = v
res.append(o)
return HttpResponse(json.dumps({
"res": res
}))
实现效果
点击高频关键词可以检索到包含关键词的新闻,实现对中文分词查询。
4.8 用户管理
管理界面,可以查看用户状态并管理注册用户。当我们停用某用户时,之后他将无法从登录页面成功登陆,只有再次激活才可以重新登陆。
核心代码
<div class="content-body">
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12">
<table class="table table-hover">
<thead>
<tr>
<th style="text-align: center">序号</th>
<th style="text-align: center">用户名</th>
<th style="text-align: center">邮箱</th>
<th style="text-align: center">状态</th>
<th style="text-align: center">操作</th>
</tr>
</thead>
<tbody>
{% for c in res %}
<tr>
<td style="vertical-align: middle;text-align: center">{{ forloop.counter }}</td>
<td style="text-align: center">{{ c.username }}</td>
<td style="vertical-align: middle;text-align: center">{{ c.email }}</td>
<td style="vertical-align: middle;text-align: center">{{ c.status }}</td>
<td style="vertical-align: middle;text-align: center">
{% if c.status == '激活' %}
<a href="{% url 'dashboard-user-status' c.id '1' %}"
class="btn btn-danger btn-mini">停用</a>
{% else %}
<a href="{% url 'dashboard-user-status' c.id '0' %}"
class="btn btn-danger btn-mini">启用</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
实现效果
4.9 日志管理
管理界面,可以查看用户操作记录。具体来说,用户可以看到user的操作时间和操作内容。
核心代码
<div class="content-body">
<div class="row">
<div class="col-md-12 col-sm-12 col-xs-12">
<table class="table table-hover">
<thead>
<tr>
<th style="text-align: center">序号</th>
<th style="text-align: center">日志</th>
<th style="text-align: center">操作者</th>
<th style="text-align: center">时间</th>
</tr>
</thead>
<tbody>
{% for c in log %}
<tr>
<td style="vertical-align: middle;text-align: center">{{ forloop.counter }}</td>
<td style="text-align: center">{{ c.operation }}</td>
<td style="vertical-align: middle;text-align: center">{{ c.user }}</td>
<td style="vertical-align: middle;text-align: center">{{ c.created }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="metadata-pagination">
{% include 'dashboard/common/log.html' %}
</div>
</div>
</div>
实现效果
五. 总结
在学习和实践的过程中,我基于期中项目进行功能的增添和完善,由此完成了一个完整的新闻查询网站项目,这还是很有成就感的。从数据获取,数据处理,及数据可视化等步骤对数据在系统中的作用及转变流程有了更深一步认识和理解。
在项目中我实现了多种新闻的查询方式,一方面,我利用order_by和Paginator实现了支持分页和排序的数据查询,另一方面,在项目中我引入python的jieba分词对新闻进行分词处理,并将分词结果筛选出高频词,构建词云wordcloud实现高频词的可视化,可同时通过高频词list直接跳转到包含高频词的新闻,实现中文分词的查询。
另外,我增添了注册,登陆等功能,并搭建出用户管理界面进行用户状态和属性的管理查询,保证系统的可用性和信息的安全性。
最后,感谢本学期老师和助教学长学姐们的耐心教导和悉心帮助,让我通过理论和实践交融的方式对网站搭建的前后端逻辑及框架具体实现流程有了亲身理解和实践,收获颇丰!