bilibili壁纸站-node爬虫

前言

之前初学node的时候,有用爬虫爬过一些磁力链接
详情见羞羞的node爬虫
但是没有并发,没有代理,那时也对异步不是很了解
所以这次又写了个爬虫,爬取bilibili壁纸站的所有壁纸
并且爬取开心代理的100条ip,并将有用的ip存进json文件中

用到的模块

  1. async (控制并发)
  2. cheerio (解析DOM)
  3. superagent (http库)
  4. superagent-proxy (使用代理)
  5. fs (读写文件)

其中cheerio, superagent的具体用法见我之前的 羞羞的node爬虫
不过之前初学,代码写得很难看就对了

爬取代理ip

代理ip是干嘛的

我们访问互联网资源时,都是用我们自己的ip(身份证)去访问的
而爬虫得频繁地去获取互联网资源
因此如果你在某个时间点频繁地访问某网站的某资源
造成该网站的服务器压力
就有可能被网站管理者禁ip, 从而访问不了该网站
代理ip就是伪造身份去访问

怎么检验ip的可用性

这里面就使用到了 superagent 的一个拓展 superagent-proxy
然后用其去访问http://ip.chinaz.com/getip.aspx
若 3s 内能返回值,则证明该 ip 可用

const superagent = require('superagent')
require('superagent-proxy')(superagent);

// 写上你先要测试的 ip,下面仅为测试ip
let testIp = 'http://61.178.238.122:63000';

(async function() {
  superagent.get('http://ip.chinaz.com/getip.aspx').proxy(testIp).timeout(3000)
  .end((err, res) => {
    if(res === undefined) {
      console.log('挂了'); 
      return 
    }
    if(err) {
      console.log('报错啦')
    }
    console.log('成功: ' + res.text)
  })
}())

爬取ip并存储

首先我们先看下我们要爬取的开心代理的DOM

我们要爬取得ip地址放在tr 标签的第一个td上
并且点击第二页时,链接变为http://www.kxdaili.com/dailiip/1/2.html#ip
链接上的数组表示得是页数,也就是说我们只要改变链接上数字的值
就可以获取到其他页的html

代码如下:

const superagent = require('superagent')
const cheerio = require('cheerio')
const fs = require('fs')
const apiFunc = require('../common/apiFunc')  // 封装的一些读写api

// 爬取开心代理的 ip
const website = 'http://www.kxdaili.com'
let url = website + '/dailiip/1/'

// 总执行函数
let getIp = async function() {
  // promise 存放的数组
  let tasks = []

  // 读取 ip.js 本身存储的ip
  let ips = await apiFunc.readFile('./ip.js')
  ips = JSON.parse(ips)

  for(let page = 1; page <= 10; page++) {
    let res = await superagent.get(url + page +'.html')
    let $ = cheerio.load(res.text)
    let tr = $('tbody>tr')

    for(let i = 0; i < tr.length; i++) {
      let td = $(tr[i]).children('td')
      let proxy = 'http://' + $(td[0]).text() + ':' + $(td[1]).text()
      let pro = apiFunc.filterIp(proxy)

      // 将所有的IP过滤Promise存入一个tasks数组中
      tasks.push(pro)
    }
  }

  // 使用 all 等待所有ip过滤完毕后执行 写入 ip.js过程
  Promise.all(tasks).then((arr) => {
    // 过滤掉返回值为 undefined 的数据
    let usefulIp = arr.filter((item) => {
      return (item !== undefined)
    })
    ips = JSON.stringify(ips.concat(usefulIp))
    console.log(ips)
    apiFunc.writeFile('./ip.js', ips)   
  })
}

getIp()

module.exports = getIp

爬取bilibili壁纸站

我们先进入bilibili壁纸站

发现有一个点击加载更多的按钮
如果有对前端有了解的话,我们应该知道这是通过 ajax 请求来异步获取数据
因此我们打开开发者的NetWork

果然在 XHR 这一栏发现了一个api
里面返回的是存储了当前页面所有壁纸缩略图信息的json文件
仅依靠这个json文件,我们便可以爬取所有壁纸的缩略图
可我们要的可是高清大图啊

于是我们随意点击一张缩略图


发现它的url的参数(il_id, width, height)都来自我们之前获取的json内的数据
也就是说我们可以拼接该链接来获取到该高清图片的链接,再利用cheerio来解析DOM获取图片地址就ok了
!!!
!!!
!!!
然而,哈哈哈哈哈哈哈哈哈哈哈哈
当我们获取到该网页的html后,发现该<img>标签内的src是空的
也就是说该<img>也是js赋值,所以下意识又去看了NetWorkXHR
果然发现了另一个api

而高清图片的url就是该api返回的json数据中的il_file

因此我们只需要拼接该api链接,再用superagent请求就可以获取到高清图片的url

理下思路

  1. 获取缩略图api返回的包含高清图片数据的json
  2. 将1的json数据拼接到高清图片api链接上,并将所有api链接存入数组
  3. 并发获取2数组中的api, 获取所有的图片url,并将url存入数组
  4. 并发下载数组中的图片url, 存进本地文件夹

