想说 node爱你不容易啊

之前写的图片下载器,使用python和C#,今儿个使用nodejs重写,使用cheerio库,和jq比较像

代码是没问题,我出问题了…

  • http.request没有同步版本,导致一开始就回调…
  • http.request参数要么是string,要么是option,必须要分host,path,但是这个下载器必须带User-Agent的header
  • request类库option可以使用 url|uri,但是回调函数,callback(err,res,body),body是utf8编码,gbk中文乱码,而res是request类库处理之后传回的,此时给res加上on data,end事件接收数据无效,使用pipe也无效
  • 获取页面的srcs,判断是否有下一页,同步代码一个while搞定,node使用async.whilst也可以,之前使用if + 递归搞定,但是内存占用更多,不好的方法,使用whilst代替
  • 下载完回调,log一下耗时,本想使用async.each,但是这货不提供index呀,自己实现index++,可能不能保证顺序呀,使用[].forEach,自己实现done函数,判断finished看是否全部完成,呢吗async内部也是这么写的好么!!!我就想实现个回调而已啊!!!!
  • 下载文件,res.pipe(fs-stream),此时要给fs.WriteStream加上finish事件,要不然,程序不管有没有接受完,就退出了

网络差的时候,程序莫名报错…打断点停不下来…异步么,啊啊啊!!!
python的大概也就90多行

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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
var http = require('http');
var urlFn = require('url');
var pathFn = require('path');
var fs = require('fs');
var util = require('util');

var iconv = require('iconv-lite');
var cheerio = require('cheerio');
var async = require('async');

/*
* configs
*/
var page_encoding = 'gbk'; // 'gbk'
var debuging = true; //use example url
var example_url = '----------------url------------------';
var image_dir = 'iamge';

function get_title($) { // 文件夹名称
return $("title").text();
}

function get_page_srcs($) { // 获取当前page imgs src
var srcs = $('.img_box img').map(function() {
return $(this).attr('src');
})
return [].slice.call(srcs);
}

function has_next_page($) { // 是否有next page

return $('.pages').eq(1).find('a').last().attr('href') !== '#';
}

function next_page_url($) { // next page url

return $('.pages').eq(1).find('a').last().attr('href');
}
//--------------------------------------------------------------



var url = '';
if (debuging) {
url = example_url;
}
else if (process.argv.length > 2) {
url = process.argv[2]; // node down a
}
else {
return console.log("\n\
Usage : node down url\n\
url like : %s\n\
", example_url);
}

var $;
var title;
var start = new Date();
getHtml(url, function(err, html) {
$ = cheerio.load(html);
title = get_title($);
var dir = image_dir + "/" + title + "/";
!fs.existsSync(image_dir) && fs.mkdirSync(image_dir);
!fs.existsSync(dir) && fs.mkdirSync(dir);

getSrcs(url, function(srcs) {
var count = srcs.length;
console.log("共%s张", count);

srcs.forEach(function(src, index) {
var ext = pathFn.extname(src) || '.jpg';
var dest = dir + (index + 1) + ext;
download(src, dest, function() {
console.log("%s 完成下载 ...", index + 1);
//done();
});
})

var finished = 0;

function done() {
finished++;
if (finished == count) {
console.log("完成下载,耗时 : %s秒", (new Date() - start) / 1000);
}
}
})
})

function getHtml(url, callback) { // callback(err,html)
var parsed = urlFn.parse(url);
callback = callback || function() {};

http.request({
host: parsed.host,
path: parsed.path,
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2101.0 Safari/537.36"
}
}, function(res) {
//response event handler

var buffer = new Buffer(0);
res.on('data', function(buf) {
buffer = Buffer.concat([buffer, buf]);
});
res.on('end', function() {
var html = '';
if (Buffer.isEncoding(page_encoding)) { //utf8
html = buffer.toString(page_encoding);
}
else {
html = iconv.decode(buffer, page_encoding);
}
// console.log(html);
callback(null, html);
})
}).on('error', callback).end();
}

function getSrcs(url, callback) { // callback(srcs)
var srcs = [];

getHtml(url, function(err, html) {
if (err) throw err;

//main page
var $ = cheerio.load(html);
var cur_url = url; //当前url
var cur_srcs = get_page_srcs($).map(function(src) { // 当前页面的src
return urlFn.resolve(cur_url, src);
});
//add main page srcs
srcs = srcs.concat(cur_srcs);

//next pages
async.whilst(function() {
return has_next_page($);
}, function(next) {
cur_url = urlFn.resolve(cur_url, next_page_url($)); // change to next page

//next page
getHtml(cur_url, function(err, html) {
if (err) throw err;

$ = cheerio.load(html);
var cur_srcs = get_page_srcs($).map(function(src) { // 当前页面的src
return urlFn.resolve(cur_url, src);
});
srcs = srcs.concat(cur_srcs);
next();
})
}, function(err) {
if (err) throw err;

callback(srcs);
});
})
}

function download(src, dest, end) {
end = end || function() {};

var parsed = urlFn.parse(src);
http.get({
host: parsed.host,
path: parsed.path,
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2101.0 Safari/537.36"
}
}, function(res) {
// http://stackoverflow.com/questions/11944932/how-to-download-a-file-with-node-js
res.pipe(fs.createWriteStream(dest)).on('finish', function() {
this.close(end);
});
}).on("error", function(err) {
throw err;
}).end();
}