Node.JS - 爬取vmgirls寫真

一累盗、vmgirls

vmgirls是一個(gè)個(gè)人維護(hù)的圖集集合網(wǎng)站.
里面有很多來(lái)自圖蟲攝影亲族,POCO攝影国裳,網(wǎng)易攝影鬓照,網(wǎng)易樂(lè)乎等好看小姐姐的寫真.

index.png

圖片基本都是2000px左右的高清大圖,收集資源,養(yǎng)眼都非常不錯(cuò).

applause.png

站點(diǎn)結(jié)構(gòu)非常簡(jiǎn)單.直接用正則匹配或者框架都行.
本文介紹的是使用cheerio框架爬取。

二、分析

category.png

首頁(yè)無(wú)論是nav欄或者目錄,分類都特別的雜且不全.


recommend.png

最終敲定專題推薦page
只把所有的圖集分為兩類,并且可通過(guò)下拉刷新加載


html.png

查看頁(yè)面可知,界面是分段渲染的.
即打開(kāi)頁(yè)面初始渲染八個(gè)圖集
通過(guò)點(diǎn)擊加載更多,再向服務(wù)器請(qǐng)求數(shù)據(jù),再刷新頁(yè)面
more.png

F12開(kāi)發(fā)者模式觀察請(qǐng)求


post.png

每一次點(diǎn)擊更多都發(fā)送一次POST請(qǐng)求
并且攜帶四個(gè)參數(shù)


post_.png

對(duì)比兩次請(qǐng)求,可初步判斷決定數(shù)據(jù)結(jié)果的參數(shù)應(yīng)該是query和paged
使用ajax嘗試請(qǐng)求數(shù)據(jù)

Request URL: https://www.vmgirls.com/wp-admin/admin-ajax.php
async function ajax(data) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open("post", "https://www.vmgirls.com/wp-admin/admin-ajax.php")
    xhr.setRequestHeader(
      "Content-Type",
      "application/x-www-form-urlencoded; charset=UTF-8"
    )
    xhr.send(data)
    xhr.onload = function () {
      if (xhr.status == 200) {
        resolve(xhr.responseText)
      } else {
        reject("error")
      }
    }
  })
}

返回結(jié)果與預(yù)期相同,拿到了一組8個(gè)圖集對(duì)應(yīng)data

const page = 1
// options 輕私房/小姐姐
const category = "小姐姐"
const data = `append=list-archive&paged=${i}&action=ajax_load_posts&query=${encodeURI(category)}&page=tax`

通過(guò)page決定下載寫真數(shù)
寫真數(shù) = page × 8
類別 = 小姐姐 / 輕私房


detail.png

使用cheerio庫(kù),傳入選擇器,捕獲元素

      const $ = cheerio.load(res)
      const $arr = $(".col-6 .list-item .media a")

這里不選擇img元素,測(cè)試發(fā)現(xiàn)imgsrc屬性渲染結(jié)果與源代碼不同.
應(yīng)該是執(zhí)行了某種反爬蟲js腳本.
而data-src雖然是相同的,但webp圖片格式無(wú)法通過(guò)axios arraybuffer寫入


src.png

最終敲定捕獲a元素
只需發(fā)送需要的ajax請(qǐng)求獲取圖集
再通過(guò)圖集下載對(duì)應(yīng)圖片即可

三当娱、成品展示

result_.png
result.png
result__.png

四吃既、代碼實(shí)現(xiàn)

函數(shù)已封裝完畢.
改變?nèi)肿兞縫age、category.
執(zhí)行 tsc ,node 即可

注意事項(xiàng)

  • axios POST請(qǐng)求得不到想要的結(jié)果
  • 極少部分詳情頁(yè)布局采用的是ul li布局,此代碼并不囊括
  • vgirls服務(wù)器些許卡頓,圖片都是大尺寸,可適當(dāng)延長(zhǎng)axios timeout屬性
  • webp圖像格式無(wú)法采用axios arraybuffer 下載
const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest
const cheerio = require("cheerio")
const axios = require("axios")
const fs = require("fs")
const path = require("path")
const chalk = require('chalk')

type IvmGirls = () => Promise<void>
type IgetDataArr = () => Array<string>
type IgetDetailArr = (dataArr: string[]) => Promise<Array<object>>
type IgetImgArr = (detailArr: any[]) => Promise<Array<object>>
type IdownloadPic = (imgArr: any[]) => void
type Iajax = (data: string) => Promise<string>
type IwriteFile = (path: string, content: any) => void

const page = 1
// options 輕私房/小姐姐
const category = "小姐姐"


const vmGirls: IvmGirls = async () => {
  // 獲取ajax請(qǐng)求data arr
  const dataArr = getDataArr()
  // 獲取詳情頁(yè)url arr
  const detailArr = await getDetailArr(dataArr)
  // 獲取imgurl arr
  const imgArr = await getImgArr(detailArr)
  // 下載圖片
  downloadPic(imgArr)
}

const getDataArr: IgetDataArr = () => {
  const dataArr = []
  for (let i = 1; i <= page; i++) {
    const data = `append=list-archive&paged=${i}&action=ajax_load_posts&query=${encodeURI(
      category
    )}&page=tax`
    dataArr.push(data)
  }
  return dataArr
}

