1. 网络爬虫
网络爬虫是一种自动获取网页内容的程序,功能如下
- 发出HTTP请求获取指定URL中的内容
- 使用jQuery的语法操作网页元素,提取需要的元素
- 将数据保存到mysql数据库中
- 建立web服务器显示这些数据
- 使用计划任务自动执行更新任务
- 布署项目到阿里云中并配置反向代理
2. 预备知识
2.1 request
一个简单的HTTP请求工具,用来获取网页内容
var request = require('request'); var iconv = require('iconv-lite'); request({url: 'http://top.baidu.com/category?c=10&fr=topindex' , encoding: null},function(err,response,body){ if(err) console.error(err); body = iconv.decode(body, 'gbk').toString(); var regex = /<a href=".\/buzz\?b=\d+&c=\d+">.+<\/a>/g; console.log(body.match(regex)); })
2.2 cheerio
在服务器端实现了jQuery中的DOM操作API
var cheerio = require('cheerio'); var $ = cheerio.load('<ul> <li><a href="./buzz?b=353&c=10">玄幻奇幻</a></li> \<li><a href="./buzz?b=354&c=10">爱情</a></li></ul>'); $('ul li a').each(function () { var $me = $(this); var item = { name: $me.text().trim(), url: $me.attr('href').slice(2) } var result = item.url.match(/buzz\?b=(\d+)/); if (Array.isArray(result)) { item.id = result[1]; } console.log(item); });
2.3 cron
用来周期性的执行某种任务或等待处理某些事件的一个守护进程
符号 | 含义 |
---|---|
星号(*) | 代表所有可能的值 |
逗号(,) | 可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9” |
中杠(-) | 可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6” |
正斜线(/) | 可以用正斜线指定时间的间隔频率,*/10,如果用在minute字段,表示每十分钟执行一次 |
var cronJob = require('cron').CronJob; var job1 = new cronJob("* * * * * *",function(){ console.log('每秒'); }); job1.start();
2.4 debug
根据环境变量的有选择向控制台输出调试信息
var debug = require('debug')('crawler:main'); //windows set DEBUG=crawler:* //linux export DEBUG=crawler:* debug('welcome to zhufengpeixun');
2.5 child_process
child_process即子进程可以创建一个系统子进程并执行shell命令
spawn语法
child_process.spawn(cmd, args=[], [options])
示例
var url = require('url'); var fs = require('fs'); var DOWNLOAD_DIR = './'; var spawn = require('child_process').spawn; // 使用curl下载文件的函数 var download_file_curl = function(file_url) { // 提取文件名 var file_name = url.parse(file_url).pathname.split('/').pop(); // 创建一个可写流的实例 var file = fs.createWriteStream(DOWNLOAD_DIR + file_name); // 使用spawn运行curl var curl = spawn('curl', [file_url]); // 为spawn实例添加了一个data事件 curl.stdout.on('data', function(data) { file.write(data); }); // 添加一个end监听器来关闭文件流 curl.stdout.on('end', function(data) { file.end(); console.log(file_name + ' downloaded to ' + DOWNLOAD_DIR); }); // 当子进程退出时,检查是否有错误,同时关闭文件流 curl.on('exit', function(code) { if (code != 0) { console.log('Failed: ' + code); } }); }; download_file_curl('http://pic.baidu.jpg');
exec语法
child_process.exec(cmd, [options], callback)
示例
var url = require('url'); var fs = require('fs'); var DOWNLOAD_DIR = './'; var exec = require('child_process').exec; var download_file_curl = function(file_url) { // 提取文件名 var file_name = url.parse(file_url).pathname.split('/').pop(); // 使用exec执行curl命令 var child = exec('curl '+file_url+' -o ' +DOWNLOAD_DIR+file_name, function(err, stdout, stderr) { if (err) throw err; else console.log(file_name + ' downloaded to ' + DOWNLOAD_DIR); }); }; download_file_curl('http://pic.baidu.jpg');
2.6 async
async是一个流程控制库,为我们带来了丰富的嵌套解决方案
2.6.1 串行执行
串行执行一个函数数组中的每个函数 series(tasks,callback);
async.series([function(callback){ callback(null, "tv is over"); },function(callback){ callback(null, 'homework is down'); }],function(err, results) { console.log(results); });
2.6.2 并行执行
parallel函数是并行执行多个函数,每个函数都是立即执行,不需要等待其它函数先执行 parallel(tasks, [callback])
console.time('start'); async.parallel([ function (callback) { setTimeout(function () { callback(null, 'one'); },2000); }, function (callback) { setTimeout(function () { callback(null, 'two'); },3000); } ], function (err, results) { console.log(results); console.timeEnd('start'); });
2.6.3 waterfall(瀑布)
waterfall和series函数有很多相似之处,都是按顺序依次执行一组函数,不同之处是waterfall每个函数产生的值,都将传给下一个函数 waterfall(tasks, [callback])
async.waterfall([function(callback){ callback(null, "水"); },function(data,callback){ callback(null, data+'+咖啡'); },function(data,callback){ callback(null, data+'+牛奶'); }],function(err, results) { console.log(results);//水+咖啡+牛奶 });
2.6.4 自动依赖
用来处理有依赖关系的多个任务的执行 auto(tasks, [callback]);
async.auto({ getWater: function(callback){ callback(null, 'Water'); }, getFlour: function(callback){ callback(null, 'Flour'); }, mixFlour: ['getWater', 'getFlour', function(callback, results){ callback(null, results['getWater']+',' +results['getFlour']+','+'mixFlour'); }], steam: ['mixFlour', function(callback, results){ callback(null, results['mixFlour']+',steam'); }] }, function(err, results) { console.log('err = ', err); console.log('results = ', results); });
注意callback, results的顺序在不同的
async
版本中不一样
2.6.5 迭代多个异步任务
所有的任务都迭代完成后才执行后续任务
var arr = [{name:'zfpx1'},{name:'zfpx2'}]; async.forEach(arr, function(item, callback) { console.log('1.1 enter: ' + item.name); setTimeout(function(){ console.log('1.1 handle: ' + item.name); callback(null); }, 1000); }, function(err,result) { console.log('1.1 err: ' + err); });
3.实现爬虫
3.1 抓取内容
3.1.1 读取内容
tasks/read.js
var request = require('request'); var cheerio = require('cheerio'); var debug = require('debug')('crawler:read'); var iconv = require('iconv-lite'); module.exports = function (url, callback) { var items = []; debug('读取电影列表'); request({url:url,encoding:null},function(err,response,body){ body = iconv.decode(body,'gbk'); var $ = cheerio.load(body); $('.keyword .list-title').each(function(){ var that = $(this); var item = { name:that.text().trim(), url:that.attr('href') } items.push(item); debug('读取电影 ',item); }); callback(null,items); debug('读取电影列表完毕'); }); }
3.1.2 数据库操作
model/index.js
var mongoose = require('mongoose'); mongoose.connect('mongodb://123.57.143.189/zhufengcrawl'); exports.Movie = mongoose.model('Movie',new mongoose.Schema({ name:String, url:String }));
3.1.3 保存内容
tasks/save.js
var async = require('async'); var Movie = require('../model').Movie; var debug = require('debug')('crawler:save'); module.exports = function(items,callback){ async.series([ function(callback){ debug('清空电影列表'); Movie.remove({},callback); }, function(callback){ debug('保存电影列表'); async.forEach(items, function (item, next) { debug('保存电影 ',item); Movie.create(item,next); }, callback); } ],function(err,result){ debug('保存电影完毕'); callback(); }) }
3.1.4 入口文件
tasks/main.js
var read = require('./read'); var save = require('./save'); var async = require('async'); var debug = require('debug')('crawler:main'); debug('开始计划任务'); var movies = []; async.series([ function(done){ read('http://top.baidu.com/buzz?b=26&c=1&fr=topcategory_c1',function(err,items){ movies = items; done(); }); }, function(done){ save(movies,done); } ],function(err,result){ debug('结束计划任务'); process.exit(0); })
3.1.5 计划任务
app.js
var spawn = require('child_process').spawn; var cronJob = require('cron').CronJob; var path = require('path'); var job = new cronJob('* * * * * ',function(){ var update = spawn(process.execPath,[path.join(__dirname,'tasks/main.js')]); update.stdout.pipe(process.stdout); update.stderr.pipe(process.stderr); update.on('close',function(code){ console.log(code); }); update.on('error',function(code){ console.log(code); }); }); job.start();
3.2 页面展示
3.2.1 启动应用
app.js
var express = require('express'); var app = express(); app.set('view engine', 'jade'); app.set('views', 'views'); var Movie = require('./model').Movie; app.get('/', function (req, res) { Movie.find({}, function (err, movies) { res.render('index', { movies: movies }); }); }); app.listen(9090); var spawn = require('child_process').spawn; var cronJob = require('cron').CronJob; var path = require('path'); var job = new cronJob('* * * * * ',function(){ var update = spawn(process.execPath,[path.join(__dirname,'tasks/main.js')]); update.stdout.pipe(process.stdout); update.stderr.pipe(process.stderr); update.on('close',function(code){ console.log(code); }); update.on('error',function(code){ console.log(code); }); }); job.start();
3.2.2 编写模板
doctype html html head title 电影风云榜 link(rel="stylesheet",href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css") body .container .panel.panel-primary .panel-heading.text-center 电影风云榜 .panel-body ul.list-group each movie in movies li.list-group-item a(href="#{movie.url}")=movie.name
4. 资源
珠峰培训node培训之MongoDB+Express+Angular+Node+Bootstrap+Linux开发的爬虫