新闻网站爬虫及结果查询
(一)nodejs+express搭建服务器
1.安装node.js
Node.js 安装包及源码下载地址为:https://nodejs.org/en/download/
2.全局安装express-generator
npm install express --save -g
npm install express-generator --save -g
3.初始化项目
express project1 //构建这个应用的目录结构
cd project1
npm install //安装依赖
npm start //启动项目
项目目录结构:
bin: 启动目录 里面包含了一个启动文件 www ,默认监听端口是3000
node_modules: 所有安装的依赖模块都在这个文件夹里面
public: 所有的前端静态资源 css image js
routes: 放的是路由文件
路由主要定义 url 和资源的映射关系 ( 一一对应关系 )
主要用来接收前端发送的请求响应数据给前端
views: 主要放置 ejs 后端模板文件
app.js: 入口文件(主文件) 总路由 (其他的路由要由它来分配)
package.json: 描述文件,最重要的是依赖的模板列表 dependencies
依赖列表里面的所有模板可以通过 npm i 一次性全部安装
4.添加依赖
在package.json文件里添加需要的依赖,如:request,Cheerio,pg等,再次安装依赖。
(二)连接数据库
1.连接池创建
在代码中引入pg模块,并编写数据库配置:
var pg = require('pg');
var config = {
user:"postgres",
database:"spider",
password:"postgres",
port:5432,
// 扩展属性
max:20, // 连接池最大连接数
idleTimeoutMillis:3000, // 连接最大空闲时间 3s
}
var pool = new pg.Pool(config);
2.编写操作数据库的函数
有参数和无参数,该函数实现连接数据库并执行SQL语句
var query = function(sql, sqlparam, callback) {
pool.connect(function(err, conn, done) {
if (err) {
console.log(err)
callback(err, null, null);
} else {
conn.query(sql, sqlparam, function(err, result) {
//conn.release(); //释放连接
done();
callback(err, result); //事件驱动回调
});
}
});
};
var query_noparam = function(sql, callback) {
pool.connect(function(err, conn, done) {
if (err) {
callback(err, null, null);
} else {
conn.query(sql, function(err, result) {
conn.release(); //释放连接
callback(err, result); //事件驱动回调
});
}
});
};
3.新闻表和关键词表的设计
新闻表:
id_news是每个新闻的id,设为UNIQUE属性唯一标识新闻数据
url是新闻链接
source_name是新闻的来源网站名
source_encoding是新闻的编码方式
title是新闻标题
publish_date是新闻的发表日期
content是新闻内容
关键词表:
id_word是关键词id,唯一标识该关键词
id_news是该关键词所属的新闻id,用于两表的连接
word是关键词
CREATE TABLE news (
id_news serial UNIQUE, //避免重复
url text DEFAULT NULL UNIQUE,
source_name varchar(50) DEFAULT NULL,
source_encoding varchar(45) DEFAULT NULL,
title varchar(100) DEFAULT NULL,
publish_date date DEFAULT CURRENT_TIMESTAMP,
content text,
PRIMARY KEY (id_news)
);
CREATE TABLE splitwords (
id_word serial UNIQUE,
id_news int, //两表的连接
word varchar(50) DEFAULT NULL
);
(三)爬虫
爬取了三个网站:雪球网、中国财经网、网易新闻网。
每一个爬虫的大致步骤都是:
1、读取种子页面
2、分析出种子页面里的所有新闻链接,并爬取内容,解析成结构化数据
3、将结构化数据存储到数据库中
下面以中国财经网为例
1.读取种子页面
引入需要的包
var pgsql = require('../pg.js');
var myIconv = require('iconv-lite');
var myRequest = require('request');
var myCheerio = require('cheerio');
构造模仿浏览器的request是每个爬虫都包含的部分,定义来header来防止网站屏蔽,并且定义了request函数,调用该函数能够访问指定的url并且能够设置回调函数来处理得到的html页面。
//防止网站屏蔽我们的爬虫
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'
}
function request(url, callback) {//request module fetching url
var options = {
url: url,
encoding: null,
headers: headers,
timeout: 10000
}
myRequest(options, callback)
}
爬取种子页面,并且获取其中每一个新闻对应的链接,其对应的代码如下:
其中seedurl_news对应的页面上所有新闻,使用seedurl_news.each()对每一块新闻获取其具体url,并且检查了url是否在数据库中已经存储过了,避免重复插入数据
var seedURL = 'http://www.chinanews.com/finance/';
request(seedURL, function(err, res, body) { //读取种子页面
//用iconv转换编码
var html = myIconv.decode(body, myEncoding);
//console.log(html);
//准备用cheerio解析html
var $ = myCheerio.load(html, { decodeEntities: true });
var seedurl_news;
try {
seedurl_news = $('a');
//console.log(seedurl_news);
} catch (e) { console.log('url列表所处的html块识别出错:' + e) }
seedurl_news.each(function(){
try {
var href = "";
href = $(this).attr('href');
}catch(e) {
console.log('get the seed url err' + e);
}
//if (href.startsWith('//')) href = 'http:' + href;
if (!url_reg.test(href)) {
console.log(href + " not match the url_reg");
return;
}
href = String(href);
if (href.indexOf("//www.chinanews.com/cj") != 0) {
// console.log(href + " not match the right url");
return ;
}
href = 'http:' + href;
console.log("crawler " + href);
// not try to crawl the repeat page
var fetch_url_Sql = 'select url from news where url= $1';
var fetch_url_Sql_Params = [href];
pgsql.query(fetch_url_Sql, fetch_url_Sql_Params, function(err, result) {
if (err) {
console.log(err)
} else { // a new page
if(result) {
console.log("URL " + href + " repeat");
} else {
newsGet(href);
}
}
});
})
});
2.爬取新闻链接内容
在获取了新闻详情页面后,调用newsGet函数,爬取所需要的内容。
爬取的信息包括:新闻标题、内容、来源、发表时间,并对内容、发表时间等内容的格式进行了处理
request(myURL, function(err, res, body) { //读取新闻页面
try {
var html_news = myIconv.decode(body, myEncoding); //用iconv转换编码
//console.log(html_news);
//准备用cheerio解析html_news
var $ = myCheerio.load(html_news, { decodeEntities: true });
myhtml = html_news;
} catch (e) { console.log('读新闻页面并转码出错:' + e);};
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; //编码
if (title_format == "") fetch.title = ""
else fetch.title = eval(title_format); //标题
if (date_format != "") fetch.publish_date = eval(date_format); //$('.article-meta').children().eq(1).text(); //刊登日期
console.log(fetch.publish_date);
fetch.publish_date = fetch.publish_date.split(" ")[0];
console.log('date: ' + fetch.publish_date);
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 (content_format == "") fetch.content = "";
else fetch.content = eval(content_format).replace('\n', "").replace(" ", ""); //内容,是否要去掉作者信息自行决定
//console.log(fetch.content);
}
});
3.存储结构化数据
执行SQL语句把新闻数据插入news表
// save the result in postgresql
var fetchAddSql = 'INSERT INTO news (url, source_name, source_encoding, title, publish_date, content)'
+ 'VALUES ($1, $2,$3,$4,$5, $6)';
var fetchAddSql_Params = [fetch.url, fetch.source, fetch.source_encoding,
fetch.title, fetch.publish_date, fetch.content
];
//执行sql,数据库中fetch表里的url属性是unique的,不会把重复的url内容写入数据库
pgsql.query(fetchAddSql, fetchAddSql_Params, function(err, result) {
if(err) {
console.log(err);
}
});
4.关键词表数据的插入
利用nodejieba.extract()方法,提取新闻内容中的关键词,并剔除停用词。
var fetchAddSql = 'select id_news, content from news';
pgsql.query_noparam(fetchAddSql, function(err, result) {
for(var i = 0; i < result.rows.length; i++) {
var words = nodejieba.extract(result.rows[i].content,30);
var id_news = result.rows[i].id_news;
var insert_word_Sql;
var insert_word_Params;
for(var j = 0; j < words.length; j++) {
if(!stop_words.has(words[j].word)) {
insert_word_Sql = 'insert into splitwords(id_news, word)' + ' VALUES ($1, $2)';
insert_word_Params = [id_news, words[j].word];
// console.log(words[j]);
pgsql.query(insert_word_Sql, insert_word_Params, function(err, result) {
if(err) {
console.log(err);
}
});
}
}
}
});
数据库显示:
news表:
关键词表:
(四)结果查询网站的搭建
1.用pgsql查询已爬取的数据(按时间顺序)
构建sql查询语句并且调用pgsql.query_noparam进行查询即可,且对标题、关键词、源网站的查询都是按照时间降序排列,最新的新闻排在旧的新闻前面呈现。
router.get('/get_title', function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' });
// console.log(req.query.title);
var fetchSql = "select url,source_name,title,publish_date " +
"from news where title like '%" + req.query.title + "%' Order By publish_date desc" ;
pgsql.query_noparam(fetchSql, function(err, result, fields) {
// console.log(result.rows);
res.end(JSON.stringify(result.rows));
// console.log(res);
});
});
2、用网页发送请求到后端查询
前端采用文本框样式,获取输入的查询词,并提交给后端
每个文本框及按钮绑定不同的id,便于访问不同的路由
<title>新闻信息检索</title>
<h1>输入新闻标题并搜索:</h1>
<input type="text" name="title" id="title_text">
<input type="button" value="搜索" id="title_button">
<h1>输入源网站名并搜索:</h1>
<input type="text" name="source_name" id="source_name_text">
<input type="button" value="搜索" id="source_name_button">
<h1>输入新闻关键词并搜索:</h1>
<input type="text" name="keyword" id="keyword_text">
<input type="button" value="搜索" id="keyword_button">
网页显示:
3.用表格显示查询结果
采用现有的css框架bootstrapTable呈现查询结果
$('#title_table').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:'publish_date',
title:'publish_date',
sortable : true
}]
})
表格显示:
4.关键词的时间热度分析
从数据库的新闻信息中统计每天包括该关键词的新闻数量,按照时间顺序降序排列
router.get('/order_keyword', function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' });
// console.log(req.query.keyword);
var fetchSql = "SELECT publish_date,count(*) as Num FROM news WHERE content like '%" + req.query.keyword + "%' group by publish_date order by publish_date desc;";
pgsql.query_noparam(fetchSql, function(err, result, fields) {
// console.log(result.rows);
res.end(JSON.stringify(result));
// console.log(res);
});
});
结果展示:
图表形式更加直观:
引入echarts.js库,绘制动态柱状图
var myChart = echarts.init(document.getElementById('chart'));
// 指定图表的配置项和数据
var option = {
title: {
text: '关键词时间热度图'
},
tooltip: {},
legend: {
data:['新闻数量']
},
xAxis: {
data: dates
},
yAxis: {},
series: [{
name: '新闻数量',
type: 'bar',
data: nums
}]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
});
5.网站展示
为了美化前端界面,加入了背景图片以及鼠标跟随特效