请教一个下载图片的问题
发布于 5 年前 作者 974806047 5009 次浏览 来自 问答

项目中有一个下载图片的需求,现在在编写下载代码的过程中遇到了一些问题。

我的需求是进行批量下载,如果下载失败需要进行记录,然后用本地的404图片替代。 下载完的图片需要马上放入docx文件中,所以需要对下载的过程进行整体阻塞。

一开始我是使用最简单的方法进行下载

function download(src, dest) {
	return new Promise((res,rej) => {
		const pipe = request(src).pipe(fs.createWriteStream(dest))
		pipe.on('close',() => res());
		pipe.on('error',(err) => rej(err));
	});
}

后面我发现, 如果目标是404的话,他会将404的信息写入到图片中,而不会有报错信息。 我的代码变成 request.get({url}, (err,res) => { if(err) return rej(err); if(res.statusCode === 404) return rej(‘404’); fs.writeFile(dest, responseBody, ‘binary’, err => { if(err) return rej(err); return res(1); }); }); 但组长说得使用流的方式写, 不然占用太多内存。 我又使用了wget,但是写完了之后发现偶尔会有一两张下载不完全,导致后面代码获取的时候出错。 查看了一下wget的代码,

    var downloader = new EventEmitter(),
    req = request(options, function(res) {
        var fileSize, writeStream, downloadedSize;
        if (res.statusCode === 200) {
            downloadedSize = 0;
            fileSize = res.headers['content-length'];
            writeStream = fs.createWriteStream(output, {
                flags: 'a',
                encoding: 'binary'
            });

            res.on('error', function(err) {
                writeStream.end();
                downloader.emit('error', err);
            });
            res.on('data', function(chunk) {
                downloadedSize += chunk.length;
                downloader.emit('progress', downloadedSize/fileSize);
                writeStream.write(chunk);
            });
            res.on('end', function() {
                writeStream.end();
                downloader.emit('end', output);
            });
        } else {
            downloader.emit('error', 'Server respond ' + res.statusCode);
        }
    });

我的代码里是用 downloader.on(‘end’,() => res());来判断是否成功, 但是在res.on(‘end’)的方法里面,writeStream.end();结束写入了,但好像还没有彻底完成? 所以我决定这样写

downloader.on('end',async () =>{
	await sleep(500);
	return res();
});

发现问题解决, 但是组长说不能这么写。

所以我又开始考虑superagent 我尝试是这么写

request.get(src)
	.ok(res => res.statusCode === 200)
	.then(res => {
		const pipe = res.pipe(writeStream);
		pipe.on....
	})

但是这里报错,显示 end() has already been called, so its too late to starting piping

那么现在问题来了, 怎样才能在判断了statusCode 的情况下使用流下载图片啊

7 回复
request.get(src)
    .on('response', res => {
        if (res.statusCode === 200) {
            res.pipe(writeStream);
        }
    });

@GaleLQ 谢谢你的回答! 我用superagent和request都试了一下 发现request可以,superagent不行,不知道这是为什么呢,明明两个都有on(‘response’,…)方法

@GaleLQ superagent 的压根没有触发 response事件

@974806047 看这里
重写了pipe等于发送请求(getFrame)加pipe行为,对你来说不可见,不改源码比较难弄,还是用别的吧

function download ( args ) {
	//some code ...
return new Promise((resolve, reject) => {
  try {
	let rs = request({
	  url,
	}).on('error', function (err) {
	  fs.appendFileSync('./failLog.log', url + '\n');
	  fs.writeFileSync(filePath, no_png);
	  reject(err);
	});
	rs.pipe(fs.createWriteStream(filePath));
	rs.on('end', () => {
	  COUNT++;
	  resolve('Success:  ' + COUNT);
	})
  } catch (error) {
	console.log(error);
	reject(error);
  }
})
}

这个样子啦

@LeavesSky nice!我现在做的跟你差不多了,就是没有错误的时候fs.writeFileSync(filePath, no_png); 这一步,我都是错误了之后返回给上层,再统一替换,就不用在下载的过程中一直复制了。

@974806047 在外部有个流式任务队列, 请求频率的比较凶, 所以放在里面帮我拖慢点速度(这理由好像有点牵强 !_!) 悄悄告诉你, 我还开了一个进程递归监测failLog.log确保fs.writeFileSync(filePath, no_png);

回到顶部