结果在爬取bilibili壁纸站时,是不需要解析DOM的,也就是不需要使用cheerio模块啦

代码如下:

const superagent = require('superagent')
require('superagent-proxy')(superagent);
const fs = require('fs')
const cheerio = require('cheerio')
const async = require('async')

// 获取bilibili API的json数据
let jsonUrl = 'http://h.bilibili.com/wallpaperApi?action=getOptions&page=1'
let proxy = "http://218.201.98.196:3128"

let getPicJson = function () {
  return new Promise((resolve, reject) => {
    superagent
      .get(jsonUrl)
      .proxy(proxy)
      .end((err, res) => {
        if (err) console.log('代理出错啦')
        if (res === undefined) return
        if (res.statusCode == 200) {
          let json = JSON.parse(res.text)
          resolve(json)
        }
      })
  })
}

// 获取高清图片api的json数据
let dealHd = async function () {
  let picHd = []
  let picJson = await getPicJson()
  let picLength = picJson.length

  for (let i = 1; i < picLength; i++) {
    let item = {}
    // let width = picJson[i].detail[0].width
    // let height = picJson[i].detail[0].height
    let il_id = picJson[i].detail[0].il_id
    item.title = picJson[i].detail[0].title
    item.url = `http://h.bilibili.com/wallpaperApi?action=getDetail&il_id=${il_id}`
    picHd.push(item)
    // item.url = `http://h.bilibili.com/wallpaper?action=detail&il_id=${il_id}&type=Bilibili&width=${width}&height=${height}`
    // picHtmlJson.push(item)
  }
  return picHd
}

// 获取高清图片的url ===== queue
let dealPicJson = async function () {

  console.log('获取高清图片url,开始执行....')
  var concurrencyCount = 0;
  let result = []
  let hdJson = await dealHd()
  return new Promise((resolve, reject) => {

    let q = async.queue((hDJson, callback) => {
      var delay = parseInt((Math.random() * 30000000) % 1000, 10);  //设置延时并发爬取
      concurrencyCount++;
      console.log('现在的并发数是', concurrencyCount, ',正在获取的是', hDJson.title, '延迟', delay, '毫秒');

      superagent.get(hDJson.url).proxy(proxy).end((err, res) => {
        if (err) {
          console.log(err);
          callback(null);
        } else {
          // let $ = cheerio.load(res.text)
          // let hdUrl = $('#wallpaper').attr('id')
          // console.log('链接是' + hdUrl)
          let pic = {}
          pic.title = hDJson.title
          pic.url = res.body[0].detail[0].il_file
          pic.format = pic.url.match(/.{3}$/)[0]
          // console.log(result)

          result.push(pic)
          concurrencyCount --
          callback(null)
        }
      })
    }, 5)
    q.drain = function () {
      resolve(result)
    }

    q.push(hdJson)
  })
}


// 下载HD图片
let downloadImg = async function () {
  console.log('开始下载图片...');
  // let folder = `Data/img-${Config.currentImgType}-${Config.startPage}-${Config.endPage}`;
  // fs.mkdirSync(folder);
  let downloadCount = 0;
  var concurrencyCount = 0;
  let q = async.queue(function (image, callback) {
    // console.log('正在下载 : ' + image.title);
    var delay = parseInt((Math.random() * 30000000) % 1000, 10);  //设置延时并发爬取
    concurrencyCount++;
    console.log('现在的并发数是', concurrencyCount, ',正在抓取的是', image.title, '延迟', delay, '毫秒');
    superagent.get(image.url).proxy(proxy).end(function (err, res) {
      if (err) {
        console.log(err);
        callback(null);
      } else {
        downloadCount++;
        fs.writeFile(`./picture/${downloadCount}-${image.title}.${image.format}`, res.body, function (err) {
          if (err) {
            console.log(err);
          } else {
            console.log("图片下载成功");
          }
          setTimeout(() => {
            concurrencyCount--;
            callback(null);
          }, delay)
        });
      }
    });
  }, 5);
  
  // 当所有任务都执行完以后,将调用该函数
  q.drain = function () {
    console.log('All img download');
  }
  let imgList = await dealPicJson();
  q.push(imgList);//将所有任务加入队列
}

downloadImg()

async控制并发

控制并发我通常是用async.maplimit,因为最早接触
不过看到一篇文章介绍了async.queue,我就试了下
区别在于, mapLimit会返回所有并发任务结束后的结果数组
queue是没有的,因此要自己定个变量来存放每一个并发任务返回的结果
具体api用法见: async常用api

运行结果


后记

github代码: bilibili壁纸站爬虫
里面有一些必要注释
有4个可以跑的js

  1. ./aboutIp/getIp.js (用来抓并存有用的代理ip)
  2. ./aboutIp/ipTest.js (测试ip可不可用)
  3. app-thumbnails.js (用来爬壁纸的缩略图)
  4. app-hd.js (用来爬壁纸的高清图)

虽然懂得很浅,但能渐渐感受到爬虫的魅力了😁

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容