Node爬虫:批量下载图片

编写网页爬虫时,不仅要爬取 html 页面,往往需要把 html 页面里的图片抽取并下载,因此有必要实现批量图片下载。

在 Node.js 中,无需第三方模块,只需借助内置的 http 模块和 fs 模块,用很简短的代码,就能实现一个图片下载器,代码如下:

const fs = require('fs')
const http = require('http')
const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'

/**
 * 图片下载器
 * @param {string} url - 图片的网络地址
 * @param {string} dest - 保存图片的地址
 * @param {number} timeout - 超时时间,默认 3 分钟
 * @param {number} retries - 重试次数,默认重试 2 次
 */
module.exports = function pictureDownloader(url, dest, timeout = 3 * 60 * 1000, retries = 2) {
  let isRetry = false
  let req = http.request(url, res => res.pipe(fs.createWriteStream(dest)))
  req.setTimeout(timeout, () => {
    req.abort()
    isRetry = true
  })
  req.setHeader('User-Agent', userAgent)
  req.on('error', () => {
    isRetry = true
  })
  req.on('close', () => {
    // 重试时,将超时时间递增 1 分钟
    if (isRetry && retries > 0) pictureDownloader(url, dest, timeout + 60 * 1000, retries - 1)
  })
  req.end()
}

有了上述图片下载器(pictureDownloader.js),只需循环遍历从 html 里面抽取的图片地址,即可实现批量图片下载,代码如下:

const url = require('url')
const path = require('path')
const pictureDownloader = require('./pictureDownloader')

let imgUrls = [
  'http://s7.sinaimg.cn/mw690/4bd1954ct7b5d906a4f36',
  'http://s6.sinaimg.cn/mw690/4bd1954ctced017cf7695',
  'http://s4.sinaimg.cn/middle/4bd1954ctb42bdf76fce3',
  ...
  'http://s8.sinaimg.cn/mw690/4bd1954ctddbd4ab6a817'
]

imgUrls.map((imgUrl) => {
  let filename = path.parse(url.parse(imgUrl).pathname).base
  pictureDownloader(imgUrl, '/path/to/' + filename)
})

后记

上述图片下载器的代码很简短,核心代码不到 20 行,看似比较简单,可是写出上述代码,却花费我不少时间,主要被以下 2 个问题所困扰:

1. 异步逻辑导致 Node 进程不退出

Node 的 Event Loop 线程会跟踪未完成的异步逻辑,只要有未完成的异步逻辑,Node 进程就不会退出。批量下载图片,需要发出大量的 http 请求,这就有可能遇到服务器错误、网络超时等问题,而 Node.js 中,http 请求是异步的,因此,若服务器迟迟不响应,就会导致 Event Loop 线程中一直有未完成的 http 请求,进而导致 Node 进程不退出。明晰了原因,解决问题就简单了,在发出 http 请求时设置超时时间,当超时时,放弃该次请求,并尝试重试,即如下代码:

req.setTimeout(timeout, () => {
  req.abort()
  isRetry = true
})

2. Node 报错 Error: Can't set headers after they are sent.

将上述代码中的 http.request 改成 http.get 就会现 Error: Can't set headers after they are sent. 。很容易发现是代码 req.setHeader('User-Agent', userAgent) 导致了该错误。

查看 Node 的文档,会发现这 2 个方法返回的都是 http.ClientRequest 对象,而 http.ClientRequest 对象实现了 setHeader 方法,既然有 setHeader 方法,为何还会报错呢?所以我就有点困惑了。

后仔细查看文档,发现这 2 个方法唯一的区别是: http.get 会把 method 设置为 GET 并自动调用 req.end() 。也就是说, http.get 会立即发送请求。既然请求已经发出,当然就不能设置头部了。

问题解决后,回头看时,发现 Node 的报错信息很明了。只是,当时迷在其中。

 

转载于:https://my.oschina.net/junyiz/blog/1615182

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值