共计 27440 个字符,预计需要花费 69 分钟才能阅读完成。
@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 url
function 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)) return
else{ // 否则依据网址解析网页,取得所需内容
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_name
else {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)) return
else{ // 否则依据网址解析网页,取得所需内容
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_name
else{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_name
else {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)) return
else{ // 否则依据网址解析网页,取得所需内容
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_name
else {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 = 0
else fetch.partici = eval(partici_format)
探讨数:
获取为“num ml5”的 class 类,从而取得探讨数的内容
var partici_format = "$('.num.ml5').text()"
// 存储评论数量,若不存在,将其设置为空
if (comment_format == "") fetch.comment = 0
else 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 语句和参数,返回 callback
var 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 语句,返回 callback
var 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 = query
exports.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 = 0
rule.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 中的选择器进行页面信息的提取
- 通过对爬得的字段进行正则表达式解决,理解了正则表达式的一些根本规定
- 通过前后端的设计,理解了如何在前端进行表格分页和绘制图标,更晓得了如何进行网页格局的调整,从而对于前端网页的格局有了加深的了解,对前后端之间的交互有了加深的了解