用Node.js寫爬蟲,擼羞羞的圖片

lunlunshiwo發表於2018-04-03

  說到爬蟲,很多人都認為是很高大上的東西。哇塞,是不是可以爬妹紙圖啊,是不是可以爬小片片啊。答案就是對的。爬蟲可以完成這些東西的操作。但是,作為一個正直的程式設計師,我們要在法律允許範圍內用爬蟲來為我們服務,而不是為所欲為。(ps:此處應有掌聲,謝謝。)

  今天,我帶來一個用Node.js寫的爬蟲。一說到教程呢,可能大多數人認為比較枯燥無味。那這樣好了,我教大家爬妹紙圖,上乾貨:

  是不是瞬間有了動力了?

  說到爬蟲呢,其實從客觀上來說,“所有網站皆可爬”。網際網路的內容都是人寫出來的,而且都是偷懶寫出來的(不會第一頁是a,下一頁是8),所以肯定有規律,這就給人有了爬取的可能,可以說,天下沒有不能爬的網站。而且即使網站不同,但是原理都類似,大部分爬蟲都是從 傳送請求——>獲得頁面——>解析頁面——>下載內容——>儲存內容 這樣的流程來進行,只是用的工具不同,可能你用python,我用Node,他用PHP,但是思路也是與上面相同。

  既然是用node完成爬蟲,那麼我們就要用到node環境,如果不會配的話,請參考我的第一篇部落格。

  好的,我們從爬蟲流程開始分析我們需要的一些模組。

  首先,我們需要傳送請求獲得頁面,在這裡呢,我們用到了request-promise模組。

const rp = require("request-promise"), //進入request-promise模組
async getPage(URL) {
    const data = {
        url, 
        res: await rp({
            url: URL
        }) 
    }; 
    return data //這樣,我們返回了一個物件,就是這個頁面的url和頁面內容。
}複製程式碼

  其次,解析頁面,我們使用一個叫做Cheerio的模組將上面返回的物件中的res解析成類似JQ的呼叫模式。Cheerio使用一個非常簡單,一致的DOM模型。因此解析,操作和渲染非常高效。初步的端到端基準測試表明cheerio 比JSDOM快大約8倍。

const cheerio = require("cheerio");//引入Cheerio模組
const $ = cheerio.load(data.res); //將html轉換為可操作的節點複製程式碼

  此時,我們要對我們即將進行爬取的頁面進行分析。“www.mzitu.com/125685”,這是我們進行爬取的網址,F12檢視DOM結構:

  根據這個結構我們可以使用$(".main-image").find("img")[0].attribs.src來爬取這張圖片的地址(如果不知道為什麼是attribs.src的話可以一步一步console.log()一下看看)。

  最後,到了最關鍵的時候,我們使用fs模組進行建立資料夾以及下載檔案。這裡用到了fs模組的幾個指令:

    1.fs.mkdirSync(downloadPath):檢視是否存在這個資料夾。

    2.fs.mkdirSync(downloadPath):建立資料夾。

    3.fs.createWriteStream(`${downloadPath}/${index}.jpg`):寫入檔案,這裡需要注意的是fs.createWriteStream 似乎不會自己建立不存在的資料夾,所以在使用之前需要注意,儲存檔案的資料夾一定要提前建立。

  好的,大體的方法就是以上的幾個模組和步驟。

  在這裡,我針對這個網站的一些情況進行一下分析:

    1.這個網站一個頁面只有一張圖片,但是每個頁面的網址都是有根據的。“http://www.mzitu.com/125685”(當你輸入“http://www.mzitu.com/125685/1”時也會跳轉此頁面),“http://www.mzitu.com/125685/2”等等。那麼我們可以根據這個規律去爬取,並且我們需要在頁面的下方的頁碼欄中獲得這一組圖圖片的頁碼:

  

    2.我們一般不會只爬取一組圖片,但是這個網站的圖片的標題也就是最後的六位數基本沒有規律可言,那麼我們只能從最開始的首頁入手。具體方法不多做描述,與獲取圖片的URL方式相同。

    3.同理,我們爬取完一頁的目錄之後會進行對第二個目錄的爬取,“http://www.mzitu.com/page/2/”,其原理和第一條相同。

    4.但是,有的網站存在防盜鏈的情況,面對這種措施,我們需要偽造一個請求頭來避開這個情況。這個可以從F12的Network中查到,看到這裡的朋友我想也會明白。

let headers = {
          Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
          "Accept-Encoding": "gzip, deflate",
          "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
          "Cache-Control": "no-cache",
          Host: "i.meizitu.net",
          Pragma: "no-cache",
          "Proxy-Connection": "keep-alive",
          Referer: data.url,//根據爬取的網址跟換
          "Upgrade-Insecure-Requests": 1,
          "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.19 Safari/537.36"
        };複製程式碼

  以上就是我的全部思路。

  程式碼:

    業務程式碼:

