web final project 2021

作业要求

基本要求:
1、用户可注册登录网站,非注册用户不可登录查看数据
2、用户注册、登录、查询等操作记入数据库中的日志
3、实现查询词支持布尔表达式 (比如“新冠 AND 肺炎”或者“新冠 OR 肺炎”)
4、爬虫数据查询结果列表支持分页和排序
5、用Echarts或者D3实现3个以上的数据分析图表展示在网站中

扩展要求(非必须):
1、实现对爬虫数据中文分词的查询
2、实现查询结果按照主题词打分的排序
3、用Elastic Search+Kibana展示爬虫的结果

基本要求完成过程

用户登陆与记录用户操作的日志(任务一与任务二)

首先,我们需要为用户在数据库中创建用户的信息表,和用户的操作表。

-创建用户信息数据表
CREATE TABLE `crawl`.`user` (  
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,  `username` VARCHAR(45) NOT NULL,  
`password` VARCHAR(45) NOT NULL,
  `registertime` datetime DEFAULT CURRENT_TIMESTAMP,  PRIMARY KEY (`id`),  
  UNIQUE KEY `username_UNIQUE` (`username`))
  ENGINE=InnoDB DEFAULT CHARSET=utf8;
--记录用户的登陆,查询(具体查询语句)操作CREATE TABLE `crawl`.`user_action` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,  
  `username` VARCHAR(45) NOT NULL,  
  `request_time` VARCHAR(45) NOT NULL,  
  `request_method` VARCHAR(20) NOT NULL,  
  `request_url` VARCHAR(300) NOT NULL,  
  `status` int(4),  
  `remote_addr` VARCHAR(100) NOT NULL,   
   PRIMARY KEY (`id`))ENGINE=InnoDB 
   DEFAULT CHARSET=utf8;

接下来,我们需要配置mysql,使得nodejs能够连接到我们的数据库。
我们在mysqlConf.js文件下加入以下内容。

module.exports = {
    mysql: {
        host: 'localhost',
        user: 'root',
        password: 'root',
        database:'crawl',
        // 最大连接数,默认为10
        connectionLimit: 10
    }
};

这样,nodejs就可以与本地mysql连接了。
接下来,我们来实现登陆页面。
登陆页面中应该有登陆和注册两种选项,并且用户能够对用户的输入有所反馈。
我们在public/index.html和routes/users.js下写入登陆页面的内容。它的内在逻辑是:
1.当用户选择登录时,检查用户输入的账户密码是否与数据库中的一致,如果一致,则进入应用页面,否则提示错误信息。
2. 当用户选择注册时,
a. 检查输入的两次密码是否一致,如果不一致则提示用户输入密码不一致;如果一致则提示用户注册成功并且将用户的注册信息加入数据库。
b. 如果用户的密码或者用户名为空,则提示用户名或为不能为空;.
c. 如果用户名已经存在则提示用户用户名已经存在。
最后登陆页面结果如图所示。
在这里插入图片描述
登陆失败情况
在这里插入图片描述
接下来,我们看看数据库的状态
在这里插入图片描述

显示一个注册成功的用户。
在这里插入图片描述
这里是用户lm123的部分操作记录。
所以任务一与任务二我们都完成了。

查询词的实现以及分页结果的展示。(任务三和四)

当用户进入应用之后,news.html文件会向用户展示界面, 如下图所示。
在这里插入图片描述
用户可以选择检索选项进行布尔查询。
我们将代码实现写在public/search.html文件里面。查询界面如图所示。
在这里插入图片描述
用户分标题关键字和内容关键字进行查询,也可以自行选择AND或者OR进行查询。
当用户输入查询字段之后,public/javascripts/news.js文件代码会处理该字段。这个文件的实现逻辑是:
1.检查用户传的参数是否有问题。检查用户是否输入了一些无意义字符。
2. 如果用户一个查询词都不输入,那么默认就是查找全部数据。
接下来,根据用户的输入,我们利用sql在数据库中查询,这里我们主要用到了newsDAO.search函数,它位于dao/newsDAO.js中。它根据用户的输入内容在mysql对新闻进行查询并且返回查询结果。
得到查询的结果之后进行分页展示。分页展示的实现逻辑是
3. 首先我们定义每页显示的数据量,这样一个页面所显示的数据量就不会大于这个数。
4. 接下来打印当前选中页。最多显示分页数5,如果大于5页则开始分页转换。
5. 设置当前选中页样式并且设置上下页的转换。
这样,我们的分页操作就完成了。
查询如图所示。
在这里插入图片描述
查询结果如下图所示。
在这里插入图片描述
至此,我们已经完成了作业三分次查询和作业四分页展示。

