JS爬虫

1. 网络爬虫

网络爬虫是一种自动获取网页内容的程序,功能如下

  1. 发出HTTP请求获取指定URL中的内容
  2. 使用jQuery的语法操作网页元素,提取需要的元素
  3. 将数据保存到mysql数据库中
  4. 建立web服务器显示这些数据
  5. 使用计划任务自动执行更新任务
  6. 布署项目到阿里云中并配置反向代理

2. 预备知识

2.1 request

一个简单的HTTP请求工具,用来获取网页内容

request

1
2
3
4
5
6
7
8
9
10
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

cheerio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

用来周期性的执行某种任务或等待处理某些事件的一个守护进程

cron

符号 含义
星号(*) 代表所有可能的值
逗号(,) 可以用逗号隔开的值指定一个列表范围,例如,“1,2,5,7,8,9”
中杠(-) 可以用整数之间的中杠表示一个整数范围,例如“2-6”表示“2,3,4,5,6”
正斜线(/) 可以用正斜线指定时间的间隔频率,*/10,如果用在minute字段,表示每十分钟执行一次
1
2
3
4
5
var cronJob = require('cron').CronJob;
var job1 = new cronJob("* * * * * *",function(){
console.log('每秒');
});
job1.start();

2.4 debug

根据环境变量的有选择向控制台输出调试信息

debug

1
2
3
4
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语法

1
child_process.spawn(cmd, args=[], [options])

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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语法

1
child_process.exec(cmd, [options], callback)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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是一个流程控制库,为我们带来了丰富的嵌套解决方案

async

2.6.1 串行执行

串行执行一个函数数组中的每个函数
series(tasks,callback);

1
2
3
4
5
6
7
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])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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])

1
2
3
4
5
6
7
8
9
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]);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 迭代多个异步任务

所有的任务都迭代完成后才执行后续任务

1
2
3
4
5
6
7
8
9
10
var arr = [{name:'hwz1'},{name:'hwz2'}];
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 编写模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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. 资源

项目源码

文章目录
  1. 1. 1. 网络爬虫
  2. 2. 2. 预备知识
    1. 2.1. 2.1 request
    2. 2.2. 2.2 cheerio
    3. 2.3. 2.3 cron
    4. 2.4. 2.4 debug
    5. 2.5. 2.5 child_process
    6. 2.6. 2.6 async
      1. 2.6.1. 2.6.1 串行执行
      2. 2.6.2. 2.6.2 并行执行
      3. 2.6.3. 2.6.3 waterfall(瀑布)
      4. 2.6.4. 2.6.4 自动依赖
      5. 2.6.5. 2.6.5 迭代多个异步任务
  3. 3. 3.实现爬虫
    1. 3.1. 3.1 抓取内容
      1. 3.1.1. 3.1.1 读取内容
      2. 3.1.2. 3.1.2 数据库操作
      3. 3.1.3. 3.1.3 保存内容
      4. 3.1.4. 3.1.4 入口文件
      5. 3.1.5. 3.1.5 计划任务
    2. 3.2. 3.2 页面展示
      1. 3.2.1. 3.2.1 启动应用
      2. 3.2.2. 3.2.2 编写模板
  4. 4. 4. 资源
|