新闻爬虫与爬取结果的查询网站的实现
项目要求
一、爬虫部分 30分
1、完成最少一个目标网站(网站主题不限,不允许直接使用示例中的中国新闻网)的分析和爬虫设计。
2、爬取不少于100条数据(每条数据包括最少3个字段,标题、内容和时间),并存储在数据库中。
3、需提交源代码,完成多个网站的爬虫酌情加分。
二、搜索网站部分 30分
1、完成对数据库中爬取数据内容或标题的搜索功能,搜索结果以表格形式展示在前端页面中。
2、完成对搜索内容的时间热度分析,比如搜索“新冠”,可以展示爬取数据内容中每一天包含“新冠”的条数,具体展示形式不限,可以用文字或表格展示,也可以用图表展示。
3、需提交源代码,网站页面设计简洁美观酌情加分。
三、报告 40分
1、详细描述对目标网站的分析过程。
2、详细描述爬虫整体结构,使用的工具包,以及数据库设计。
3、详细描述搜索网站前后端的设计。
4、图文展示实现的效果。
0. 准备工作
node.js
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。Node.js 的包管理器 npm,是全球最大的开源库生态系统。
在其官网https://nodejs.org/en/上下载node-v14.16.0-x64.msi,一路点击next即可完成,在安装完成后命令行里可以查询到版本号。Node.js可以用于实现网络爬虫和查询网站后端。
MySQL
MySQL之前已经完成了下载安装,且安装了对应的可视化软件Navicat用于实时地查询数据库中的爬取结果,本次实验使用的数据库为MySQL。
vscode
Visual Studio Code是一个轻量且强大的跨平台开源代码编辑器(IDE),支持Windows,OS X和Linux。内置JavaScript、TypeScript和Node.js支持,而且拥有丰富的插件生态系统,可通过安装插件来支持C++、C#、Python、PHP等其他语言。本次实验使用vscode对node.js进行调试。
Cheerio 和 Request
cheerio是nodejs的抓取页面模块,为服务器特别定制的,快速、灵活、实施的jQuery核心实现。适合各种Web爬虫程序,具体实现方式如下:
// 引入cheerio模块
const cheerio = require('cheerio')
// 加载HTML字符串
const $ = cheerio.load('<h2 class="title">Hello world</h2>')
// 设置Text
$('h2.title').text('Hello there!')
// 添加class
$('h2').addClass('welcome')
// 获取完整HTML
$.html()
//=> <html><head></head><body>
// <h2 class="title welcome">Hello there!</h2></body></html>
Request也是一个Node.js的模块库,是服务端发起请求的工具包,可以完成http请求,默认为get,实现方法如下:
var request = require('request');
request('您的请求url', function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body) // 请求成功的处理逻辑
}
});
1. 数据库
本实验的数据库采用的是mysql。
该实验需要在mysql下创建数据库crawl,并创建表fetches,用于存储爬取的代码信息,该表具体的在mysql中的构造流程如下:
CREATE TABLE `fetches` (
`id_fetches` int(11) NOT NULL AUTO_INCREMENT,
`url` varchar(200) DEFAULT NULL,
`source_name` varchar(200) DEFAULT NULL,
`source_encoding` varchar(45) DEFAULT NULL,
`title` varchar(200) DEFAULT NULL,
`keywords` varchar(200) DEFAULT NULL,
`author` varchar(200) DEFAULT NULL,
`publish_date` date DEFAULT NULL,
`crawltime` datetime DEFAULT NULL,
`content` longtext,
`createtime` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id_fetches`),
UNIQUE KEY `id_fetches_UNIQUE` (`id_fetches`),
UNIQUE KEY `url_UNIQUE` (`url`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在爬虫部分中,爬虫代码的js文件会通过引用mysql.js来访问数据库,从而实现各种的数据库的操作。所以在mysql.js中,除了连接到本地数据库外(配置sql信息),还需要定义query函数,将sql指令传给数据库来执行。
在node.js中,采用的连接池的方法连接数据库,从而提升多并发的效率。
var mysql = require("mysql");
var pool = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: '123456',
database: 'crawl'
});
对于“增删改查”的数据库操作,本实验主要是对数据库进行查询和插入的操作,并没有用更新和删除操作。所以在这里主要写了两个query函数,用于操作有参数和无参数的情况。
var query = function(sql, sqlparam, callback) {
pool.getConnection(function(err, conn) {
if (err) {
callback(err, null, null);
} else {
conn.query(sql, sqlparam, function(qerr, vals, fields) {
conn.release(); //释放连接
callback(qerr, vals, fields); //事件驱动回调
});
}
});
};
var query_noparam = function(sql, callback) {
pool.getConnection(function(err, conn) {
if (err) {
callback(err, null, null);
} else {
conn.query(sql, function(qerr, vals, fields) {
conn.release(); //释放连接
callback(qerr, vals, fields); //事件驱动回调
});
}
});
};
exports.query = query;
exports.query_noparam = query_noparam;
2. 爬虫部分
结合爬虫部分的代码,可以总结爬虫过程的步骤如下:
1、读取种子页面
2、查询在种子页面里的所有二级页面对应的新闻链接
3、根据这些二级页面(新闻)爬取所有相关的内容
4、根据新闻页面内容,解析出结构化数据
5、将相应的数据存储到mysql数据库中
爬虫部分的开头需要确定对应爬取的网站作为全局常量,即爬取网站源以及对应的url。
在实际的爬取过程中,在老师给定了不能爬取已给的中国新闻网的例子的基础上,我选取了新浪新闻、网易新闻、搜狐网新闻、看看新闻四种网站作为新闻来源。
四种新闻来源整体类似,主要是在对于种子页面有些网页可以直接获取所有url,有些网页需要筛选出符合正则表达式的url。二级页面的处理上有稍许不同,不同的网站新闻发布时间、新闻标题、来源等信息的组织结构略有不同。当进入到在在某新闻网站的二级页面上时,需要通过对应的网站的各类属性去匹配到具体的某项html元素,定义对应数据的读取方式以获得数据。对于其中的部分信息,还需要做正则化处理。
这里做一下对目标网站的分析过程,这里以网易新闻为详细例子,其他三个网站简要阐述:
网易新闻
var source_name = "网易新闻";
var domain = 'https://news.163.com/';
var myEncoding = "utf-8";
var seedURL = 'https://news.163.com/';
观察某一个网易新闻下的二级页面可以看到对应的url格式,从而可以得到根据正则表达式解析有var url_reg = //(\d{2})/(\d{4})/(\d{2})/([A-Z0-9]{16}).html/
接着通过按F12键观察整个新浪新闻的网页结构,对相关元素爬取的代码进行对应的修改:
这里通过对这一片代码使用全局搜索(Crtl+F),找到对应要素的位置,进行解读该要素对应的格式,并将其写入到代码中,从而实现后面的爬取功能。
例如找关键词,全局搜索keywords。找到如下图所示的一行代码。meta是html语言head区的一个辅助性标签,告诉浏览器网页所识别的文件类型及语言类型。meta标签有许多参数,使用不同的参数就可以使主页实现不同的功能,例如用于鉴别作者,设定页面格式,标注内容提要和关键字,以及刷新页面等等。
这里就可以说明需要找到对应的meta标签下name项为keywords的那一行所对应的content内容,所以对应的语句是var keywords_format = " $(‘meta[name=“keywords”]’).eq(0).attr(“content”)";。
同理,可以先找到每一个需要存储在数据库中的属性对应的实例所在的位置,然后将其格式写在对应的爬虫代码开头,用于实现最终结果。
title格式,title标签下的对应内容:
日期格式,html#ne_wrap标签下的data-publishtime属性:
作者格式,meta标签下name值为author对应的content内容:
内容格式,#endText标签下的内容:
描述格式,meta标签下name值为description对应的content内容:
所以对应的代码中的语句为:
var seedURL_format = "$('a')";
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var date_format = "$('html#ne_wrap').attr(\"data\-publishtime\")";//
var author_format = " $('meta[name=\"author\"]').eq(0).attr(\"content\")";
var content_format = "$('#endText').text()";
var desc_format = " $('meta[name=\"description\"]').eq(0).attr(\"content\")";
var url_reg = /\/(\d{2})\/(\d{4})\/(\d{2})\/([A-Z0-9]{16}).html/;
var regExp = /((\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2})|(\d{4}年\d{1,2}月\d{1,2}日)/
新浪新闻
var source_name = "新浪新闻";
var domain = 'http://news.sina.com.cn/';
var myEncoding = "utf-8";
var seedURL = 'http://news.sina.com.cn/';
观察某一个新浪新闻下的二级页面可以看到对应的url格式,从而可以得到根据正则表达式解析有url_reg = ///news.sina.com.cn/[a-z]/.*.shtml/
接着观察整个新浪新闻的网页结构,对相关元素爬取的代码进行对应的修改:
可以看到,对于新浪新闻网来说,每一个属性都放在对应的class中,可以在这些属性下找到我们需要爬取的新闻属性,例如title/author等等。这里以content属性为例,如下图所示,在html对应为在div class = 'article’下,所以定义对应数据的读取方式为var content_format = “$(‘div[class=“article”]’).text()”。
同理,可以将其他属性找到对应的class。此外,读取摘要的对应语句为var desc_format = " $(‘meta[name=“description”]’).eq(0).attr(“content”)"。
var seedURL_format = "$('div>ul>li>a')";
var keywords_format = " $('div[class=\"keywords\"]>a').text()";
var title_format = "$('h1[class=\"main-title\"]').text()";
var date_format = "$('span[class=\"date\"]').text()";//
var author_format = "$('a[class=\"source\"]').text()";
var content_format = "$('div[class=\"article\"]').text()";
var desc_format = " $('meta[property=\"og:description\"]').eq(0).attr(\"content\")";
var source_format = "$('a[class=\"source\"]').text()";
var url_reg = /\/\/news.sina.com.cn\/[a-z]\/.*\.shtml/;
var regExp = /((\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2})|(\d{4}年\d{1,2}月\d{1,2}日)/
搜狐网新闻
搜狐网与代码实例给的中国新闻网结构十分类似,这里不再赘述了,直接给出代码。
var source_name = "搜狐网新闻";
var domain = 'http://news.sohu.com/';
var myEncoding = "utf-8";
var seedURL = 'https://business.sohu.com/';
var seedURL_format = "$('a')";
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = " $('meta[property=\"og:title\"]').eq(0).attr(\"content\")";
var date_format = " $('meta[property=\"og:release_date\"]').eq(0).attr(\"content\")"|" $('meta[itemprop=\"datePublished\"]').eq(0).attr(\"content\")";
var author_format = " $('meta[name=\"mediaid\"]').eq(0).attr(\"content\")";
var content_format = "$('.article').text()";
var desc_format = " $('meta[property=\"og:description\"]').eq(0).attr(\"content\")"|" $('meta[name=\"description\"]').eq(0).attr(\"content\")";
var source_format = " $('meta[property=\"og:url\"]').eq(0).attr(\"content\")";
var url_reg = /sohu.com\/a\/.{20,}/;
var regExp = /(\d{4})-(\d{2})-(\d{2})/
看看新闻
整体结构最为简单的一个,基本上都是直接.text()就可以了,部分需要用到meta标签,整体比较容易获取,方法与之前类似。但是看看有很多网站的keywords是不存在的,所以最终爬取的结果中会存在很多keywords不存在的数据。
var source_name = "看看新闻";
var domain = 'http://www.kankanews.com/';
var myEncoding = "utf-8";
var seedURL = 'http://www.kankanews.com/';
var seedURL_format = "$('a')";
var keywords_format = " $('meta[name=\"Keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var date_format = "$('.time').text()";
var author_format = "$('.resource').text()";
var content_format = "$('.textBody').text()";
var desc_format = " $('meta[name=\"Description\"]').eq(0).attr(\"content\")";
var source_format = "$('.resource').text()";
var url_reg = /a\/(\d{4})-(\d{2})-(\d{2})\/(\d{10}).shtml/;
var regExp = /((\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2})|(\d{4}年\d{1,2}月\d{1,2}日)/
代码主体部分
为了防止网站屏蔽我们的爬虫,所以要为爬虫写一个头,在爬取网页时来实现假装设备访问。
var headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36'
}
编写request函数,用于模拟抓捕网页请求。调用request函数能够访问指定的url并且能够设置回调函数来处理爬取得到的html页面。
function request(url, callback) {
var options = {
url: url,
encoding: null,
//proxy: 'http://x.x.x.x:8080',
headers: headers,
timeout: 10000 //
}
myRequest(options, callback)
};
Node.js中的定时任务node-schedule可以实现对于网站的定时爬取,这里选定每天1、5、9、13、17、21的整点执行相应的爬取网站的操作。在实际操作中,由于电脑时不时要开关机,也很难一直挂着几天去运行,我一般是选择直接调用seedget函数来启动爬虫程序的。
var rule = new schedule.RecurrenceRule();
var times = [1,5,9,13,17,21]; //每隔四个小时执行一次
var times2 = 00; //定义在整点执行
rule.hour = times;
rule.minute = times2;
schedule.scheduleJob(rule, function() {
seedget();
});
定义seedget函数,用于从种子页面获取相应的新闻所在的二级页面的url。这里使用cheerio来解析html,遍历种子页面里所有的a链接,在检验是否符合新闻url的正则表达式后读取新闻页面。
function seedget() {
request(seedURL, function (err, res, body) { //读取种子页面
try {
// 用iconv转换编码
var html = myIconv.decode(body, 'GBK');
// console.log(html);
//准备用cheerio解析html
var $ = myCheerio.load(html, { decodeEntities: true });
} catch (e) { console.log('读种子页面并转码出错:' + e) };
var seedurl_news;
try {
seedurl_news = eval(seedURL_format);
} catch (e) { console.log('url列表所处的html块识别出错:' + e) };
seedurl_news.each(function (i, e) { //遍历种子页面里所有的a链接
var myURL = "";
try {
//得到具体新闻url
var href = "";
href = $(e).attr("href");
if (href == undefined) return;
if (href.toLowerCase().indexOf('https://') >= 0 || href.toLowerCase().indexOf('http://') >= 0) myURL = href; //http://开头的
else if (href.startsWith('//')) myURL = 'http:' + href; 开头的
else myURL = seedURL.substr(0, seedURL.lastIndexOf('/') + 1) + href; //其他
} catch (e) { console.log('识别种子页面中的新闻链接出错:' + e) }
if (!url_reg.test(myURL)) return; //检验是否符合新闻url的正则表达式
console.log(myURL);
var fetch_url_Sql = 'select url from fetches where url=?';
var fetch_url_Sql_Params = [myURL];
mysql.query(fetch_url_Sql, fetch_url_Sql_Params, function (qerr, vals, fields) {
// console.log(vals)
if (!vals) {
console.log('vals=NULL')
}
else if (vals.length > 0) {
console.log('URL duplicate!')
} else newsGet(myURL); //读取新闻页面
});
});
});
};
定义newsGet函数,对于传入的某种子页面网站下的二级新闻界面,解析得到具体的新闻中的title, publish_date, source_name, keywords, content等信息,并最后将爬取的信息存储为JSON文件。
用cheerio解析html_news,转码成功后动态执行format字符串,构建json对象准备写入数据库,最后执行sql语句INSERT INTO fetches(url, source_name, source_encoding, title,’ + 'keywords, author, publish_date, crawltime, content) VALUES ……
function newsGet(myURL) { //读取新闻页面
request(myURL, function (err, res, body) { //读取新闻页面
try {
// res.on('html_news',function(res){
// var html_news = myIconv.decode(String(body), 'GBK'); //用iconv转换编码
// })
var html_news = myIconv.decode(new Buffer(body), 'GBK'); //用iconv转换编码
// console.log(html_news);
//准备用cheerio解析html_news
var $ = myCheerio.load(html_news, { decodeEntities: true });
myhtml = html_news;
} catch (e) {
console.log('读新闻页面并转码出错:' + e);
return;
};
console.log("转码读取成功:" + myURL);
//动态执行format字符串,构建json对象准备写入文件或数据库
var fetch = {};
fetch.title = "";
fetch.content = "";
fetch.publish_date = (new Date()).toFormat("YYYY-MM-DD");
//fetch.html = myhtml;
fetch.url = myURL;
fetch.source_name = source_name;
fetch.source_encoding = myEncoding; //编码
fetch.crawltime = new Date();
if (keywords_format == "") fetch.keywords = source_name; // eval(keywords_format); //没有关键词就用sourcename
else fetch.keywords = eval(keywords_format);
if (title_format == "") fetch.title = ""
else fetch.title = eval(title_format); //标题
console.log(date_format);
if (date_format != "") fetch.publish_date = eval(date_format); //刊登日期
console.log('date: ' + fetch.publish_date);
if(fetch.keywords!=undefined && fetch.publish_date!=""){
fetch.publish_date = regExp.exec(fetch.publish_date)[0];
fetch.publish_date = fetch.publish_date.replace('年', '-')
fetch.publish_date = fetch.publish_date.replace('月', '-')
fetch.publish_date = fetch.publish_date.replace('日', '')
fetch.publish_date = new Date(fetch.publish_date).toFormat("YYYY-MM-DD");
}
if (author_format == "") fetch.author = source_name; //eval(author_format); //作者
else fetch.author = eval(author_format);
if (content_format == "") fetch.content = "";
else fetch.content = eval(content_format).replace("\r\n" + fetch.author, ""); //内容
if (source_format == "") fetch.source = fetch.source_name;
else fetch.source = eval(source_format).replace("\r\n", ""); //来源
if (desc_format == "") fetch.desc = fetch.title;
else fetch.desc = eval(desc_format); //摘要
var fetchAddSql = 'INSERT INTO fetches(url,source_name,source_encoding,title,' +
'keywords,author,publish_date,crawltime,content) VALUES(?,?,?,?,?,?,?,?,?)';
var fetchAddSql_Params = [fetch.url, fetch.source_name, fetch.source_encoding,
fetch.title, fetch.keywords, fetch.author, fetch.publish_date,
fetch.crawltime.toFormat("YYYY-MM-DD HH24:MI:SS"), fetch.content
];
//执行sql,数据库中fetch表里的url属性是unique的,不会把重复的url内容写入数据库
mysql.query(fetchAddSql, fetchAddSql_Params, function(qerr, vals, fields) {
if (qerr) {
console.log(qerr);
}
}); //mysql写入
});
}
爬取的内容包括网站的链接,来源,关键词,作者,出版日期,创建日期,爬取时间,爬取内容等信息。其存储信息在mysql中,最终爬取的部分结果用图形化界面Navicat中显示如下图所示:
3. 网页端
根据老师给的代码,可以基本实现查询功能,但存在诸多问题。首先是目前的前端过于简单了,需要美化;查询只有一个标题查询,没有支持多种类别的查询;最终查询结果是一个JSON格式,没有转化为更具可读性的形式,结果没有以表格形式展示;查询结果没有基于时间热度的分析等等。
为了完善要求,对网页做了多次优化,如下:
网页-第一次优化:前端美化、多项查询、结果不以JSON格式输出
在对于网络前端制作时,改变了查询格式与查询框,完善了查询按钮,并添加了背景,更改了字体,增加了用户提示语,使得整体显示更加美观。对应的html和css代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link href="http://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<style>
.test {
border: 3px solid black;
width: 900px;
height: 300px;
margin: auto;
margin-top: 5%;
font-size: 22px;
color: #143946;
padding-top: 15px;
border-radius: 10px;
background: white;
}
.demo {
height: 22px;
width: 400px;
}
.tick {
background: url(http://bpic.588ku.com/back_pic/00/11/56/305638b3b70454b.jpg) no-repeat center center fixed;
-webkit-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
.warp-mask {
width: 100%;
height: 100%;
background: rgba(0, 0, 0, .4);
position: absolute;
}
.bar1 {background: #FFFFFF;}
.bar1 input {
border: 2px solid #e9e9e9;
border-radius: 5px;
background: #FFFFFF;
color: #9E9C9C;
}
.bar1 button {
top: 5;
right: 0;
background: #3dd5e0;
border-radius: 0 5px 5px 0;
}
.bar1 button:before {
content: "\f002";
font-family: FontAwesome;
font-size: 16px;
color: #F9F0DA;
}
* {
box-sizing: border-box;
}
form {
position: relative;
width: 500px;
margin: 0 auto;
}
input, button {
border: none;
outline: none;
}
input {
width: 80%;
height: 42px;
padding-left: 13px;
}
button {
height: 42px;
width: 42px;
cursor: pointer;
position: absolute;
}
img{
height: 250px;
width: 250px;
}
.form-submit {
background-color: #4CAF50; /* Green */
border: none;
color: white;
padding: 10px 16px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
</style>
</head>
<body class="tick">
<div class="warp-mask">
<div class="test" align="center">
请输入要查询的信息:
<br/>
<br/>
<div class="search bar1">
<form action="http://127.0.0.1:8080/process_get" method="GET">
标题      <input type="text" name="title" placeholder="请输入您要搜索的内容...">
<button type="submit"></button>
<br/><br/>
关键词   <input type="text" name="keywords"placeholder="请输入您要搜索的内容...">
<button type="submit"></button>
<br/><br/>
作者      <input type="text" name="author" placeholder="请输入您要搜索的内容...">
<button type="submit"></button>
<br/>
<br/><br/>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
最终的查询界面展示如下:
在实现查询的过程中,为了实现题目中要求的“分项查询”。这里需要注意的是,由于传入的参数包括了多个参数,包括title、keywords和author姓名,构造mysql查询语句时需要做一个交集处理。如果这里是非分项查询时,只需要在整体数据库中匹配对应的词语即可,而这里是分项查询,所以要做一个and操作处理,同时对于有些框没有输入的也不能有报错。
为了实现上述要求,这里增添了一个tag标识符,当在查询语句中已经有一句查询时(例如查询title中包含“中”),那么如果是在此基础上还要求取并集(例如查询keywords中包含“文化”),那么需要在select查询语句中增加一个and,所以解决办法就是使用标识符判断是否已经存在一句查询了,如果有的话需要增加and,如果没有的话将标识符修改为ture,说明当前查询中已经存在了一句查询了。
这样的好处是可以支持用户对于既有标题限制又有关键词限制的查询,同样可以查询到结果,而非像之前一样搜索只能限制在某一个属性中进行查询了。
当接收到查询语句后,这里对输出格式做了修改,不再单纯用JSON格式输出。
var express = require('express');
var mysql = require('./mysql.js')
var app = express();
/* GET home page. */
app.get('/test.html', function(req, res) {
res.sendFile(__dirname + "/" + "test.html");
})
app.get('/process_get', function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' }); //设置res编码为utf-8
//sql字符串和参数
var fetchSql = "select url,source_name,title,keywords,author,publish_date from fetches where";
var tag = false;
if (req.query.title != ""){
if (!tag){tag=true;}
else fetchSql+=" and"
fetchSql += " title like '%" + req.query.title + "%'";
}
if (req.query.keywords != ""){
if (!tag){tag=true;}
else fetchSql+=" and"
fetchSql += " keywords like '%"+req.query.keywords + "%'";
}
if (req.query.author != ""){
if (!tag){tag=true;}
else fetchSql+=" and"
fetchSql += " author like '%"+req.query.author + "%'";
}
fetchSql+=";";
if (!tag){
fetchSql = "select * from fetches where author like '123456'"
}
mysql.query(fetchSql, function(err, result, fields) {
var str="已经为您搜索到如下的结果:<br><br><br>";
//console.log(result);
var object=JSON.parse(JSON.stringify(result));
object.forEach(element => {
str+="网页地址: <br>" + element.url + "<br>";
str+="来源:<br>" + element.source_name + "<br>";
str+="标题: <br>" + element.title + "<br>";
str+="关键词: <br>" + element.keywords + "<br>";
str+="作者:<br>";
if(element.author == ""){
str+="无 <br>";
}else{
str+=element.author+"<br>";
}
str+="出版日期:<br>" + element.publish_date.substring(0, 10) +"<br><br><br><br>";
});
res.end(str);
});
})
var server = app.listen(8080, function() {
console.log("访问地址为 http://127.0.0.1:8080/test.html")
})
最终显示的结果如下:
网页-第二次优化:使用express,加入表格,加入时间排序
在前几天知道了查询界面要求以表格的形式进行展示,同时还要对所查的关键词实现时间热度分析的功能,所以对网页做了第二次和第三次优化。
在命令行输入express -e search_site,创建search_site的文件夹,将数据库设置文件mysql.js拷贝进该文件夹。
在文件夹对应的命令行中输入pm install mysql -save和npm install来安装包和依赖项。
打开search_site文件夹,将之前第一次中的查询文件放入到index.js文件中。
在相应public文件夹下的stylesheets中创建style.css文件,将之前的css部分放入其中。
最后将网页search.html放入到文件夹中,在文件头加入下面的语句引入css。
<link rel="stylesheet" href="stylesheets/style.css">
引入表格,将搜索结果以表格的形式输出:
<script>
$(document).ready(function() {
$("input:button").click(function() {
$.get('/process_get?title=' + $("input[name='title']").val() + '&keywords=' + $("input[name='keywords']").val()
+ '&author=' + $("input[name='author']").val(), function(data) {
$("#record1").empty();
for (let list of data) {
let table = '<tr class="cardLayout"><td>';
Object.values(list).forEach(element => {
table += (element + '</td><td>');
});
$("#record1").append(table + '</td></tr>');
}
});
});
});
</script>
表格样式的css参照了网上的代码,这里不再具体贴出了。
在对应的index.js关于搜索sql语句的查询描述中最后加上按发表时间倒序排序,从而使得最终查询结果能够发布时间较晚的(较新的新闻)首先展示给用户。
fetchSql+=" order by publish_date DESC";
加入了搜索转化为表格后的结果如下,且搜索结果按时间由后到新排序:
网页-第三次优化:加入时间热度分析,加入蜘蛛网背景
题目要求还包括完成对搜索内容的时间热度分析,比如搜索“新冠”,可以展示爬取数据内容中每一天包含“新冠”的条数,这里同样是用表格展示搜索结果。
这里选择了增加一个搜索框,用于时间热度分析,用户可以选择热度分析的顺序(时间、来源等)。如果用户决定了使用某一种类别进行分析,例如时间,那么在对应的sql语句中会按照这一类别进行group by分组,从而将其按照相同的组整理,从而再通过表格的形式展示该每一组的查询包含的结果,对应的sql语句是:
select publish_date, count(*) where … group by publish_date
最终的index.js下代码如下:
var express = require('express');
var router = express.Router();
var mysql = require('../mysql.js')
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
router.get('/process_get', function(req, res) {
// res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' }); //设置res编码为utf-8
//sql字符串和参数
var fetchSql = "select url,title,source_name,keywords,publish_date";
var tag = false;
var WhereSql = " from fetches where"
if (req.query.title != ""){
if (!tag){tag=true;}
else WhereSql+=" and"
WhereSql += " title like '%" + req.query.title + "%'";
}
if (req.query.keywords != ""){
if (!tag){tag=true;}
else WhereSql+=" and"
WhereSql += " keywords like '%"+req.query.keywords + "%'";
}
if (req.query.author != ""){
if (!tag){tag=true;}
else WhereSql+=" and"
WhereSql += " author like '%"+req.query.author + "%'";
}
fetchSql += WhereSql;
if (req.query.time == "时间"){
fetchSql = " select publish_date, count(*)" + WhereSql + " group by publish_date";
}
fetchSql+=" order by publish_date DESC";
if (req.query.time == "来源"){
fetchSql = " select source_name, count(*)" + WhereSql + " group by source_name";
}
fetchSql+=";";
if (!tag){
fetchSql = "select * from fetches where author like '123456';"
}
mysql.query(fetchSql, function(err, result, fields) {
if(err) {
console.log(err)
}
res.writeHead(200, {
"Content-Type": "application/json"
});
console.log(result);
res.write(JSON.stringify(result));
res.end();
});
})
module.exports = router;
对应的search.html下需要增添的内容如下,由于增加了时间热度分析,所以把之前的三个搜索按钮框改为了底下有一个搜索按钮键来实现。
热度分析<input type="text" name="time" placeholder="请输入您热度分类的指标:时间/来源">
<br/><br>
<input class="form-submit" type="button" value="查询">
此外在前端还给网页添加一个基于canvas绘制的蜘蛛网js背景(与实验要求无关,单纯觉得挺好看的233),参考了该博客:js蛛网背景实现
最终前端的效果如下:
表格查询(多项联合查询例子,标题包含中国and关键词包含日本)
热度查询(对于前三个要求约束的查询信息按照发表日期分组,按分组日期倒序展示结果,在下图例子中,结果显示了每一天搜索对应的标题包含“中国”的条数)
总结
本次实验是首次web编程的项目,主要任务包括爬虫、数据库查询与web界面的实现,通过本次实验,对整个web项目的搭建有了基本的认识。