const rp = require("request-promise"), //進入request-promise模組
  fs = require("fs"), //進入fs模組
  cheerio = require("cheerio"), //進入cheerio模組
  depositPath = "D:/blog/reptile/meizi/"; //存放照片的地址
let downloadPath; //下載圖片的資料夾地址
module.exports = {
  async getPage(url) {
    const data = {
      url,
      res: await rp({
        url: url
      })
    };
    return data;
  },
  getUrl(data) {
    let list = [];
    const $ = cheerio.load(data.res); //將html轉換為可操作的節點
    $("#pins li a")
      .children()
      .each(async (i, e) => {
        let obj = {
          name: e.attribs.alt, //圖片網頁的名字,後面作為資料夾名字
          url: e.parent.attribs.href //圖片網頁的url
        };
        list.push(obj); //輸出目錄頁查詢出來的所有連結地址
      });
    return list;
  },
  getTitle(obj) {
    downloadPath = depositPath + obj.name;
    if (!fs.existsSync(downloadPath)) {//檢視是否存在這個資料夾
      fs.mkdirSync(downloadPath);//不存在就建資料夾
      console.log(`${obj.name}資料夾建立成功`);
      return true;
    } else {
      console.log(`${obj.name}資料夾已經存在`);
      return false;
    }
  },
  getImagesNum(res, name) {
    if (res) {
      let $ = cheerio.load(res);
      let len = $(".pagenavi")
        .find("a")
        .find("span").length;
      if (len == 0) {
        fs.rmdirSync(`${depositPath}${name}`);//刪除無法下載的資料夾
        return 0;
      }
      let pageIndex = $(".pagenavi")
        .find("a")
        .find("span")[len - 2].children[0].data;
      return pageIndex;//返回圖片總數
    }
  },
  //下載相簿照片
  async downloadImage(data, index) {
    if (data.res) {
      var $ = cheerio.load(data.res);
      if ($(".main-image").find("img")[0]) {
        let imgSrc = $(".main-image").find("img")[0].attribs.src;//圖片地址
        let headers = {
          Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
          "Accept-Encoding": "gzip, deflate",
          "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
          "Cache-Control": "no-cache",
          Host: "i.meizitu.net",
          Pragma: "no-cache",
          "Proxy-Connection": "keep-alive",
          Referer: data.url,
          "Upgrade-Insecure-Requests": 1,
          "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.19 Safari/537.36"
        };//反防盜鏈
        await rp({
          url: imgSrc,
          resolveWithFullResponse: true,
          headers
        }).pipe(fs.createWriteStream(`${downloadPath}/${index}.jpg`));//下載
        console.log(`${downloadPath}/${index}.jpg下載成功`);
      } else {
        console.log(`${downloadPath}/${index}.jpg載入失敗`);
      }
    }
  }
};複製程式碼

    主體邏輯程式碼:

const model = require("./model"),
  basicPath = "http://www.mzitu.com/page/";
let start = 1,
  end = 10;
const main = async url => {
  let list = [],
    index = 0;
  const data = await model.getPage(url);
  list = model.getUrl(data);
  downLoadImages(list, index);//下載
};
const downLoadImages = async (list, index) => {
  if (index == list.length) {
    start++;
    if (start < end) {
      main(basicPath + start);//進行下一頁圖片組的爬取。
    }
    return false;
  }
  if (model.getTitle(list[index])) {
    let item = await model.getPage(list[index].url),//獲取圖片所在網頁的url
      imageNum = model.getImagesNum(item.res,list[index].name);//獲取這組圖片的數量
    for (var i = 1; i <= imageNum; i++) {
      let page = await model.getPage(list[index].url + `/${i}`);//遍歷獲取這組圖片每一張所在的網頁
      await model.downloadImage(page, i);//下載
    }
    index++;
    downLoadImages(list, index);//迴圈完成下載下一組
  } else {
    index++;
    downLoadImages(list, index);//下載下一組
  }
};
main(basicPath + start);複製程式碼

  此次專案已上傳我的Github倉庫github.com/lunlunshiwo…,求star,謝謝。

  總結:

  至於後續操作,比如存到分類儲存到本地和MongoDB資料庫這樣的操作,我下次再寫,請關注我。

  鄭重提示,爬蟲雖好,一定不能觸犯法律。

  如果本本文觸犯您的利益,請留言。

  如果覺得本文不錯,不要吝嗇您的點贊和關注。謝謝。

  


相關文章