爬虫期末作业
爬虫期末作业
- 一、基本要求
- 1.用户可注册登录网站,非注册用户不可登录查看数据
- (1)前端建立登录和注册网页(运用html和css)
- (2)后端实现
- 2.用户注册、登录、查询等操作记入数据库中的日志
- 3.爬虫数据查询结果列表支持分页和排序
- 4.用Echarts或者D3实现3个以上的数据分析图表展示在网站中
- 5.实现一个管理端界面,可以查看(查看用户的操作记录)和管理(停用启用)注册用户。
- 二、拓展要求
- 1.实现对爬虫数据中文分词的查询
- 2.实现查询结果按照主题词打分的排序
- 3.用Elastic Search+Kibana展示爬虫的数据结果
- 总结
基于第一个项目爬虫爬取的数据,完成数据展示网站。

一、基本要求
1.用户可注册登录网站,非注册用户不可登录查看数据
这里有几个分任务:
(1)前端建立登录和注册网页(运用html和css)
我学习了一波大神分享的网页设置,做了个动态毛玻璃样式的网页:

底下框起来的两行分别是 找回密码 和 注册账号,
login.html
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Glassmorphism Login Formtitle><link rel="stylesheet" href="style.css"><script type="text/javascript" src="login.js">script><script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js">script>
head>
<body><section><div class="color">div><div class="color">div><div class="color">div><div class="box"><div class="square" style="--i:0;">div><div class="square" style="--i:1;">div><div class="square" style="--i:2;">div><div class="square" style="--i:3;">div><div class="square" style="--i:4;">div><div class="container"><div class="form"><h2>Login Formh2><form><div class="inputBox"><input id="uname" type="text" name="user" placeholder="Username">div><div class="inputBox"><input id="upass" type="password" name="psw" placeholder="Password">div><div class="inputBox"><input type="submit" value="Login">div><p class="forget">Forget password ? <a href="#">Click Herea>p><p class="forget">Don't have an account ? <a href="sign.html">Sign upa>p>form>div>div>div>section>
body>
html>
点击注册账号的话会转入注册界面👇

