使用 Node.js 下载文件

最近在写一个爬虫,选择了 Node.js 做了工具,因为 Javascript 写多了很熟练。为什么不选 Python,因为基本不会 :P

不过 Node.js 是一个比较坑的东西,它的 API 是我见到过最奇怪的。拿 HttpClient 来说吧,大多数语言都自带了 HttpClient 的库,基本用法是发起一个 Http 请求,可以指定各种参数,比如 Http 方法(Method)、URL、发送过去的 Http 头和内容,然后设置返回内容的处理等等。比如下面是 jQuery 的 ajax 请求示例代码:

Node.js 中也差不多,比如这样:

但是 Node.js 有一个小小的问题,当 URL 是 Https 的时候,它会报错。为什么呢?因为 Http 库只支持 Http,如果需要下载 Https 的内容,需要使用 Https 库。看到这里我整个人就不好了。Node.js 作为一个服务器程序,对 Http 和 Https 服务器端有不同的支持,这无可厚非;但是对于 HttpClient 也要区别对待,这没有任何的道理,只能认为 API 的设计人员水平太差。

另外,Node.js 中回调函数(Callback)和事件(Event)混用,有些 API 使用回调函数,有些 API 使用事件,还有一些则两者都用。从上面的代码中就可以看出,HttpClient.get() 第二个参数是一个回调函数,而它的返回值又支持事件模式,什么时候要用哪种方式,对初学者而言,相当不友好。

而 Node.js 的官方文档相当不全,比如 ReadableStream 的 pipe() 方法,可以把当前的 Readable Stream 传递到一个 Writable Stream 中。当 pipe 读完之后,自动把 Writable Stream 给关闭。文档中是这么写的:

它只说明了,如果 end 参数为 true,则自动把 Writable Stream 关闭,而完全没有提到这个过程是同步的还是异步的。由于 Readable Stream 有一个 end 事件,我一开始很自然就把后续的操作挂在了 end 事件上,既然读完了,Stream 也关闭了,自然可以进行后续操作了。但万万没想到,那个关闭方法是异步的,要等 Writable Stream 把文件内容写完才可以,也就是需要等待 Writable Stream 的 finish 事件。这一细节在文档和示例代码中,完全没有体现出来。

好吧,写一个下载工具也有这么多坑,我也算是服了。以下是一个简单版本的代码,按 Node.js 的传统实现了回调机制:

更加完善的爬虫代码稍后公开,敬请期待。