@TOC
残缺代码能够拜访GitHub链接
试验要求
一、外围需要
1、选取3-5个代表性的新闻网站(比方新浪新闻、网易新闻等,或者某个垂直畛域权威性的网站比方经济畛域的雪球财经、西方财产等,或者体育畛域的腾讯体育、虎扑体育等等)建设爬虫,针对不同网站的新闻页面进行剖析,爬取出编码、题目、作者、工夫、关键词、摘要、内容、起源等结构化信息,存储在数据库中。
2、建设网站提供对爬取内容的分项全文搜寻,给出所查关键词的工夫热度剖析。
二、技术要求
1、必须采纳Node.JS实现网络爬虫
2、必须采纳Node.JS实现查问网站后端,HTML+JS实现前端(尽量不要应用任何前后端框架)
后果展现
demo展现(不蕴含解说)
(带有解说的前端展现已放入文件夹中)
试验过程
一、爬取新闻网站内容
设定了每天每隔两个小时进行定时爬虫,通过了一天多的新闻数据爬取,最终取得125条中国广播网的新闻、336条西方财产的新闻和69条网易体育的新闻,具体数据如下:
中国广播网:
西方财富网:网易体育网:
1、总体流程
- 依据url读取种子页面
- 取得种子页面中的网页链接
- 解决链接的url,筛选出所需的新闻url
- 读取每条新闻url的页面
- 剖析页面构造,提取页面中题目、内容、登载工夫等字段
- 将提取的字段存储入mysql数据库中
(其中因为不同新闻网站的url格局和页面构造不同,导致不同网页的筛选url格局和爬取字段内容不同,所以对于不同的页面,步骤三、五、六将会存在区别,以下将分为爬取新闻网站中类似步骤和不同步骤两局部具体讲述)
2、爬取不同新闻网站中雷同的步骤
接下来以中国广播网为例,进行读取种子页面、取得种子页面中的网页链接、读取每条新闻url的页面三个步骤
1)读取种子页面
==request==:第三方HTTP申请工具,向网页发动拜访,接管返回内容
应用request中的GET办法,依据URL获取页面信息(GET办法的模板)
// 应用request库,向网页发动拜访,接管返回内容var request = require('request')request('url', function (err, res, body) { // 页面申请胜利 if (!err && res.statusCode == 200) { console.log(body) }})
在GET办法中设置参数headers,避免网站屏蔽咱们的爬虫
// 应用request库,向网页发动拜访,接管返回内容var request = require('request') // 定义request模块,进行异步fetch urlfunction MyRequest(url, callback) { // 设置GET办法中的参数,其中包含headers var options = { url: url, encoding: null, headers: headers, timeout: 10000 } request(options, callback)}// 调用request模块,传入种子页面的URL,爬取网站信息MyRequest(seedURL, function(err, res, body) { // 页面申请胜利 if (!err && res.statusCode == 200) { console.log(body) }});
==iconv-lite==:纯javascript转化字符编码工具
应用iconv-lite中的decode办法将爬取的网站内容转码至utf-8格局
// 应用iconv-lite库,对网页内容进行转码var iconv = require('iconv-lite') if (!err && res.statusCode == 200) { // 对爬取内容进行转码 seed_html = iconv.decode(body,Encode) console.log(seed_html)}
此时将取得网页F12后的所有内容
2)取得种子页面中的网页链接
因为咱们之前爬取的种子页面相当于一个新闻目录,咱们需爬取其中的所有链接,来获取目录中每条新闻的网址
==cheerio==:从html的片断中构建DOM构造,而后提供像jquery一样的css选择器查问
var cheerio = require('cheerio')
咱们首先通过cheerio模块中的load函数创立一个和jQuery选择器用法差不多的选择器 $
其中load函数第一个参数html就是之前http.get办法中所取得的数据;第二个参数可选,次要是用来设置格局,比方decodeEntities:false设置了不会呈现中文乱码
// 创立一个选择器 $,decodeEntities:false设置了不会呈现中文乱码var $ = cheerio.load(seed_html, { decodeEntities: false });
因为链接的格局为,所以采纳选择器$对此格局的链接进行获取,返回抉择到的伪数组实例对象
// 获取种子页面中的所有新闻网页的链接,格局为<a href="">var URL_format = 'a'try { html_URL = eval(URL_format)} catch (error) { console.log("获取种子页面中的链接呈现谬误:" + error)}
后果为所有链接的信息
应用$('div').each(function)办法,遍历 jQuery 选择器抉择到的伪数组实例对象,再通过jquery中用attr()办法来获取href后的网址
attr(属性名) :获取属性的值(获得第一个匹配元素的属性值。通过这个办法能够不便地从第一个匹配元素中获取一个属性的值。如果元素没有相应属性,则返回 undefined )
html_URL.each(function(i,body){ // 遍历伪数组实例对象,获取每个对象的序号i、信息内容body try { // 获取href后的页面网址 var href = ""; href = $(body).attr("href"); //console.log(href) } catch (error) { console.log("获取页面网址呈现谬误:" + error) }})
爬取的网页链接存在以下几种状况,对其别离进行解决
- undefined 元素没有相应属性,不进行网址保留即可
- //www.cnr.cn/ 网址短少了http:结尾,在最后方加上即可
- http://military.cnr.cn/ 残缺的网址,无需改变
- https://www.cnrmall.com/ 残缺的网址,无需改变
- ./ent/zx/20210417/t20210417_525464354.shtml 网址中的一部分,需在其后面加上种子页面的网址,即为残缺的网址
- javascript:void(0) 示意网页不存在,在其后面加上种子页面的网址和/,即为残缺的网址
// 解决网址信息if (typeof(href) == "undefined") { // 为获取到href属性,返回undefined,不进行网址保留即可 return true}// http://结尾的或者https://结尾,残缺的网址,无需改变if (href.toLowerCase().indexOf('http://') >= 0 || href.toLowerCase().indexOf('https://') >= 0) whole_URL = href// //结尾的,短少了http:结尾,在最后方加上即可else if (href.startsWith('//')) whole_URL = 'http:' + href// 其余,在其后面加上种子页面的网址else whole_URL = seedURL + href
3)读取每条新闻url的页面
与获取种子页面信息相似,通过MyRequest办法进行拜访,获取页面信息,再通过iconv模块的decode办法将其解码为utf-8,之后应用cheerio模块的load办法创立选择器$
取得的页面信息如图所示,与F12内容雷同
3、依据不同网站的页面信息,别离提取编码、题目、作者、工夫、关键词、摘要、内容、起源等内容
1)中国广播网
- 依据正确新闻网址的格局,对取得的网址进行判断,选取正确的新闻网址
察看到网页最初为(八位的年月日/t八位数字_九位数字.shtml)组成,以此进行正则化筛选
// 依据正确的网页格局,正则化筛选网址var url_reg = /\/(\d{8})\/t(\d{8})_(\d{9}).shtml/// 测验是否合乎新闻url的正则表达式if (!url_reg.test(whole_URL)) returnelse{ // 否则依据网址解析网页,取得所需内容 news_get_info(whole_URL)}
- 从页面中提取题目、作者、工夫、内容和起源五个字段
题目:
获取为“article-header”的class类,再进入标签h1,从而取得题目的内容
var title_format = "$('.article-header > h1').text()"// 存储题目,若不存在题目则设置为空if (title_format == "") fetch.title = ""else fetch.title = eval(title_format)
作者:
获取为“editor”的class类,从而取得作者的内容,因为取得的内容中存在\n、\t等符号,应用replace将其删除
var author_format = "$('.editor').text()"// 存储作者名称,若不存在,将其设置为网页名,其中删除空格、\t、\n等字符if (author_format == "") fetch.author = source_name else { fetch.author = eval(author_format) if (fetch.author != null){ fetch.author = fetch.author.replace(/\s/g, "") }}
工夫:
获取为“source”的class类,再进入标签span,从而取得工夫的内容,删除空格、\t、\n等字符,依据设定的正则表达式获取年月日的信息,将其中的年月代替为-,将日删除,即为新闻的登载日期
var date_format = "$('.source > span').text()"// 解析工夫日期var regExp = /((\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2})|(\d{4}年\d{1,2}月\d{1,2}日)/// 存储登载日期,因为爬得的日期第一局部则为所需格局的年月日,无需进行工夫提取if (date_format != "") { fetch.publish_date = eval(date_format) if (fetch.publish_date != null){ fetch.publish_date = fetch.publish_date.replace(/\s/g, "") }}// 依据正则化式,在一个指定字符串中执行一个搜寻匹配,返回匹配失去的数组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('日', '')console.log('date: ' + fetch.publish_date)// 将其转换为Date的格局fetch.publish_date = new Date(fetch.publish_date).toFormat("YYYY-MM-DD")
内容:
获取为“article-body”的class类,再进入标签div,从而取得新闻的内容,因为取得的内容中存在\n、空格等符号,应用replace将其删除
var content_format = "$('.article-body > div').text()"// 存储内容,若不存在,将其设置为空,其中删除空格、\t、\n等字符if (content_format == "") fetch.content = ""else { fetch.content = eval(content_format) if (fetch.content != null){ fetch.content = fetch.content.replace(/\s/g, "") }}
起源:
获取为“source”的class类,再进入标签span,从而取得起源的内容,将其依据冒号进行宰割,获取第二个元素,并且删除内容"原创版权禁止商业转载",即为起源的信息
var source_format = "$('.source > span').text()" // 存储起源名,若不存在,将其设置为网页名,其中依据格局获取冒号后内容,再删除"原创版权禁止商业转载"内容if (source_format == "") fetch.source = source_nameelse { fetch.source = eval(source_format).split(" ")[1].split(":")[1] if (fetch.source != null){ fetch.source = fetch.source.replace("原创版权禁止商业转载","") }}
- 表格字段内容
创建表格news_info用于寄存中国广播网的信息,news_info表格局如下所示:
CREATE TABLE IF NOT EXISTS `news_info` ( // 用于标记每条数据的id,其值不能够为空,并且在插入数据后主动减少(个别用于主键) `id` int(11) NOT NULL AUTO_INCREMENT, // 用于寄存网址url,默认为空字符 `url` varchar(200) DEFAULT NULL, // 用于寄存种子页面的起源,默认为空字符 `source_name` varchar(200) DEFAULT NULL, // 用于寄存网页的编码方式,默认为空字符 `source_encoding` varchar(45) DEFAULT NULL, // 用于寄存网页的题目,默认为空字符 `title` varchar(200) DEFAULT NULL, // 用于寄存新闻的起源,默认为空字符 `source` 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, // 将id设置为主键 PRIMARY KEY (`id`), // id不能够反复 UNIQUE KEY `id_UNIQUE` (`id`), // url不能够反复 UNIQUE KEY `url_UNIQUE` (`url`) // 设置存储引擎,这是编码为utf8) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2)网易体育
- 依据正确新闻网址的格局,对取得的网址进行判断,选取正确的新闻网址
察看到网页最初为(/article/一个字母一个数字一个字母一个数字四个字母八个数字.shtml)组成,以此进行正则化筛选
// 依据正确的网页格局,正则化筛选网址var url_reg = /\/article\/(\w{1})(\d{1})(\w{1})(\d{1})(\w{4})(\d{4})(\d{4}).html/// 测验是否合乎新闻url的正则表达式if (!url_reg.test(whole_URL)) returnelse{ // 否则依据网址解析网页,取得所需内容 news_get_info(whole_URL)}
- 从页面中提取题目、作者、工夫、起源、内容和责任编辑六个字段
题目:
获取为“post_title”的class类,从而取得题目的内容
var title_format = "$('.post_title').text()"// 存储题目,若不存在题目则设置为空if (title_format == "") fetch.title = ""else fetch.title = eval(title_format)
作者:
获取为“post_author”的class类,从而取得作者的内容,将其依据冒号进行宰割,获取第三个元素,并且删除空格、\t、\n等字符,删除“作者”内容,删除“责任编辑”内容,即为作者的信息
var author_format = "$('.post_author').text()"// 存储作者名称,若不存在,将其设置为网页名,其中依据冒号进行宰割,获取第三局部的内容,再删除空格、\t、\n等字符,删除“责任编辑”内容if (author_format == "") fetch.author = source_name else{ fetch.author = eval(author_format).split(":")[2] if (fetch.author != null){ fetch.author = fetch.author.replace(/\s/g, "").replace("作者","").replace("责任编辑","") } }
工夫:
获取为“post_info”的class类,从而取得工夫的内容,删除空格、\t、\n等字符,依据设定的正则表达式获取年月日的信息,将其中的年月代替为-,将日删除,即为新闻的登载日期
var date_format = "$('.post_info').text()"// 解析工夫日期var regExp = /((\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2})|(\d{4}年\d{1,2}月\d{1,2}日)/// 存储登载日期,因为爬得的日期中存在空格等字符,应用replace将其删除if (date_format != "") { fetch.publish_date = eval(date_format) if (fetch.publish_date != null){ fetch.publish_date = fetch.publish_date.replace(/\s/g, "") }}// 依据正则化式,在一个指定字符串中执行一个搜寻匹配,返回匹配失去的数组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('日', '')console.log('date: ' + fetch.publish_date)// 将其转换为Date的格局fetch.publish_date = new Date(fetch.publish_date).toFormat("YYYY-MM-DD")
起源:
获取为“post_author”的class类,从而取得起源的内容,将其依据冒号进行宰割,获取第二个元素,并且删除空格、\t、\n等字符,删除“作者”内容,删除“责任编辑”内容,即为起源的信息
var source_format = "$('.post_author').text()"// 存储起源名,若不存在,将其设置为网页名,其中依据冒号进行宰割,获取第二局部的内容,再删除空格、\t、\n等字符,删除“作者”内容if (source_format == "") fetch.source = source_nameelse{ fetch.source = eval(source_format).split(":")[1] if (fetch.source != null){ fetch.source = fetch.source.replace(/\s/g, "").replace("作者","").replace("责任编辑","") } }
内容:
获取为“post_body”的class类,再进入标签p,从而取得内容的信息
var content_format = "$('.post_body > p').text()"// 存储内容,若不存在,将其设置为空if (content_format == "") fetch.content = ""else fetch.content = eval(content_format)
责任编辑:
获取为“post_author”的class类,从而取得责任编辑的内容,将其依据冒号进行宰割,获取第四个元素,并且删除空格、\t、\n等字符,即为责任编辑的信息
var edit_format = "$('.post_author').text()"// 存储责任编辑名称,若不存在,将其设置为网页名,其中依据冒号进行宰割,获取第四局部的内容,再删除空格、\t、\n等字符if (edit_format == "") fetch.edit = source_nameelse { fetch.edit = eval(edit_format).split(":")[3] if (fetch.edit != null){ fetch.edit = fetch.edit.replace(/\s/g, "") }}
- 表格字段内容
创建表格sports_info用于寄存网易体育网的信息,sports_info表格局如下所示:
CREATE TABLE IF NOT EXISTS `sports_info` ( // 用于标记每条数据的id,其值不能够为空,并且在插入数据后主动减少(个别用于主键) `id` int(11) NOT NULL AUTO_INCREMENT, // 用于寄存网址url,默认为空字符 `url` varchar(200) DEFAULT NULL, // 用于寄存种子页面的起源,默认为空字符 `source_name` varchar(200) DEFAULT NULL, // 用于寄存网页的编码方式,默认为空字符 `source_encoding` varchar(45) DEFAULT NULL, // 用于寄存网页的题目,默认为空字符 `title` varchar(200) DEFAULT NULL, // 用于寄存新闻的作者,默认为空字符 `author` varchar(200) DEFAULT NULL, // 用于寄存新闻的编辑,默认为空字符 `editor` varchar(200) DEFAULT NULL, // 用于寄存新闻的登载日期,默认为空字符 `publish_date` date DEFAULT NULL, // 用于寄存新闻的起源,默认为空字符 `source` varchar(200) DEFAULT NULL, // 用于寄存爬取网页的工夫,默认为空字符 `crawltime` datetime DEFAULT NULL, // 用于寄存网页的内容 `content` longtext, // 用于寄存插入数据的工夫,默认为插入数据的工夫 `createtime` datetime DEFAULT CURRENT_TIMESTAMP, // 将id设置为主键 PRIMARY KEY (`id`), // id不能够反复 UNIQUE KEY `id_UNIQUE` (`id`), // url不能够反复 UNIQUE KEY `url_UNIQUE` (`url`) // 设置存储引擎,这是编码为gbk) ENGINE=InnoDB DEFAULT CHARSET=gbk;
3)西方财产
- 依据正确新闻网址的格局,对取得的网址进行判断,选取正确的新闻网址
察看到网页最初为(a/t十八位数字.html)组成,以此进行正则化筛选
// 依据正确的网页格局,正则化筛选网址var url_reg = /\/a\/(\d{18}).html/// 测验是否合乎新闻url的正则表达式if (!url_reg.test(whole_URL)) returnelse{ // 否则依据网址解析网页,取得所需内容 news_get_info(whole_URL)}
- 从页面中提取题目、责任编辑、工夫、起源、评论数、探讨数、内容和摘要八个字段
题目:
获取为“newsContent”的class类,而后进入h1标签,从而取得题目的内容
var title_format = "$('.newsContent > h1').text()"// 存储题目,若不存在题目则设置为空if (title_format == "") fetch.title = ""else fetch.title = eval(title_format)
责任编辑:
获取为“res-edit”的class类,从而取得责任编辑的内容,删除空格、\t、\n等字符,即为责任编辑的信息
var edit_format = "$('.res-edit').text()"// 存储责任编辑名称,若不存在,将其设置为网页名,因为爬取的内容存在\n等字符,通过replace将其删除if (edit_format == "") fetch.edit = source_nameelse { fetch.edit = eval(edit_format) if (fetch.edit != null){ fetch.edit = fetch.edit.replace(/\s/g, "") }}
工夫:
获取为“time”的class类,从而取得登载日期的内容,将其依据空格进行宰割,获取第一个元素,将其中的年月代替为-,将日删除,即为新闻的登载日期
var date_format = "$('.time').text()"// 存储登载日期,依据空格将其宰割,获取第一个元素,即为日期if (date_format != "") { fetch.publish_date = eval(date_format).split(" ")[0]}// 将年月代替为-,将日删除fetch.publish_date = fetch.publish_date.replace('年', '-')fetch.publish_date = fetch.publish_date.replace('月', '-')fetch.publish_date = fetch.publish_date.replace('日', '')console.log('date: ' + fetch.publish_date)// 将其转换为Date的格局fetch.publish_date = new Date(fetch.publish_date).toFormat("YYYY-MM-DD")
起源:
获取为“source data-source”的class类,从而取得起源的内容,删除空格、\t、\n等字符,即为起源的信息
var source_format = "$('.source.data-source').text()"// 存储内容,若不存在,将其设置为空if (content_format == "") fetch.content = ""else { fetch.content = eval(content_format) if (fetch.content != null){ fetch.content = fetch.content.replace(/\s/g, "") } }
评论数:
获取为“cNumShow num”的class类,从而取得评论数的内容
var comment_format = "$('.cNumShow.num').text()"// 存储参加人数,若不存在,将其设置为空if (partici_format == "") fetch.partici = 0else fetch.partici = eval(partici_format)
探讨数:
获取为“num ml5”的class类,从而取得探讨数的内容
var partici_format = "$('.num.ml5').text()"// 存储评论数量,若不存在,将其设置为空if (comment_format == "") fetch.comment = 0else fetch.comment = eval(comment_format)
内容:
获取为“Body”的class类,而后进入p标签,从而取得新闻内容,删除空格、\t、\n等字符,即为内容的信息
var content_format = "$('.Body > p').text()"// 存储内容,若不存在,将其设置为空,因为爬取的内容存在\n等字符,通过replace将其删除if (content_format == "") fetch.content = ""else { fetch.content = eval(content_format) if (fetch.content != null){ fetch.content = fetch.content.replace(/\s/g, "") } }
摘要:
获取为“b-review”的class类,从而取得摘要的内容
var desc_format = " $('.b-review').text()"// 存储摘要,若不存在,将其设置为空if (desc_format == "") fetch.desc = ""else fetch.desc = eval(desc_format)
- 表格字段内容
创建表格finance_info用于寄存网易体育网的信息,finance_info表格局如下所示:
CREATE TABLE IF NOT EXISTS `finance_info` ( // 用于标记每条数据的id,其值不能够为空,并且在插入数据后主动减少(个别用于主键) `id` int(11) NOT NULL AUTO_INCREMENT, // 用于寄存网址url,默认为空字符 `url` varchar(200) DEFAULT NULL, // 用于寄存种子页面的起源,默认为空字符 `source_name` varchar(200) DEFAULT NULL, // 用于寄存网页的编码方式,默认为空字符 `source_encoding` varchar(45) DEFAULT NULL, // 用于寄存网页的题目,默认为空字符 `title` varchar(200) DEFAULT NULL, // 用于寄存新闻的编辑,默认为空字符 `editor` varchar(200) DEFAULT NULL, // 用于寄存新闻的登载日期,默认为空字符 `publish_date` date DEFAULT NULL, // 用于寄存新闻的起源,默认为空字符 `source` varchar(200) DEFAULT NULL, // 用于寄存爬取网页的工夫,默认为空字符 `crawltime` datetime DEFAULT NULL, // 用于寄存网页的内容 `content` longtext, // 用于寄存网页的摘要,默认为空字符 `description` varchar(200) DEFAULT NULL, // 用于寄存网页的参加人数,默认为空字符 `participate_number` varchar(45) DEFAULT NULL, // 用于寄存网页的评论数,默认为空字符 `comment_number` varchar(45) DEFAULT NULL, // 用于寄存插入数据的工夫,默认为插入数据的工夫 `createtime` datetime DEFAULT CURRENT_TIMESTAMP, // 将id设置为主键 PRIMARY KEY (`id`), // id不能够反复 UNIQUE KEY `id_UNIQUE` (`id`), // url不能够反复 UNIQUE KEY `url_UNIQUE` (`url`) // 设置存储引擎,这是编码为utf-8) ENGINE=InnoDB DEFAULT CHARSET=utf8;
4、将爬取数据存入数据库中
1)判断是否反复爬取雷同的URL
==mysql==:用于连贯数据库,对数据库进行增删改查等操作
在mysql.js文件中应用mysql库设置数据库的配置,包含用户名、明码以及数据库名称
// 应用mysql库连贯数据库var mysql = require("mysql")// 设置数据库的配置,包含用户名、明码以及数据库名称var pool = mysql.createPool({ host: '127.0.0.1', user: 'root', password: 'root', database: 'test1'})
而后,应用getConnection函数连贯数据库,定义函数query用于执行具备查问语句和查问参数的数据库查问,输出sql语句和参数,返回callback
// 执行具备查问语句和查问参数的数据库查问,输出sql语句和参数,返回callbackvar query = function(sql, sqlparam, callback) { // 依据设置的数据库参数连贯数据库 pool.getConnection(function(err, conn) { if (err) { callback(err, null) } else { // 进行查问语句的查问 conn.query(sql, sqlparam, function(err, result) { conn.release(); //开释连贯 callback(err, result); //事件驱动回调 }) } })}
定义函数query_noparam用于执行具备查问语句,没有查问参数的数据库查问,输出sql语句,返回callback
// 执行具备查问语句,没有查问参数的数据库查问,输出sql语句,返回callbackvar query_noparam = function(sql, callback) { // 依据设置的数据库参数连贯数据库 pool.getConnection(function(err, conn) { if (err) { callback(err, null) } else { // 进行查问语句的查问 conn.query(sql, function(err, result) { conn.release() //开释连贯 callback(err, result) //事件驱动回调 }) } })}
再将办法query和query_noparam模块化,便于在其余文件中进行应用
// 将办法query和query_noparam模块化,便于在其余文件中进行应用exports.query = queryexports.query_noparam = query_noparam
因为在数据库中将url设置为UNIQE类型,因而须要url不能反复,所以在依据url爬取页面之前,先判断其url是否存在于数据库中,防止反复爬取雷同的页面
// 从数据库中查问URL,判断之前是否进行该网址的爬取var fetch_url_Sql = 'select url from news_info where url=?' // 从表web_info中提取相应URL的数据var fetch_url_Sql_Params = [whole_URL] // 查问的参数// 调用mysql文件中的query模块(具备参数的数据库语句查问)mysql.query(fetch_url_Sql, fetch_url_Sql_Params, function(err, result) { if (err) console.log(err) // 查问返回内容表述数据库中存在该URL的数据,即反复爬取 if (result[0] != null) { console.log('URL:' + whole_URL + 'duplicate!') } else newsGet(myURL); // 未爬取过这个网页,进行新闻页面的读取})
2)将爬取的数据存入数据库中
编写将爬取的数据写入数据库的mysql语句,编写插入语句中相应的参数,而后调用办法query,将数据写入mysql
// 编写将爬取的数据写入数据库的mysql语句var fetchAddSql = 'INSERT INTO news_info(url,source_name,source_encoding,title,' +'source,author,publish_date,crawltime,content) VALUES(?,?,?,?,?,?,?,?,?)'// 插入语句中相应的参数var fetchAddSql_Params = [fetch.url, fetch.source_name, fetch.source_encoding,fetch.title, fetch.source, 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(err, result) {if (err) { console.log(err);}}) //mysql写入
5、爬虫定时操作
==node-schedule==:用于设置定时工作
// 调用设置定时工作的模块var schedule = require('node-schedule')
应用RecurrrenceRule函数制订工作的规定,将其设置为每天凌晨0点整开始,每隔两个小时主动爬取
// 定义规定var rule = new schedule.RecurrenceRule()// 设置参数rule.hour = [0,2,4,6,8,10,12,14,16,18,20,22] // 每隔两小时进行主动爬取数据rule.minute = 0rule.second = 0
应用scheduleJob函数,进行函数get_info的执行,从而实现爬虫的定时操作
//定时执行 get_info() 函数schedule.scheduleJob(rule, function() { get_info()})
二、分项全文搜寻以及关键词的工夫热度剖析
1、前端设计
1)前端页面概述
因为爬取了三个类别的新闻网站,我将其分为三种主题的新闻信息查问,别离为正式新闻、体育新闻和财经新闻。
用户能够登入主页面,依据主页面中的新闻网站简介,抉择查问哪一板块的新闻
在查问信息内容中,会提供不同的查问内容(包含题目、内容等字段)进行复合查问,若用户在相应字段不输出内容,则默认对其字段进行全文搜寻
在查问信息内容中,会以表格的模式向用户返回相应板块中最新的5条新闻内容
在查问信息内容中,在用户点击查问按钮后,会以表格模式返回用户的查问后果,并且以折线图的模式返回题目的内容的工夫热度剖析(展现用户搜寻题目的字段在爬取内容中每一天蕴含的条数)
以下是查问题目中蕴含“疫苗”的后果
在查问词热度剖析中,以柱状图向用户展现题目和内容的查问词的频率,便于用户理解哪些查问词是最常被查问的
2)主页面:home.html
主页面次要包含滚动的幻灯片用于欢送用户返回网站、对于这个网站的介绍、对三种查问新闻网站的简介、提供链接进行查问新闻以及查看查问词的热度剖析
- 滚动的幻灯片
应用了==Bootstrap==中的轮播(Carousel)插件
<div class="carousel slide" id="carousel-832580"> <ol class="carousel-indicators"> <li data-slide-to="0" data-target="#carousel-832580"> </li> <li data-slide-to="1" data-target="#carousel-832580"> </li> <li data-slide-to="2" data-target="#carousel-832580" class="active"> </li> </ol> <div class="carousel-inner"> <div class="item"> <img alt="" src="博客.jpg" class="img-responsive center-block" /> <div class="carousel-caption"> </div> </div> <div class="item"> <img alt="" src="欢送.jpg" class="img-responsive center-block" /> <div class="carousel-caption"> </div> </div> <div class="item active"> <img alt="" src="欢送表情包.jpg" class="img-responsive center-block" /> <div class="carousel-caption"> </div> </div> </div> <a class="left carousel-control" href="#carousel-832580" data-slide="prev"><span class="glyphicon glyphicon-chevron-left"></span></a> <a class="right carousel-control" href="#carousel-832580" data-slide="next"><span class="glyphicon glyphicon-chevron-right"></span></a> </div>
- 对于这个网站的介绍
<div class="container"> <div class="row clearfix"> <div class="col-md-12 column"> <p class="lead text-left"> 欢送来到<strong>新闻内容查问的网站</strong> ,在这里你能够查问到<strong>中国新闻</strong>、<strong>体育娱乐</strong>和<strong>财经信息</strong>三个板块的新闻内容,其别离来自于网站<strong>中国广播网</strong>、<strong>网易体育</strong>和<strong>西方财产</strong>。 </p> </div> </div></div>
- 对三种查问新闻网站的简介
- 提供链接进行查问新闻和查看查问词的热度剖析
应用了==Bootstrap==中的缩略图(thumbnail)插件
<div class="row"> <div class="col-md-4"> <div class="thumbnail"> <img alt="300x200" src="中国广播网.jpg" /> <div class="caption"> <h3> 中国广播网新闻 </h3> <p> 中国广播网,由中央人民广播电台主办,具备显明的播送特色,致力于打造“全天24小时不间断直播的中文互动在线播送第一品牌”,建设寰球最大中文音频网络门户,通过互联网"让中国的声音传向世界各地"。 </p> <p> <a class="btn btn-primary" href="/news_info.html">查问信息</a> <a class="btn btn-primary" href="/news_search.html">查问词热度剖析</a> </div> </div> </div> <div class="col-md-4"> <div class="thumbnail"> <img alt="300x200" src="网易体育网.jpg" /> <div class="caption"> <h3> 网易体育新闻 </h3> <p> 网易体育,有态度的体育门户,蕴含体育新闻,NBA,CBA,英超,意甲,西甲,冠军杯,体育比分,足彩,福彩,体育秀色,网球,F1,棋牌,乒羽,体育论坛,中超,中国足球,综合体育等业余体育门户网站。 </p> <p> <a class="btn btn-primary" href="/sports_info.html">查问信息</a> <a class="btn btn-primary" href="/sports_search.html">查问词热度剖析</a> </p> </div> </div> </div> <div class="col-md-4"> <div class="thumbnail"> <img alt="300x200" src="西方财富网.jpg" /> <div class="caption"> <h3> 西方财富网新闻 </h3> <p> 西方财富网,业余的互联网财经媒体,提供7*24小时财经资讯及寰球金融市场报价,汇聚全方位的综合财经资讯和金融市场资讯,笼罩股票、财经、基金、期货等。 </p> <p> <a class="btn btn-primary" href="/finance_info.html">查问信息</a> <a class="btn btn-primary" href="/finance_search.html">查问词热度剖析</a> </p> </div> </div> </div> </div>
3)查问新闻页面(news_info.html、finance_info.html、sports_info.html)
三个新闻的查问页面构造大致相同,包含用户能够进行查问的字段、返回的5条举荐最新新闻、返回的用户查问内容以及查问词工夫热度剖析
- 用户能够查问的字段
因为每个新闻网站爬取的字段不同,我对于三个网站选取不同的字段,其中中国广播网蕴含题目、作者、内容和登载工夫;网易体育网蕴含题目、作者、编辑、内容和登载工夫;西方财富网包含题目、编辑、摘要、内容和登载工夫
<form> <br> 题目:<input type="text" name="title_text"> <br> <br> 内容:<input type="text" name="content_text"> <br> <br> 作者:<input type="text" name="author_text"> <br> <br> 登载工夫:<input type="text" name="publish_time_text"> <br> <br> <input class="form-submit" type="button" value="查问"></form>
- 返回的5条举荐最新新闻
其中为了好看,应用css扭转了表格的格局,表格的css格局如下:
table thead,table tr {border-top-width: 1px;border-top-style: solid;border-top-color: #a8bfde;}table {border-bottom-width: 1px;border-bottom-style: solid;border-bottom-color: #a8bfde;}/* Padding and font style */table td, table th {padding: 5px 10px;font-size: 12px;font-family: Verdana;color: #5b7da3;}/* Alternating background colors */table tr:nth-child(even) {background: #d3dfed}table tr:nth-child(odd) {background: #FFF}
构建id = record1的表格,将其清空,而后增加表头,其中包含url、起源、题目、作者、登载日期字段,再对GET操作失去的每一行数据改成合乎表格模式,最初增加到record1中
<div class="cardLayout" style="margin: 10px 0px"> <table width="100%" id="record1"></table></div><script> $(document).ready(function() { $.get('/get_recommand_news_info', function(data) { $("#record1").empty(); $("#record1").append('<tr class="cardLayout"><td>url</td><td>source</td><td>title</td><td>author</td><td>publish_date</td></tr>'); 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>
- 返回的用户查问内容
其中应用了==bootstrap table==插件,使得对查问后果分页并以某个字段进行排序
构建id = record2的表格,并且为后面的提交按钮,绑定一个脚本,使得用户点击按钮后,方能显示查问后果的表格
通过GET办法拜访给定的url,并且将返回的json文件,解析生成表格,其中field的值应该和返回的json文件对应属性的key/value雷同
<div class="cardLayout" style="margin: 10px 0px"> <table width="100%" id="record2"></table></div><script> $(document).ready(function() { $("input:button").click(function() { var params = '/get_news_info?title=' + $('input[name="title_text"]').val() + '&content=' + $('input[name="content_text"]').val() + '&author=' + $('input[name="author_text"]').val() + '&publish_time=' + $('input[name="publish_time_text"]').val() $(function(){ $("#record2").empty(); $('#record2').bootstrapTable({ url:params, method:'GET', pagination:true, sidePagination:'client', pageSize:5, striped : true, showRefresh:true, search:true, showToggle: true, toolbar: '#toolbar', showColumns : true, columns:[{ field:'url', title:'url', },{ field:'source', title:'source', },{ field:'title', title:'title', sortable : true },{ field:'author', title:'author', },{ field:'new_publish_date', title:'publish_date', sortable : true }] }) }); }); });</script>
- 查问词工夫热度剖析
应用了==Echarts==,一个纯 Javascript 的图表库,用于绘制柱状图、折线图等图标,便于用户更直观的理解后果
针对查问词 ==题目字段== 的返回后果绘制查问词的工夫热度剖析图,折线图的横坐标为新闻登载工夫,纵坐标为查问后果中登载工夫为横坐标的条数
(因为只是针对题目字段,若用户未输出题目,则将返回数据库中所有新闻的工夫热度剖析图)
<!-- 为ECharts筹备一个具备大小(宽高)的Dom --><div id="time" style="width: 800px;height:500px;"></div><script type="text/javascript"> $(document).ready(function() { $("input:button").click(function() { // 基于筹备好的dom,初始化echarts实例 var myChart2 = echarts.init(document.getElementById('time')); // 异步加载数据 var param = '/news_time_info?title=' + $('input[name="title_text"]').val() $.get(param).done(function (result) { myChart2.setOption({ tooltip: {}, xAxis: { data: result[0] }, yAxis: {}, series: [{ name: '频数', type: 'line', data: result[1] }] }) }) }) })</script>
4)查问词热度剖析页面(news_search.html、finance_search.html、sports_search.html)
应用了==Echarts==,一个纯 Javascript 的图表库
三个新闻的查问词热度剖析页面构造大致相同,其中包含题目的查问词热度剖析图和内容的查问词热度剖析图,两张柱状图的横坐标为依照查问次数从高到低排序的查问词,纵坐标为查问次数
<!-- 为ECharts筹备一个具备大小(宽高)的Dom --><div id="title" style="width: 400px;height:300px;"></div> <script type="text/javascript"> // 基于筹备好的dom,初始化echarts实例 var myChart1 = echarts.init(document.getElementById('title')); // 异步加载数据 $.get('/news_title_info').done(function (result) { myChart1.setOption({ title: { text: '题目查问词的热度剖析' }, tooltip: {}, xAxis: { data: result[0] }, yAxis: {}, series: [{ name: '频数', type: 'bar', data: result[1] }] }) }) </script><!-- 为ECharts筹备一个具备大小(宽高)的Dom --><div id="content" style="width: 400px;height:300px;"></div> <script type="text/javascript"> // 基于筹备好的dom,初始化echarts实例 var myChart2 = echarts.init(document.getElementById('content')); // 异步加载数据 $.get('/news_content_info').done(function (result) { myChart2.setOption({ title: { text: '内容查问词的热度剖析' }, tooltip: {}, xAxis: { data: result[0] }, yAxis: {}, series: [{ name: '频数', type: 'bar', data: result[1] }] }) }) </script>
2、后端设计
三种新闻内容的后端设计大致相同,区别在于别离从不同的表中抉择信息(别离从news_info表、finance_info表和sports_info表中取得查问和举荐新闻的后果、查问词工夫热度剖析后果;别离从newssearch表、sportssearch表和financesearch表中取得查问词的热度剖析后果)
以下将以中国广播网的后端为例:
1)用户查问返回新闻
首先,从前端取得用户提交查问字段的内容,编写插入语句,将用户的查问内容插入newssearch表中
因为用户查问的内容均为字符串,所以将publish_date类型设置为字符类型,newssearch表的字段内容如下所示:
CREATE TABLE IF NOT EXISTS `newssearch` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(200) DEFAULT NULL, `author` varchar(200) DEFAULT NULL, `publish_date` varchar(200) DEFAULT NULL, `content` longtext, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=gbk;
var searchSql = 'INSERT INTO newssearch(title,publish_date,author,content)' + ' VALUES (?,?,?,?)'var searchSql_Params = [request.query.title,request.query.publish_time,request.query.author,request.query.content] mysql.query(searchSql, searchSql_Params, function(err, result, fields) { if (err) console.log(err)})
而后,编写查问语句,从news_info表中选取蕴含用户查问内容的新闻信息,将后果转换为JSON类型,发送至前端
var fetchSql = "select url,source,title,author,cast(date_format(publish_date,'%Y-%m-%d') as char) as new_publish_date from news_info where title like '%" +request.query.title + "%' and author like '%" + request.query.author + "%' and content like '%" +request.query.content + "%' and publish_date like '%" + request.query.publish_time + "%'";mysql.query(fetchSql, function(err, result, fields) { response.writeHead(200, { "Content-Type": "application/json" }) response.status = true; response.write(JSON.stringify(result)); response.end()})
2)用户不查问,返回举荐的最新新闻
编写查问语句,将news_info表中信息依据publish_date进行降序排序,而后选取前5条的新闻信息,将后果转换为JSON类型,发送至前端
var fetchSql = "select url,source,title,author,cast(date_format(publish_date,'%Y-%m-%d') as char) as new_publish_date from news_info order by publish_date DESC limit 5";mysql.query(fetchSql, function(err, result, fields) { response.writeHead(200, { "Content-Type": "application/json" }) response.status = true; response.write(JSON.stringify(result)); response.end()})
3)查问词的热度剖析
思考到用户依据题目和内容查问的次数较多,所以针对这两个字段进行查问词的热度剖析,提供用户在这两个字段中查问较高频率的内容
编写查问语句,选取news_info表中的题目或内容不为空的信息,对其依据题目或内容进行分组,再依据题目或内容的频数进行排序,返回题目或内容以及其频数至前端
// 返回题目查问词的热度剖析var fetchSql = "select title,count(*) from newssearch where title != '' group by title order by count(*) desc";mysql.query(fetchSql, function(err, result, fields) { response.writeHead(200, { "Content-Type": "application/json" }) var search = []; var the_count = []; for (var i = 0; i < result.length; i++) { search[i] = (result[i]['title']).toString(); the_count[i] = result[i]['count(*)']; } var tempt = [search,the_count] response.write(JSON.stringify(tempt)); response.end()})// 返回内容查问词的热度剖析var fetchSql = "select content,count(*) from newssearch where content != '' group by content order by count(*) desc";mysql.query(fetchSql, function(err, result, fields) { response.writeHead(200, { "Content-Type": "application/json" }) var search = []; var the_count = []; for (var i = 0; i < result.length; i++) { search[i] = (result[i]['content']).toString(); the_count[i] = result[i]['count(*)']; } var tempt = [search,the_count] response.write(JSON.stringify(tempt)); response.end()})
4)查问词的工夫热度剖析
这里选定对用户查问的题目字段进行工夫热度剖析,返回题目中蕴含用户查问内容的信息在每一天的条数,若用户不输出题目字段,则对所有新闻信息进行工夫热度剖析
编写查问语句,从news_info表中选取蕴含题目查问内容的信息,将其依照publish_date分组,并依据publish_date字段升序排序,返回publish_date字段以及其对应的个数
var fetchSql = "select cast(date_format(publish_date,'%Y-%m-%d') as char) as new_publish_date,count(*) from news_info where title like '%" + request.query.title + "%' group by publish_date order by publish_date";mysql.query(fetchSql, function(err, result, fields) { response.writeHead(200, { "Content-Type": "application/json" }) var search = []; var the_count = []; console.log(result) for (var i = 0; i < result.length; i++) { search[i] = (result[i]['new_publish_date']).toString(); the_count[i] = result[i]['count(*)']; } var tempt = [search,the_count] response.write(JSON.stringify(tempt)); response.end()})
3、扩大性能
1)对查问后果进行分页显示
应用了==bootstrap table==插件,使得对查问后果分页,当返回后果数量较多时,每页返回五条新闻信息
==bootstrap table==是十分不便好用的前端表格分页插件,使用者只须要提供数据源就能实现十分完满的分页成果,其分页形式能够分成客户端分页和服务端分页,其接管的数据源都是json数据格式。服务端分页在我的项目中利用得十分的宽泛,但有时也须要应用客户端分页来放慢分页速度,放慢分页浏览效率。
2)对查问后果按某个字段进行排序
应用了==bootstrap table==插件,能够抉择字段,对返回后果依照选定字段进行升序或降序排序,这里暂且选定了title和publish_date字段
3)对多个查问条件进行复合查问
对用户提供了多个查问字段的输入框,用户能够进行多个查问条件的复合查问
三、试验中遇到的问题以及须要留神的点
- 在进行replace删除一些无关字符时,需先对字符串判断是否为空,若对空的字符串进行replace操作,将会报错
- 在进行爬虫时,对爬取的url曾经判断其是否在数据库中存在,再进行爬取url,但运行代码时依然呈现数据库url反复的报错,最终发现可能是电脑的起因,雷同的代码在助教电脑上并没有呈现这个问题,所以代码的逻辑是没有问题的,就疏忽了这个谬误
- 在编写前后端时,发现前端网页能够间接利用js库,通过利用网页版的js库,从而代替了下载插件,这一点使得调用插件编写前端更加不便了
- 因为设计表格时,将publish_date字段设置为日期的类型,从而导致输入到前端的表格中或是制作工夫热度剖析图都较为不不便,所以在提取工夫字段时,应用cast(date_format(publish_date,'%Y-%m-%d') as char)办法,将其先转换成年-月-日的格局,在转换为字符串类型,传给前端
- 有时候在批改前端代码之后,未从新运行命令行node bin/www,间接进行页面的渲染查看,会发现页面未更新,须要进行之前的运行,从新执行命令行node bin/www,页面才会进行更新
- 最开始尝试过将内容以表格的模式返回前端,但因为内容字段货色过多,使得表格的格局不难看,最终放弃将内容或摘要字段返回前端的表格中
四、总结与感想
- 通过这次试验,我理解了如何通过nodejs进行网页数据的爬取以及构建前后端,领会到了应用nodejs中轻量级的库的不便和容易
- 通过对网页构造进行剖析,我理解了如何应用cheerio中的选择器进行页面信息的提取
- 通过对爬得的字段进行正则表达式解决,理解了正则表达式的一些根本规定
- 通过前后端的设计,理解了如何在前端进行表格分页和绘制图标,更晓得了如何进行网页格局的调整,从而对于前端网页的格局有了加深的了解,对前后端之间的交互有了加深的了解