在这里放一下代码。

有点奇怪,在markdown中开启代码区段之后粘贴大段代码进去,缩进和换行都显示不正常,所以下面的代码的空白行我加了个空格这样似乎显示正常一些。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
var fs = require('fs');
var cheerio = require('cheerio');
var async = require('async');
var superagent = require('superagent');
var mongodb = require('./db');
var path = require('path');
var http = require('http');
var nhentaiLink = "http://nhentai.net/g/";
var imageLink = "http://i.nhentai.net/galleries/";
var startNum = 500;
var endNum = 500;
var urls = [];
var galleryUrls = [];
makeGalleryUrls(startNum,endNum);
setTimeout(function(){
fetchData(1,galleryUrls.length);
//console.log(galleryUrls);
},2000);
function fetchInfoDB(){
makeUrls(startNum,endNum);
mongodb.open(function(err,db){
if(err){console.error(err);}
console.info('mongodb connected');
async.mapLimit(urls,10,function(url,callback){
ClawInfo(url,db,callback);
},function(err,result){
if(err){console.error(err);}
});
});
}
function fetchData(i,max){
if(i > max){mongodb.close(); return null;}
var imageUrls = [];
//console.log(codea);
//console.log(galleryUrls);
//console.log(i);
imageUrls.push(galleryUrls[i-1].GallleryUrl+'cover.'+galleryUrls[i-1].Format);
for(var j=1;j<=galleryUrls[i-1].Page;j++){
imageUrls.push(galleryUrls[i-1].GallleryUrl+j+'.'+galleryUrls[i-1].Format);
}
//console.log(imageUrls);
console.log('开始抓取第'+galleryUrls[i-1].Code+'项:'+galleryUrls[i-1].GallleryUrl+',共'+galleryUrls[i-1].Page+'页。');
var dir = galleryUrls[i-1].Title?'data/'+galleryUrls[i-1].Code+'. '+galleryUrls[i-1].Title:'data/'+galleryUrls[i-1].Code+'. '+galleryUrls[i-1].EngTitle;
fs.mkdir(dir,function(err){
if(err){
//console.log('error occured.');
dir = 'data/'+galleryUrls[i-1].Code;
fs.mkdirSync(dir);
}
async.mapLimit(imageUrls,4,function(imageUrl,callback){
ClawImage(imageUrl,dir,callback);
},function(err,result){
if(err){console.error(err);}
console.log('抓取第'+galleryUrls[i-1].Code+'项完成。');
i = i+1;
fetchData(i,max);
});
});
}
function ClawImage(url,dir,callback){
http.get(url,function(res){
res.setEncoding('binary');
var imagedata = '';
res.on('data',function(data){imagedata+=data}).on('end',function(){
var imageName = dir+'/'+url.match(/[\w]+.jpg|[\w]+.png|[\w]+.gif/)[0];
fs.writeFileSync(path.join(__dirname,imageName),imagedata,'binary');
console.log('下载'+url+'完成。');
callback(null);
});
});
}
function makeUrls(startNum,endNum){
for(var i=startNum;i<=endNum;i++){
urls.push(nhentaiLink + i +'/');
}
}
function makeGalleryUrls(startNum,endNum){
mongodb.open(function(err,db){
if(err){console.error(err);}
db.collection('items',function(err,collection){
if(err){console.error(err);}
pushGallery(startNum,endNum,collection);
});
});
}
function pushGallery(i,end,collection){
//console.log(i);
if(i>end){return;}
collection.findOne({Code:i},function(err,item){
if(err){console.error(err);}
//console.log(item);
if(!item){pushGallery(++i,end,collection);}
else{
galleryUrls.push({
Code:item.Code,
Title:item.Title,
EngTitle:item.EngTitle,
GallleryCode:item.GallleryCode,
GallleryUrl:imageLink+item.GallleryCode+'/',
Page:item.Page,
Format:item.Format
});
pushGallery(++i,end,collection);
}
})
}
function ClawInfo(url,db,callback){
var clawer = {};
GetInfo(url,clawer,function(clawer){
db.collection('items',function(err,collection){
if(err){mongodb.close();console.error(err);}
collection.insert(clawer,{safe:true},function(err,item){
if(err){return console.error(err);}
console.log('抓取'+url+'完成。');
});
});
});
setTimeout(function(){callback(null)},500);
}
function GetInfo(url,clawer,callback){
superagent.get(url)
.set('User-Agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.94 Safari/537.36')
.end(function(err,sres){
if(sres){
var $ = cheerio.load(sres.text);
clawer.Code = Number(url.match(/\d+/)[0]);
clawer.Title = $('#info h2').text();
clawer.EngTitle = $('#info h1').text();
clawer.Comiket = $('#info h1').text().match(/[Cc][5678]\d/)?$('#info h1').text().match(/[Cc][5678]\d/)[0]:'';
clawer.Parody = [];
clawer.Character = [];
clawer.Tag = [];
clawer.Artist = [];
clawer.Group = [];
$('#info div a').each(function(i,e){
var $e = $(e);
if($e.attr('href').search(/parody/)!= -1){clawer.Parody.push($e.attr('href').match(/[a-z][^\/]+/g)[1]);}
if($e.attr('href').search(/character/)!= -1){clawer.Character.push($e.attr('href').match(/[a-z][^\/]+/g)[1]);}
if($e.attr('href').search(/tagged/)!= -1){clawer.Tag.push($e.attr('href').match(/[a-z][^\/]+/g)[1]);}
if($e.attr('href').search(/artist/)!= -1){clawer.Artist.push($e.attr('href').match(/[a-z][^\/]+/g)[1]);}
if($e.attr('href').search(/group/)!= -1){clawer.Group.push($e.attr('href').match(/[a-z][^\/]+/g)[1]);}
});
clawer.Language = $('#info div.buttons').prev().prev().prev().text().match(/[A-Z]\w+/g)?$('#info div.buttons').prev().prev().prev().text().match(/[A-Z]\w+/g)[1]:'';
clawer.Page = $('#info div.buttons').prev().prev().text().match(/\d+/)?Number($('#info div.buttons').prev().prev().text().match(/\d+/)[0]):'';
clawer.GallleryCode = $('#cover img').attr('src')?Number($('#cover img').attr('src').match(/\d+/)[0]):'';
clawer.Format = $('.gallerythumb').eq(1).children('div.spinner').attr('data-src')?$('.gallerythumb').eq(1).children('div.spinner').attr('data-src').match(/\w+$/)[0]:'';
clawer.Favorite = $('div.buttons span.nobold').text()?Number($('div.buttons span.nobold').text().match(/\d+/)[0]):'';
}
callback(clawer);
});
}