主要提供了输入用户名和密码部分的输入框以及一个注册按钮。
sign.html
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>注册界面title><script type="text/javascript" src="login.js">script><link rel="stylesheet" href="style.css"><script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js">script>
head>
<body><section><div class="color">div><div class="color">div><div class="color">div><div class="box"><div class="square" style="--i:0;">div><div class="square" style="--i:1;">div><div class="square" style="--i:2;">div><div class="square" style="--i:3;">div><div class="square" style="--i:4;">div><div class="container"><div class="form"><h2>Signup Formh2><form><div class="inputBox"><input id="uname" type="text" name="user" placeholder="Create username">div><div class="inputBox"><input id="upass" type="password" name="psw" placeholder="Password">div><div id="error_box">div><div class="inputBox"><input type="submit" value="Sign up" onclick="Regist()" class="btn btn-primary">div>form>div>div>div>section>
body>
html>
(css的具体代码实现放在文件夹里了)
(2)后端实现
/*
在点击注册按钮之后,会调用Regist函数,函数全部存储在login.js中。
会先检查用户名以及用户密码的长度,是否符合规范,如果不符合,则会进行提示,并且之间返回。
代码如下:
*/
var oUname = document.getElementById("uname");
var oUpass = document.getElementById("upass");
var oError = document.getElementById("error_box")
var isError = true;
console.log(oUname.value);if (oUpass.value.length > 10 || oUpass.value.length < 3) {oError.innerHTML = "密码请输入6-20位字符"isError = false;return;
}
if (oUname.value.length > 10 || oUname.value.length < 3) {oError.innerHTML = "用户名请输入3-10位字符";isError = false;return;
}/*
检查之后,将表单进行提交提交,代码如下:
*/
var params = '/process_regist';
$.post(params, {name: oUname.value,passwd: oUpass.value},function(text, status) {console.log(text);JSON.parse(text, function(k,v) {if(v == true) {window.alert("注册成功");}else{window.alert("已经注册过");}window.location.href = "login.html";// location.reload();})
})
相关js操作插入在html里:
<script src="/angular/angular.min.js">script>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css">
<script type="text/javascript" src="javascripts/users.js">script>
<script>var app = angular.module('login', []);app.controller('loginCtrl', function ($scope, $http, $timeout) {// 登录时,检查用户输入的账户密码是否与数据库中的一致$scope.check_pwd = function () {var data = JSON.stringify({username: $scope.username,password: $scope.password});$http.post("/users/login", data).then(function (res) {if(res.data.msg=='ok') {window.location.href='/news.html';}else{$scope.msg=res.data.msg;}},function (err) {$scope.msg = err.data;});};//增加注册用户$scope.doAdd = function () {// 检查用户注册时,输入的两次密码是否一致if($scope.add_password!==$scope.confirm_password){// $timeout(function () {// $scope.msg = '两次密码不一致!';// },100);$scope.msg = '两次密码不一致!';}else {var data = JSON.stringify({username: $scope.add_username,password: $scope.add_password});$http.post("/users/register", data).then(function (res) {if(res.data.msg=='成功注册!请登录') {$scope.msg=res.data.msg;$timeout(function () {window.location.href='index.html';},2000);} else {$scope.msg = res.data.msg;}}, function (err) {$scope.msg = err.data;});}};});
script>
之后会用users.js调用信息:
var express = require('express');
var router = express.Router();
var userDAO = require('../dao/userDAO');router.post('/login', function(req, res) {var username = req.body.username;var password = req.body.password;// var sess = req.session;userDAO.getByUsername(username, function (user) {if(user.length==0){res.json({msg:'用户不存在!请检查后输入'});}else {if(password===user[0].password){req.session['username'] = username;res.cookie('username', username);res.json({msg: 'ok'});// res.json({msg:'ok'});}else{res.json({msg:'用户名或密码错误!请检查后输入'});}}});
});/* add users */
router.post('/register', function (req, res) {var add_user = req.body;// 先检查用户是否存在userDAO.getByUsername(add_user.username, function (user) {if (user.length != 0) {// res.render('index', {msg:'用户不存在!'});res.json({msg: '用户已存在!'});}else {userDAO.add(add_user, function (success) {res.json({msg: '成功注册!请登录'});})}});});// 退出登录
router.get('/logout', function(req, res, next){// 备注:这里用的 session-file-store 在destroy 方法里,并没有销毁cookie// 所以客户端的 cookie 还是存在,导致的问题 --> 退出登陆后,服务端检测到cookie// 然后去查找对应的 session 文件,报错// session-file-store 本身的bugreq.session.destroy(function(err) {if(err){res.json('退出登录失败');return;}// req.session.loginUser = null;res.clearCookie('username');res.json({result:'/index.html'});});
});module.exports = router;
(注意要保存session信息,不然记录用户操作日志时,不知道是哪位用户进行的操作。示例的session信息只保存在启动网站内存中,所以网站重启后信息丢失;也可以新建数据库保存session信息。)【这里的session存在app.js里】

然后调用到userDAO.js:
var mysql = require('mysql');
var mysqlConf = require('../conf/mysqlConf');
var userSqlMap = require('./userSqlMap');
var pool = mysql.createPool(mysqlConf.mysql);
// 使用了连接池,重复使用数据库连接,而不必每执行一次CRUD操作就获取、释放一次数据库连接,从而提高了对数据库操作的性能。module.exports = {add: function (user, callback) {pool.query(userSqlMap.add, [user.username, user.password], function (error, result) {if (error) throw error;callback(result.affectedRows > 0);});},getByUsername: function (username, callback) {pool.query(userSqlMap.getByUsername, [username], function (error, result) {if (error) throw error;callback(result);});},};
2.用户注册、登录、查询等操作记入数据库中的日志


先在mysql里建两个表,分别用来存用户信息和日志信息,其中用户信息表中存储了用户名以及用户的密码,日志中存储了用户名、操作时间和具体操作。

3.爬虫数据查询结果列表支持分页和排序
这里用到了Bootstrap Table : https://www.bootstrap-table.com.cn/doc/getting-started/introduction/ 👈学习地址。
一定记得在代码最上面header里加上:
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous"><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous"><link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.css">
在代码最下面script下方加上:
<script src="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.js">script> <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous">script><script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous">script><script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous">script><script src="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.js">script>

(除了在查询的时候进行排序,还可以通过bootstrap table 提供了一个前端排序的方法,用户能够通过指定列旁边的小箭头,来对该列值进行升序或降序排序)
$('#record2').bootstrapTable({url:params,method:'GET',pagination:true,sidePagination:'client',pageSize:5,striped : true,sortable : true,sortOrder:"asc",showRefresh:true,search:true,showToggle: true,toolbar: '#toolbar',showColumns : true,columns:[{field :'url',title : 'url',sortable : true}, {field:'title',title:'title'}, {field:'source_name',title:'source_name',sortable : true},{field:'author',title:'author'},{field:'publish_date',title:'publish_date',sortable : true}]
})
整体代码:
<form><body background="2.jpg"style=" background-repeat:no-repeat ;
background-size:100% 100%;
background-attachment: fixed;"><br>

👆基础的表格结果展示
<script>$(document).ready(function() {$.get('/check', function(data) {console.log(data);if(data == false) {window.alert("用户未登录");window.location.href = "login.html";}});$("input:button").click(function() {var title = $("input:text").val();var sort = $('#sortid').val();console.log("title = " + title);console.log("sort = " + sort);var params = '/process_get?title=' + $("input:text").val() + '&sort=' + sort +'&order=asc';$(function(){console.log(params);$("#record2").empty();$('#record2').bootstrapTable({url:params,method:'GET',pagination:true,sidePagination:'client',pageSize:5,striped : true,sortable : true,sortOrder:"asc",showRefresh:true,search:true,showToggle: true,toolbar: '#toolbar',showColumns : true,columns:[{field :'url',title : 'url',sortable : true}, {field:'title',title:'title'}, {field:'source_name',title:'source_name',sortable : true},{field:'author',title:'author'},{field:'publish_time',title:'publish_time',sortable : true}]})});});});script>
👆通过js来以表格的形式显示数据
(通过GET方法访问给定的url,并且将返回的json文件,解析生成表格,注意下面field的值应该和返回的json文件对应属性的keyvalue相同。)
下面就是运行search.html后的结果:

查询总页面👆

这里可以选择要从哪里寻找查询关键字👆

这里可以分页查询👆

这里是鼠标移到相应新闻上会有加暗效果出现👆

这里可以选择查询表格里显示哪些表头👆

这里选择一页里包括多少条新闻行数👆

如果按下右上角那个Toggle,可以更改页面样式,由纵分割变成横分割👆

👆对 index.js 做的相应更改!!【这里一定记住要改,我一开始就是忘记了这块儿,一直显示不出来数据 /(ㄒoㄒ)/~~】
4.用Echarts或者D3实现3个以上的数据分析图表展示在网站中
这里我用ECharts来实现:下载地址 。
实现四个图的展示代码放在news.js里:
$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: pattern.exec(element["x"])[1], 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}
{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});// 显示 tooltipmyChart.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 stringcolor: function () {// Random colorreturn '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();};}});}
记住要在项目文件夹下cmd运行node bin/www,我第一次运行出了错误,用npm install xxx(你的报错)就解决了~


输入网址:http://127.0.0.1:3000/login.html,发现自己目前还没有账号,于是选择注册账号,转到注册页面:/sign.html。
注册成功之后会再次转回登陆界面,输入刚刚填写的名称和密码就可以进入查询主页了。
此时数据库会记录你的注册信息:


查询界面是search.html和news.html的结合
(因为news.html里引入了search.html):

(前面已经介绍过了search.html的实现效果)
这部分主要介绍转到news.html之后可以进行的操作,那就是选择四种不同的图表来表示出你的查询数据结果。

柱状图 :

饼状图 :

折线图 :

然后我就遇到了困难,那就是…nodejieba那个词云压根打不开…
这时助教正好在群里发了新的解决方法:
1、把wordcut.js文件替换成以下这个,这个换了一个分词器。
// 载入模块
// var nodejieba = require('nodejieba');
var Segmenter = require('node-analyzer');//正则表达式去掉一些无用的字符,与高频但无意义的词。
const regex = /[\t\s\r\n\d\w]|[\+\-\(\),\.。,!?《》@、【】"'::%-\/“”]/g;var wordcut = function(vals) {var segmenter = new Segmenter();var word_freq = {};vals.forEach(function (content){var newcontent = content["content"].replace(regex,'');if(newcontent.length !== 0){// console.log();var words = segmenter.analyze(newcontent).split(' ');// var words = nodejieba.cut(newcontent);words.forEach(function (word){word = word.toString();word_freq[word] = (word_freq[word] +1 ) || 1;});};});return word_freq;
};
exports.wordcut = wordcut;
2、命令行输入npm install node-analyzer -save

3、freqchange.js中删掉 第2行 var nodejieba = require(‘nodejieba’);

再运行了一次就成功啦!
(顺便可以检验网站已注册账号的登录和查询情况)
词云 :

5.实现一个管理端界面,可以查看(查看用户的操作记录)和管理(停用启用)注册用户。
直接在app.js中,引入var logger = require('morgan’);
借助中间件保存的信息
// module.exports = app;
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var logger = require('morgan');
var logDAO = require('./dao/logDAO.js');
// var fs = require('fs');//加了文件操作的模块
// var accessLogStream = fs.createWriteStream(path.join(__dirname, 'access.log'), { flag: 'a' });//创建一个写文件流,并且保存在当前文件夹的access.log文件中// var indexRouter = require('./routes/users');
var usersRouter = require('./routes/users');
var newsRouter = require('./routes/news');var app = express();//设置session
app.use(session({secret: 'sessiontest',//与cookieParser中的一致resave: true,saveUninitialized: false, // 是否保存未初始化的会话cookie : {maxAge : 1000 * 60 * 60, // 设置 session 的有效时间,单位毫秒},
}));// view engine setup
// app.set('views', path.join(__dirname, 'views'));
// app.set('view engine', 'ejs');let method = '';
app.use(logger(function (tokens, req, res) {console.log('打印的日志信息:');var request_time = new Date();var request_method = tokens.method(req, res);var request_url = tokens.url(req, res);var status = tokens.status(req, res);var remote_addr = tokens['remote-addr'](req, res);if(req.session){var username = req.session['username']||'notlogin';}else {var username = 'notlogin';}// 直接将用户操作记入mysql中if(username!='notlogin'){logDAO.userlog([username,request_time,request_method,request_url,status,remote_addr], function (success) {console.log('成功保存!');})}console.log('请求时间 = ', request_time);console.log('请求方式 = ', request_method);console.log('请求链接 = ', request_url);console.log('请求状态 = ', status);console.log('请求长度 = ', tokens.res(req, res, 'content-length'),);console.log('响应时间 = ', tokens['response-time'](req, res) + 'ms');console.log('远程地址 = ', remote_addr);console.log('远程用户 = ', tokens['remote-user'](req, res));console.log('http版本 = ', tokens['http-version'](req, res));console.log('浏览器信息 = ', tokens['user-agent'](req, res));console.log('用户 = ', username);console.log(' ===============',method);}, ));app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/angular', express.static(path.join(__dirname , '/node_modules/angular')));// app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/news', newsRouter);// catch 404 and forward to error handler
app.use(function(req, res, next) {next(createError(404));
});// error handler
app.use(function(err, req, res, next) {// set locals, only providing error in developmentres.locals.message = err.message;res.locals.error = req.app.get('env') === 'development' ? err : {};// render the error pageres.status(err.status || 500);// res.render('error');
});module.exports = app;
查看用户注册信息:

查看用户操作信息:


二、拓展要求
(因为时间和能力有限,这里先说一下自己的想法好了,日后有机会再回来完善⁽⁽꜀(:3꜂ ꜆)꜄⁾⁾)
1.实现对爬虫数据中文分词的查询
因为不太理解这项任务的具体意思,所以去询问了一下老师,老师说就是对爬虫爬取的文本做中文分词后再做查询,有一些语义不同的查询结果就可以抛弃了,比如“南京市长江大桥”分词结果是“南京市/长江/大桥”,这种情况下查询“市长”就查不到了,而不做分词的时候按照like查询“市长”是可以查到这个语料的。
2.实现查询结果按照主题词打分的排序
估计是按照keyword进行一些相关操作(੭ ᐕ)੭
3.用Elastic Search+Kibana展示爬虫的数据结果
这里可以参考这个博客:干货:Kibana 可视化ElasticSearch数据展示分析
总结
感谢:
https://blog.csdn.net/yc_hong/article/details/105886576
这位大佬的博客!和老师、助教们的帮助与指导,至此web编程课圆满结束!(撒花★,°:.☆( ̄▽ ̄)/$:.°★ 。)
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