用Echarts或者D3实现3个以上的数据分析图表展示在网站中(任务五)

四个图标的实现在public/javascript/news.js中实现。
柱状图展示新闻发布数随时间的变化。结果如图所示
在这里插入图片描述
饼状图显示作者发不新闻数量。
在这里插入图片描述
折线图显示某一个词的热度变化。

词云显示所有新闻的jieba词云

至此,我们五个任务都已经完成了。下面是扩展任务。

扩展任务

1. 实现对爬虫数据中文分词的查询

下面的分词代码写在javascript/split.js 里面
(1)分词工具下载使用
我们可以利用现有的分词工具包对用户提交的文本进行分词。
首先,我们需要安装粉刺工具,这里我们选用node-analyzer。
安装:npm install node-analyzer -save
(2)去除停用此
在每个查询语句之中,停用词都是没有用的,所以我们需要去除停用词。
下面的代码是读取停用词的代码,它读取StopWords.txt文件里面的停用词,将其存入stop_words

var fs = require("fs");
var stop_words = new Set();
fs.readFile('./StopWords.txt','utf-8', function(err, data) { 
    if(err){
        console.log(err);
    }else { 
        var all_words= data.split('\n'); 
        for(var i = 0; i < all_words.length; i++) { 
            stop_words.add(all_words[i]);
        } 
    } 
})

接下来,我们在数据库中创建新的储存表来储存分词查询的结果

CREATE TABLE Splitwords ( 
id_fetches int,
word varchar(50) DEFAULT NULL
);

接下来我们进行分词处理。首先对爬虫内容,我们先用正则表达式去掉一些无用的字符与高频但无意义的词。 进行分词后,遍历所有词语判断是否存在于停用词表当中,如果是停用词,则去掉它,最后将有效词条存储到数据库的 Splitwords 表中。