db.js

1
2
3
4
5
var Db = require('mongodb').Db,
Connection = require('mongodb').Connection,
Server = require('mongodb').Server;
module.exports = new Db('nhentai', new Server('localhost', 27017),
{safe: true});

主要功能是抓取nhentai(咳咳好孩子不要点)上的所有本子存入本地,同时将本子信息存入数据库以便查询和归档。

用法:只需修改startNum和endNum的值,即从第几项抓到第几项。nhentai上现在约有不到13万项,数据库抓完了,本子过于庞大(全抓TB级)还没想好存放在哪里。

主函数:fetchData()与fetchInfoDB(),前者抓取数据并保存在当前目录data文件夹下,后者抓取信息存储在mongodb数据库中。需要先抓取信息,再抓取数据。

现在一想加个命令行界面比较好,当时没有弄。

测试爬虫的时候遇到的一些情况:

抓取信息因为数据量很小,又很快,所以没有什么大情况。所需要的大概只是增加容错性:遇到错误(要抓的数据缺失,存在失效链接)要提供解决方法,大批量任务正在进行,程序不能因此卡住。

抓取数据时,抓取的是大量成组图片,每组保存到某文件夹下。当初的想法时任务队列中异步抓两组–每组异步抓五张图片,使用async库,随后发现无论是map、mapLimit、mapSeries都无法保证顺序。无法保证顺序的后果就是当抓取某组时因错停止,调试完就很难续传了。所以用递归变成了每次抓1组,但是顺序是死的。

说到异步处理,很多时候也是要注意一下处理方式……

比如,许多任务想按顺序完成,使用for:

1
2
3
4
for (){
//异步函数+callback……
fs.mkdirdir,callback(){})
}

那么一瞬间for就执行完毕,瞬间注册了大量的异步事件,并没有等上一个完成再进行下一个。

这时候可以把异步函数换成对应的同步函数,否则可以使用递归:

1
2
3
4
5
6
(function makedir(i){
if(i === 任务总数) return;
fs.mkdir(dir,callback(){
makedir(++i);
})
})(1);

这样可以保证按顺序执行,缺点是不好看。app.js中就是使用递归来写的。

明天家里的网似乎能升级到100M带宽,如果电信真的不坑的话好想抓抓试试呢:)