const getDetailArr: IgetDetailArr = async (dataArr) => {
  const detailArr = []
  for (let i = 0; i < dataArr.length; i++) {
    try {
      const res = await ajax(dataArr[i])
      const $ = cheerio.load(res)
      const $arr = $(".col-6 .list-item .media a")
      for (let j = 0; j < $arr.length; j++) {
        const detailHref = $arr[j].attribs["href"]
        const title = $arr[j].attribs["title"]
        const obj = {
          href: detailHref,
          title
        }
        detailArr.push(obj)
      }
      console.log(`ajax paged ${i + 1} Successful\n`)
    } catch (error) {
      console.log(`ajax paged ${i + 1} Failure\n`)
      continue
    }
  }
  return detailArr
}

const getImgArr: IgetImgArr = async (detailArr) => {
  const arr = []
  for (let i = 0; i < detailArr.length; i++) {
    try {
      const res = await axios({
        url: detailArr[i].href,
        timeout: 3000,
      })
      const $ = cheerio.load(res.data)
      const $arr = $("div.nc-light-gallery p a")
      const imgArr = []
      const title = $arr[0].attribs["alt"]
      for (let j = 0; j < $arr.length; j++) {
        const imgUrl = 'https://www.vmgirls.com/' + $arr[j].attribs["href"]
        imgArr.push(imgUrl)
      }
      const obj = {
        title,
        url: imgArr,
      }
      arr.push(obj)
      console.log(chalk.green(`${detailArr[i].title} atlas get success\n`))
    } catch (error) {
      console.log(chalk.red(`${detailArr[i].title} atlas get failure\n`))
    }
  }
  return arr
}

const downloadPic: IdownloadPic = async (imgArr) => {
  fs.mkdir("./images", () => { })
  for (let atlas of imgArr) {
    const title = atlas.title
    fs.mkdir(`./images/${title}`, () => { })
    for (let i in atlas.url) {
      const extname = path.extname(atlas.url[i])
      try {
        const res = await axios({
          url: atlas.url[i],
          timeout: 2000,
          responseType: "arraybuffer",
        })
        await writeFile(`./images/${title}/${title}_${+i + 1}${extname}`, res.data)
        console.log(chalk.green(`${title} (${+i + 1}/${atlas.url.length}) Download Successful\n`))
      } catch (error) {
        console.log(chalk.red(`${title} (${+i + 1}/${atlas.url.length}) Download Failure\n`))
      }
    }
  }
}

const ajax: Iajax = async (data) => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open("post", "https://www.vmgirls.com/wp-admin/admin-ajax.php")
    xhr.setRequestHeader(
      "Content-Type",
      "application/x-www-form-urlencoded; charset=UTF-8"
    )
    xhr.send(data)
    xhr.onload = function () {
      if (xhr.status == 200) {
        resolve(xhr.responseText)
      } else {
        reject("error")
      }
    }
  })
}

const writeFile: IwriteFile = (path, content) => {
  return new Promise((resolve) => {
    fs.writeFile(path, content, (err) => {
      if (!err) resolve()
    })
  })
}
vmGirls()

export { }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末跨细,一起剝皮案震驚了整個(gè)濱河市鹦倚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冀惭,老刑警劉巖震叙,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異散休,居然都是意外死亡媒楼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門戚丸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)划址,“玉大人,你說(shuō)我怎么就攤上這事限府《岵” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵谣殊,是天一觀的道長(zhǎng)拂共。 經(jīng)常有香客問(wèn)我,道長(zhǎng)姻几,這世上最難降的妖魔是什么宜狐? 我笑而不...
    開(kāi)封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蛇捌,結(jié)果婚禮上抚恒,老公的妹妹穿的比我還像新娘。我一直安慰自己络拌,他們只是感情好俭驮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著春贸,像睡著了一般混萝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上萍恕,一...
    開(kāi)封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天逸嘀,我揣著相機(jī)與錄音,去河邊找鬼允粤。 笑死崭倘,一個(gè)胖子當(dāng)著我的面吹牛翼岁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播司光,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼琅坡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了残家?” 一聲冷哼從身側(cè)響起榆俺,我...
    開(kāi)封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坞淮,沒(méi)想到半個(gè)月后谴仙,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碾盐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了揩局。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毫玖。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖凌盯,靈堂內(nèi)的尸體忽然破棺而出付枫,到底是詐尸還是另有隱情,我是刑警寧澤驰怎,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布阐滩,位于F島的核電站,受9級(jí)特大地震影響县忌,放射性物質(zhì)發(fā)生泄漏掂榔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一症杏、第九天 我趴在偏房一處隱蔽的房頂上張望装获。 院中可真熱鬧,春花似錦厉颤、人聲如沸穴豫。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)精肃。三九已至,卻和暖如春帜乞,著一層夾襖步出監(jiān)牢的瞬間司抱,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工挖函, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留状植,地道東北人浊竟。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像津畸,于是被迫代替她去往敵國(guó)和親振定。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354