const regex = /[\t\s\r\n\d\w]|[\+\-\(\),\.。,!?《》@、【】"'::%-\/“”]/g;
var fetchSql = "select id_fetches,content from fetches;";
newsDAO.query_noparam(fetchSql, function (err, result, fields) {
    result.forEach(function (item){
        var segmenter = new Segmenter();
        var newcontent = item["content"].replace(regex,'');
        if(newcontent.length !== 0){
            var words = segmenter.analyze(newcontent).split(' ');
            var id_fetch = item["id_fetches"];
            words.forEach(function(word){
                if(!stop_words.has(word)&&word.length>1){
                    var insert_word_Sql = 'INSERT INTO Splitwords2(id_fetches,word) VALUES(?,?);';
                    var insert_word_Params = [id_fetch, word];
                    newsDAO.query(insert_word_Sql, insert_word_Params, function(err){ 
                        if(err)console.log(err);
                    });
                }                    
            });
        }
    });
});

最后是分词查询。
对于已经存储好了分词结果,前端的搭建以及前后端的连接与实现布尔查询操作类似,这里主要介绍网站的后端查询:

  1. 对读入进来的关键词,同样先进行正则表达式的处理
  2. 若用户提交的字符串没有超过三个中文字,直接到数据库索引词汇;否则先进行分词操作,再对每个词条进行判断,这里把在停用词表中出现的以及长度小于等于一的词条当作无效词,然后在 Splitwords 表中获取每个分词的id字段,最后取 id 字段交集查询 fetches 表中完整的新闻爬取信息。
		var segmenter = new Segmenter();
        var sql ='select * from fetches ';
         
        if(searchparam["t"]!="undefined"){
            if(searchparam["t"].length<=3){
                sql+=(`where id_fetches in (select id_fetches from Splitwords where word like '${searchparam["t"]}')`);
            }else{
                var newcontent = searchparam["t"].replace(regex,'');
                var words = segmenter.analyze(newcontent).split(' ');
                var n=1;
                //默认第一个分词词语是有效词,像“的”这样的无效词一般出现在词语之间
                sql+=(`where id_fetches in (select id_fetches from(select id_fetches from Splitwords where word like '${words[0]}'`);
                for(var i=1;i<words.length;i++){
                    if(!stop_words.has(words[i])&&words[i].length>1){
                        sql+=(` UNION ALL select id_fetches from Splitwords where word like '${words[i]}'`);
                        n++;
                    }                   
                }
                sql+=`)a GROUP BY id_fetches HAVING COUNT(*) = ${n})`; 
            }
             
        }
        
        if(searchparam['stime']!="undefined"){
            if(searchparam['stime']=="1"){
                sql+='ORDER BY publish_date ASC ';
            }else {
                sql+='ORDER BY publish_date DESC ';
            }
        }
        sql+=';';

这样子我们就完成了分词查询了!
部分的分词结果如下图所示
在这里插入图片描述

2. 实现查询结果按照主题词打分的排序

首先,我们创建一个新表WeightSearch用于存储每个词条计算权重。

CREATE TABLE WeightSearch ( 
id_fetches int, 
word varchar(50) DEFAULT NULL, 
weight float 
);

我们采用Elastic Search的相关性文档打分机制:TF-IDF打分。它使用了被搜索词条的频率和它有多常见来影响得分,一个词条在某篇文档中出现的次数越多,该文档就越相关;一个词条如果在不同的文档中出现的次数越多,它就越不相关。所以,一个词条在一篇文章中出现次数越多, 同时在所有文档中出现次数越少, 越能够代表该文章,越能与其它文章区分开来。
计算公式:TF-IDF=TFxIDF,TFIDF值越大表示该特征词对这个文本的重要性越大。
打分的实现方式(所有代码写在javascripts/rank.js里面):

  1. 获取所有的数据遍历splitwords
select id_fetches,word from Splitwords;
  1. 计算对应id文档中word出现次数:
select count(*) as num from Splitwords where word='${word}' and id_fetches=${id};
  1. 计算对应id文档中词条总数目
select count(*) as num from Splitwords where id_fetches=${id};
  1. 文档总数(获取一次即可)
select count(distinct id_fetches) as num from Splitwords;
  1. 计算包含该word的文档数
select count(distinct id_fetches) as num from Splitwords where word='${word}';
  1. 最后将结果插入数据表中
INSERT INTO WeightSearch VALUES (id,word,weight)

所有的打分代码如下所示

var newsDAO = require('../../dao/newsDAO');
var fetchGetSql = 'select id_fetches,word from Splitwords;';
newsDAO.query_noparam(fetchGetSql, function(err, result){
    var tn;
    var getTotalNum = 'select count(distinct id_fetches) as num from Splitwords;';
    newsDAO.query_noparam(getTotalNum, function(err,res){
        tn = res[0].num;//文档总数
        result.forEach(function(item){
            var id=item.id_fetches;
            var word=item.word; 
            var tf1;//该文档中word出现的次数
            var gettf1=`select count(*) as num from Splitwords where word='${word}' and id_fetches=${id};`
            newsDAO.query_noparam(gettf1, function(err,res){
                tf1=res[0].num;    

                var tf2;//该文档中的词条数目
                var gettf2=`select count(*) as num from Splitwords where id_fetches=${id};`
                newsDAO.query_noparam(gettf2, function(err,res){
                    tf2=res[0].num;  
                    var tf=tf1/tf2;
                    
                    var idf2;//包含词条t的文档数
                    var getidf2=`select count(distinct id_fetches) as num from Splitwords where word='${word}';`
                    newsDAO.query_noparam(getidf2, function(err,res){
                        idf2=res[0].num;   
                        var weight=tf*Math.log(tn/idf2);
                        if(weight!=undefined){
                            //写入带权重的数据表
                            console.log(weight);
                            var insert_word_Sql = 'INSERT INTO WeightSearch(id_fetches,word,weight) VALUES(?,?,?);';
                            var insert_word_Params = [id,word,weight];
                            newsDAO.query(insert_word_Sql, insert_word_Params, function(err){ 
                                if(err)console.log(err);
                            });
                        }
                    });
                });
            });
        });    
    });    
});

部分的权重存储结果如下图所示在这里插入图片描述
最后的查询结果展示
在这里插入图片描述
在这里插入图片描述
这样我们的扩展任务二就完成了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值