我的扒图工具开发过程(一)

前段时间在网上畅游,在某网站上发现了不少养眼图片素材,于是想收取其为囊中之物,但人手下载效率太差,网上一下子又找不到合适的免费工具,于是就考虑开始开发这个扒图工具。

首先这个工具的需求仅定位于帮我批量下载某网站的图片,无需智能分析哪一张图片才是主图要下的,无需智能分析哪个连接跳转下一页的连接,所有这些分析都已人工找到的规律设置好。因此开发这个工具只要解决两个问题:

  1. 获取网页内容
  2. 分析网页内容,找到主图,分析图片总数

Nodejs的世界,要获取网页内容的库太多了,我选了我最顺手的axios,而分析网页内容当然jquery了,但还需要jsdom这个库将获取的网页内容模拟浏览器dom内容给jquery分析。

好了,废话不多说。上代码:

const iconv = require('iconv-lite')
const axios = require('axios').default
const Fs = require('fs')
const Path = require('path')
const A_MOMENT = 3000
async downloadPageContent(url){
    const res = await axios({
        url,
        responseType: 'stream',
        timeout: A_MOMENT,  // 注意一,不设置timeout的话,网络不好时会一直loading不会继续跑下去。设了timeout,超时就可以跳到catch代码加以处理. A_MOMENT=3000
    }).then(res =>
        new Promise((resolve, reject) => {
            const chunks = []
            res.data.on('data', chunk => {
                chunks.push(chunk)
            })
            res.data.on('end', () => {
                const buffer = Buffer.concat(chunks)
                const str = iconv.decode(buffer, 'gbk') // 注意二,某些网站为gbk编码,需要import('iconv')来decode
                resolve(str)
            })
            res.data.on('error', reject)
        }),
    )
    return res
},

  async downloadImage(url, options) {
    const { Referer, folder, name, id } = options
    const extName = Path.extname(url)
    const dir = Path.join(folder, this.padLeft(id, 4).substr(0, 2), id)
    const path = Path.resolve(dir, `${name}${extName}`)
    if (!Fs.existsSync(dir)) {
      Fs.mkdirSync(dir, { recursive: true })
    }
    const writer = Fs.createWriteStream(path)
    const response = await axios({
      url,
      method: 'GET',
      responseType: 'stream',
      timeout: 2 * A_MOMENT,
      headers: {
        Referer, // 坑,这里一定要设浏览该图片的网址,否则只会下载到同一张遮罩图片
        'Sec-Fetch-Dest': 'image',
        'User-Agent': ' Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
      },
    })

    response.data.pipe(writer)

    return new Promise((resolve, reject) => {
      writer.on('finish', resolve)
      writer.on('error', reject)
    })
  },

首先,定义两个函数,一个用来获取网页内容,一个用来下载图片,都是使用axios库。

其中调试运行过程遇到三个问题:

  1. 首先下载的网页内容中文乱码问题。 上网找资源,使用iconv-lite库轻松解决。
  2. 由于网络问题,有时网页或图片会一直loading就是不下载。只能设置timeout,加try catch,超时后可以跳到catch 代码作处理,重试或log下来悉随尊便。
  3. 下载图片时需要设置headers的Referer属性,值为你在网页上浏览该图片的网址。这个比较坑,不设置的话只会下载同一张广告图片,而不是你要下的正确图片。估计这是网站防盗图措施吧。
const jd = require('jsdom')
const jq = require('jquery')
const w = new jd.JSDOM().window
const $ = jq(w)

async getTotalNumber(pageContent) {
    const eles = $(pageContent).find('.content-page span')
    const spans= eles.map((index,ele)=>$(ele).text())
    if (_.isArray(spans) && spans.length > 0) {
      const res = spans.map(({ text }) => {
        const reg = /共(\d+?)页/ig
        const matchs = reg.exec(text)
        if (matchs) {
          const [, total] = matchs
          return _.toNumber(total)
        } else {
          return null
        }
      }).filter(v => v).sort((a, b) => b - a)
      return res[0]
    } else {
      throw new Error('getTotalNumberError')
    }
  },

asyn getImageUrl(pageContent){
    const eles=$(pageContent).find('.content-pic>a>img')
    const imgs=eles.map((index,ele)=>({src:$(ele).attr('src'),alt:$(ele).attr('alt')})
    returm imgs
}

其次,定义两个函数,从网页内容中提取要下的图片网址,以及该系列图片一共有多少页。这涉及两个知识技能。

  1. jquery的selector。 参考:https://www.w3schools.com/jquery/jquery_ref_selectors.asp
  2. 正则表达式的运用。参考:https://baike.baidu.com/item/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/1700215?fr=aladdin 
  async genDownloadPageUrl(id, total) {
    const result = [`https://xxxx.com/xxx/${id}.html`]
    for (let i = 1; i < total; i++) {
      const url = `https://xxxx.com/xxx/${id}_${i+1}.html`
      result.push(url)
    }
    return result
  },

 取得有多少图片后,就可以根据该网站的图片网址规律,获得各图片的浏览网址了。      

const _ = require('lodash')  
async batchDownloadImages(imagePageUrls,id) {
    const MAX_LINE = 3
    const batchs = _.chunk(imagePageUrls, MAX_LINE)
    let b = 0
    for (const batch of batchs) {
      const c = await Promise.all(batch.map(url => {
        return this.downloadPageContent(url)
          .then(content => this.getImageUrl(content)
          .then(images => {
            if (images && images.length > 0) {
              return Promise.all(images.map((image, index) =>                
                this.downloadImage(image.src, { Referer: url,folder:'F:\\',name:image.alt,id})
                  .then(() => 1)
                  .catch(error => { // errhandling
                    }),
              )).then(result => result.reduce((p, c) => p + c, 0))
                .then(result => result === images.length ? 1 : 0)
            } else {
              // errorhandling
            }
          })
          .catch(error =>{ // errhandling
            })
      })).then(result => result.reduce((p, c) => p + c, 0))
      b += c
    }
    return b === imagePageUrls.length
  },

有了这些网址,就可以进行批量下载了。

我这里设置了一批3条line进行下载,MAX_LINE。当然如果资源许可的话,可以尝试设大些。

下载完成后,计算一下下载的图片数量是否与之前取得的数据是否一致,如果一致的话,则该系列的图片就成功下载完了。如果不一致的话,则需要log下以分析问题原因了。 

async process(id) {
    const url=`http://xxxxx.com/xxxx/${id}.html`
    const page = await downloadPageContent(url)
    // 省略errorhandle. 正常情况下,要判断url是否404或其他错误
    const total = await getTotalNumber(page)
    const imagePageUrls = await genDownloadPageUrl(id, total)
    const success = await batchDownloadImages(imagePageUrls,id)
}
// 主体思路:
// 1. 分析第一页内容,取得该系列图片一共有多少张
// 2. 取得各页图片的网址
// 3. 批量下载图片

(async()=>{
    const maxid=5349 // 最大
    for(let i=maxid;i>0;i--){
        await process(id) // 从最新鲜的图片开始下载
    }
})()

以上程序主体基本完成,可以for循环一个系列一个系列的开始下载了。let us go~

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值