这东西对我大一新生而言真是太不友善了555...弄了好久、头晕眼花,研究这老师的代码,对着一个个改成自己数据库对应的东西,总算是把数据查询所涉及的这些东西都大致搞完搞明白了...吧?下面就来聊聊我这几天到处找人帮忙、问问题终于整出来的成果...
基于第一个项目爬虫爬取的数据,要让用户可注册登录网站,非注册用户不可登录查看数据。具体的代码很多,就不放在这儿了。做出来的登录页面如图所示:
第一次登录要先注册,注册成功后回到登录页面,若输入密码与用户名不匹配,则会有以下显示:
注册这里的代码有好多,说实话,我也没太懂,只知大概...连上了就行叭!
进入成功后,搜索页面是长这样的。这是对爬虫数据查询结果列表的呈现,可以通过搜关键字查找想要的新闻。
如想找内容里带“三分”字眼的欣慰,就在搜索框内输入“三分”,点查询查找。
表格呈现的前端代码比较简单在这里就不搬运了。实现该服务的后端代码为:
var sql = 'select * from news ';
if(searchparam["t2"]!="undefined"){
sql +=(`where Title like '%${searchparam["t1"]}%' ${searchparam['ts']} Title like '%${searchparam["t2"]}%' `);
}else if(searchparam["t1"]!="undefined"){
sql +=(`where Title like '%${searchparam["t1"]}%' `);
};
if(searchparam["t1"]=="undefined"&&searchparam["t2"]=="undefined"&&searchparam["c1"]!="undefined"){
sql+='where ';
}else if(searchparam["t1"]!="undefined"&&searchparam["c1"]!="undefined"){
sql+='and ';
}
if(searchparam["c2"]!="undefined"){
sql +=(`Content like '%${searchparam["c1"]}%' ${searchparam['cs']} Content like '%${searchparam["c2"]}%' `);
}else if(searchparam["c1"]!="undefined"){
sql +=(`Content like '%${searchparam["c1"]}%' `);
}
if(searchparam['stime']!="undefined"){
if(searchparam['stime']=="1"){
sql+='ORDER BY Time ASC ';
}else {
sql+='ORDER BY Time DESC ';
}
}
代码段意思:从数据库news里查找关键词,若标题、内容有输入的关键词则输出相应新闻;最后一段的意思为按时间顺序呈现所找到的新闻。
数据分析图的展现就更花里胡哨了(bushi),图片页面就比较多了,但基本上是一个意思,都是要把新闻的一些属性抓取出来进行对比呈现。
比如,第一个是新闻发布数随时间变化的条形图,是对新闻发布时间Time的分析呈现。由于我之前找的是连续几天的NBA篮球赛事新闻,所以每天新闻发布数差不多。图长这样:
而决定图形状的关键句——从news中抓出时间并按时间顺序呈现如下:
只从news数据库里抓取新闻时间,后进行归纳生成条形图。
下面的饼图是对新闻来源数量的对比呈现:
与上一个意思大致相同,只是它的分类依据变成了新闻来源,从news数据库里抓取新闻来源,后进行归纳生成饼图。
再来是某个关键词的出现频率随时间变化图(我的电脑可能有bug,抓不全...换了好几个关键词都只能抓到这个程度,我也很无奈啊,凑合着看看...):
这个稍微有点不同的是抓取的关键词是写代码的时候就固定的,搜索news里新闻内容带有关键词——也就是“三分”的内容和时间做参考变量,并最后以时间顺序呈现。
最后是词云(个人觉得没有背景的时候看着更好看),在从新闻内容里找高频词,出现频率越高的词明显能看出字体越大:
搜索高频词:
下面是四个图的操作代码:
$scope.histogram = function () {
$scope.isShow = false;
$http.get("/news/histogram")
.then(
function (res) {
if(res.data.message=='url'){
window.location.href=res.data.result;
}else {
// var newdata = washdata(data);
let xdata = [], ydata = [], newdata;
var pattern = /\d{4}-(\d{2}-\d{2})/;
res.data.result.forEach(function (element) {
// "x":"2020-04-28T16:00:00.000Z" ,对x进行处理,只取 月日
xdata.push(pattern.exec(element["x"])[1]);
ydata.push(element["y"]);
});
newdata = {"xdata": xdata, "ydata": ydata};
var myChart = echarts.init(document.getElementById('main1'));
// 指定图表的配置项和数据
var option = {
title: {
text: '新闻发布数随时间变化'
},
tooltip: {},
legend: {
data: ['新闻发布数']
},
xAxis: {
data: newdata["xdata"]
},
yAxis: {},
series: [{
name: '新闻数目',
type: 'bar',
data: newdata["ydata"]
}]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
}
},
function (err) {
$scope.msg = err.data;
});
};
$scope.pie = function () {
$scope.isShow = false;
$http.get("/news/pie").then(
function (res) {
if(res.data.message=='url'){
window.location.href=res.data.result;
}else {
let newdata = [];
var pattern = /责任编辑:(.+)/;//匹配名字
res.data.result.forEach(function (element) {
// "x": 责任编辑:李夏君 ,对x进行处理,只取 名字
newdata.push({name: element["x"], value: element["y"]});
});
var myChart = echarts.init(document.getElementById('main1'));
var app = {};
option = null;
// 指定图表的配置项和数据
var option = {
title: {
text: '新闻来源数量',
x: 'center'
},
tooltip: {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
orient: 'vertical',
left: 'left',
// data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']
},
series: [
{
name: '访问来源',
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: newdata,
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
// myChart.setOption(option);
app.currentIndex = -1;
setInterval(function () {
var dataLen = option.series[0].data.length;
// 取消之前高亮的图形
myChart.dispatchAction({
type: 'downplay',
seriesIndex: 0,
dataIndex: app.currentIndex
});
app.currentIndex = (app.currentIndex + 1) % dataLen;
// 高亮当前图形
myChart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex: app.currentIndex
});
// 显示 tooltip
myChart.dispatchAction({
type: 'showTip',
seriesIndex: 0,
dataIndex: app.currentIndex
});
}, 1000);
if (option && typeof option === "object") {
myChart.setOption(option, true);
}
;
}
});
};
$scope.line = function () {
$scope.isShow = false;
$http.get("/news/line").then(
function (res) {
if(res.data.message=='url'){
window.location.href=res.data.result;
}else {
var myChart = echarts.init(document.getElementById("main1"));
option = {
title: {
text: '"三分"该词在新闻中的出现次数随时间变化图'
},
xAxis: {
type: 'category',
data: Object.keys(res.data.result)
},
yAxis: {
type: 'value'
},
series: [{
data: Object.values(res.data.result),
type: 'line',
itemStyle: {normal: {label: {show: true}}}
}],
};
if (option && typeof option === "object") {
myChart.setOption(option, true);
}
}
});
};
$scope.wordcloud = function () {
$scope.isShow = false;
$http.get("/news/wordcloud").then(
function (res) {
if(res.data.message=='url'){
window.location.href=res.data.result;
}else {
var mainContainer = document.getElementById('main1');
var chart = echarts.init(mainContainer);
var data = [];
for (var name in res.data.result) {
data.push({
name: name,
value: Math.sqrt(res.data.result[name])
})
}
var maskImage = new Image();
maskImage.src = './images/logo.png';
var option = {
title: {
text: '所有新闻内容 jieba分词 的词云展示'
},
series: [{
type: 'wordCloud',
sizeRange: [12, 60],
rotationRange: [-90, 90],
rotationStep: 45,
gridSize: 2,
shape: 'circle',
maskImage: maskImage,
drawOutOfBound: false,
textStyle: {
normal: {
fontFamily: 'sans-serif',
fontWeight: 'bold',
// Color can be a callback function or a color string
color: function () {
// Random color
return 'rgb(' + [
Math.round(Math.random() * 160),
Math.round(Math.random() * 160),
Math.round(Math.random() * 160)
].join(',') + ')';
}
},
emphasis: {
shadowBlur: 10,
shadowColor: '#333'
}
},
data: data
}]
};
maskImage.onload = function () {
// option.series[0].data = data;
chart.clear();
chart.setOption(option);
};
window.onresize = function () {
chart.resize();
};
}
});
最后是一个管理端,实现后查询用户的操作记录和管理(停用启用)注册用户的页面长这样:
呈现该页面的前端代码就不在这里赘述了。而在此之前要注册一个管理员账号,我使他为root,在我的数据库里将他的ID改为0,则他成为管理员。
root成为管理员后,用该账号登录,即可完成查看和管理注册用户的目的。比如在用户表中的用户1右边的框框内勾选停用,回到数据库便会发现他的state变成了0,即该用户处于被停用状态。若再用该账号登录则会显示“用户不存在或已被停用”,无法用该账号登录查看数据,只能通过管理员修改使用资格。
实现管理端界面部分代码如下:
router.get('/administrate',function(req,res,next){
res.writeHead(200, { 'Content-Type': 'text/html; charset= utf-8' });
let $=cheerio.load(fs.readFileSync("./public/administrate.html"));
if(admitted.id!=0)
{
res.end('<script>alert("没有权限!");location.replace("/");</script>');
return;
}
database.query_noparam('SELECT * FROM user;', function (qerr, vals, fields) {
if (qerr) {
console.error(qerr);
res.end(`<script>alert("Database Error!");</script>`);
return;
}
for (let i in vals) {
$('table#usertable').append(`<tr class="item-usertable" dbid=${i}>
<td>${vals[i].id}</td>
<td>${vals[i].username}</td>
<td><a href="/users/changeState?id=${vals[i].id}&state=${1 - vals[i].state}"><button>${vals[i].state == 0 ? "启用" : "停用"}</button></a></td>
</tr>`);
}
database.query_noparam('SELECT * FROM user_action;', function (qerr1, vals1, fields1){
if (qerr1) {
console.error(qerr1);
return;
}
for (let i in vals1) {
$('table#user_action').append(`<tr class="item-userlog" dbid=${i}>
<td>${vals1[i].id}</td>
<td>${vals1[i].username}</td>
<td>${vals1[i].request_method}</td>
<td>${vals1[i].request_url}</td>
</tr>`);
}
res.end($.html());
});
});
});
router.get('/changeState', function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
let userID = parseInt(req.query.id);
let nextState = parseInt(req.query.state);
if (admitted.id != 0) {
res.end('<script>alert("没有权限!");location.replace("/users");</script>');
return;
}
database.query('UPDATE user SET state = ? WHERE id = ?;', [nextState, userID], function(qerr, vals, fields) {
if (qerr) {
console.error(qerr);
return;
}
res.end('<script>alert("改变用户状态成功!");location.replace("/users/administrate");</script>');
});
});
module.exports = router;
总之,数据呈现网站内容、时间、来源什么的都要和自己的数据库对应上才能显示得出来。而且登录注册用户的信息最后也会生成表格存入你的数据库里,应该说数据显示网站和数据库是相连的。
大作业真的好难,但是弄好了之后还是很感动的(把自己感动了),收获感满满。