nodeJS爬虫实例
项目概述
要求:选取3-5个新闻网站,1.对网站下每篇内容的作者,日期,来源,标题等结构化数据进行爬取,2.数据保存在数据库中,3.建立网站展示爬取数据,要求实现对爬取内容的分项搜索、以及所查关键词的时间热度分析。4.爬虫和网站后端均用node.js实现。
前后端设计
该网站前后端设计主要考量了以下几点:
1)组成部分:网站由登录页,查询页面,以及时间热度分析页面组成
2)功能:用户登录注册,词云,对内容或标题的搜索,对关键词的时间热度分析
3)数据可视化:使用echarts实现词云,实现折线图热度分析
4)后端数据来源:爬取了大约2000条新闻,来源为参考消息网,光明网,以及中国新闻网,数据存储于mysql中。
开始前的准备
一、新建项目文件夹
首先用express框架新建项目文件夹
1.安装node.js,当前镜像不是淘宝镜像的切换镜像: npm config set registry https://registry.npm.taobao.org
2.安装express:npm install express –save
3.安装express-generator生成器:npm i express-generator -g
4.输入express –view=ejs project 之后系统会新建project文件夹,文件夹中有如下项:
bin:存放启动脚本文件 bin/www:启动脚本文件,可修改端口号,等功能。 public:存放图片,css,js等静态文件 routes:存放路由模块文件 views:存放视图文件,使用的ejs模板引擎 app.js:入口文件,重要的配置文件 package.json:工程信息和安装依赖文件
二、安装mysql数据库
1.安装mysql 详细安装mysql教程: https://zhuanlan.zhihu.com/p/46905335?ivk_sa=1024320u
2.配置mysql环境变量 将mysql.exe所在路径放到全局变量Path下即可
3.安装数据库依赖项:
npm install
npm install mysql -save
4.当前文件夹打开PowerShell,输入:mysql -uroot -p回车;之后输入刚刚安装mysql数据库时的密码再次回车
5.创建表fetches,用于存放爬取的数据 复制以下粘贴到PowerShell 回车
create database crawl;
use crawl;
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;
6.创建表user存放用户的账号及密码 复制以下粘贴到PowerShell 回车
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;
7.创建表user_action存放用户操作日志 复制以下粘贴到PowerShell 回车
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 ( 1000 ) NOT NULL,
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8;
三、为网页绑定mysql数据库
1.创建dao文件夹,内部创建四个js文件
logDAO.JS用于存放用户操作日志
//该文件其实并没用到,最后选择使用express中间件 morgan记录日志(app.js中) var mysql = require('mysql'); var mysqlConf = require('../conf/mysqlConf'); var pool = mysql.createPool(mysqlConf.mysql); // 使用了连接池,重复使用数据库连接,而不必每执行一次CRUD操作就获取、释放一次数据库连接,从而提高了对数据库操作的性能。 // 记录用户操作 module.exports = { userlog :function (useraction, callback) { pool.query('insert into user_action(username,request_time,request_method,request_url,status,remote_addr) values(?, ?,?,?,?,?)', useraction, function (error, result) { if (error) throw error; callback(result.affectedRows > 0); }); }, };
newsDAO.js用于新闻查询
var mysql = require('mysql'); var mysqlConf = require('../conf/mysqlConf'); var userSqlMap = require('./userSqlMap'); var pool = mysql.createPool(mysqlConf.mysql); // 使用了连接池,重复使用数据库连接,而不必每执行一次CRUD操作就获取、释放一次数据库连接,从而提高了对数据库操作的性能。 module.exports = { add: function (user, callback) { pool.query(userSqlMap.add, [user.username, user.password], function (error, result) { if (error) throw error; callback(result.affectedRows > 0); }); }, getByUsername: function (username, callback) { pool.query(userSqlMap.getByUsername, [username], function (error, result) { if (error) throw error; callback(result); }); }, };
userSqlMap.js
var userSqlMap = {
add: 'insert into user(username, password) values(?, ?)',//注册时用
getByUsername: 'select username, password from user where username = ?'//登陆时用
};
module.exports = userSqlMap;
2.在project文件夹下新建conf文件夹—>新建mysqlConf.js
module.exports = {
mysql: {
host: 'localhost',//本地数据库,127.0.0.1也可
user: 'root',//用户
password: 'root',//密码
database:'crawl',//数据库名
// 最大连接数,默认为10
connectionLimit: 10
}
};
3.在project文件夹下新建mysql.js文件
var mysql = require("mysql");
var pool = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: 'root',
database: 'crawl'
});
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;
爬取数据
一、编写爬虫代码
在project文件夹下新建crawle2.js(例子,名字不固定),写入针对目标网站的爬取代码,下面以‘参考消息网’的爬取代码为例。
我采取的爬取方式是,首先找到根网址,也就是一个新闻网站的首页,遍历首页中所有的a标签,如果该a标签中的网址符合子新闻网址的格式,则进入爬取具体内容,否则跳过,若该网址已经爬取过,则不再存入数据库中。并且设置定时,每天自动爬取两次。
var mysql = require('./mysql.js') //导入所需模块
var myRequest = require('request')
var myCheerio = require('cheerio')
var myIconv = require('iconv-lite')
require('date-utils');
var schedule = require('node-schedule');
var source_name = "参考消息网"; //声明来源,编码,以及根网址
var myEncoding = "utf-8";
var seedURL = 'http://www.cankaoxiaoxi.com/';
之后我们要对目标网站的具体内容进行分析,找到元素对应的标签,使用jquery进行绑定,比如在参考消息网的页面中,文章关键词对应的是keywords标签,题目标签是title,日期的标签id是pubtime_baidu等等。
var seedURL_format = "$('a')";//定义具体哪些元素可以读取
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var date_format = "$('#pubtime_baidu').text()";
var author_format = "$('#editor_baidu').text()";
var content_format = "$('.articleText').text()";
var desc_format = " $('meta[name=\"description\"]').eq(0).attr(\"content\")";
var source_format = "$('#source_baidu').text()";
//使用正则表达式来筛选读取到的网址是否正确
var url_reg = /\/([a-zA-Z]{7})\/(\d{8})\/(\d{7}).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模块异步fetch url
function request(url, callback) {
var options = {
url: url,
encoding: null,
//proxy: 'http://x.x.x.x:8080',
headers: headers,
timeout: 10000 //
}
myRequest(options, callback)
}
var rule = new schedule.RecurrenceRule();
var times = [0, 12]; //每天2次自动执行
var times2 = 5; //定义在第几分钟执行
rule.hour = times;
rule.minute = times2;
schedule.scheduleJob(rule, function() {
seedget();
});
seedget();
function seedget() {
request(seedURL, function(err, res, body) { //读取种子页面
// try {
//用iconv转换编码
var html = myIconv.decode(body, myEncoding);
//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