用 Node 抓站(一):怎麼寫出自己滿意的程式碼

三水清發表於2019-02-11

如果只寫怎麼抓取網頁,肯定會被吐槽太水,滿足不了讀者的逼格要求,所以本文會通過不斷的審視程式碼,做到令自己滿意(擼碼也要不斷迸發新想法!

本文目標:抓取什麼值得買網站國內優惠的最新商品,並且作為物件輸出出來,方便後續入庫等操作

抓取常用到的npm模組

本文就介紹兩個:requestcheerio,另外lodash是個工具庫,不做介紹,後面篇幅會繼續介紹其他用到的npm庫。

  • request:是一個http請求庫,封裝了很多常用的配置,而且也有promise版本(還有next版本。
  • cheerio:是一個類似jQuery的庫,可以將html String轉成類似jQ的物件,增加jQ的操作方法(實際是htmlparser2

request 示例

var request = require(`request`);
request(`http://www.smzdm.com/youhui/`, (err, req)=>{
  if(!err){
    console.log(Object.keys(req))
  }
})複製程式碼

通過上面的程式碼就看到req實際是個response物件,包括headersstatusCodebody 等,我們用body就是網站的html內容

cheerio 示例

var request = require(`request`)
var cheerio = require(`cheerio`)

cheerio.prototype.removeTagText = function () {
  var html = this.html()
  return html.replace(/<([wd]+)[^<]+?</1>/g, (m) => {
    return ``
  })
}
request(`http://www.smzdm.com/youhui/`, (err, req) => {
  if (!err) {
    var body = req.body
    var $ = cheerio.load(body, {
      decodeEntities: false
    })
    $(`.list.list_preferential`).each((i, item) => {
      var $title = $(`.itemName a`, item)
      var url = $title.attr(`href`)
      var title = $title.removeTagText().trim()

      var hl = $title.children().text().trim()
      var img = $(`img`, item).attr(`src`)
      var desc = $(`.lrInfo`, item).html().trim()
      desc = desc.replace(/<a.+?>閱讀全文</a>/g, ``)
      var mall = $(`.botPart a.mall`, item).text().trim()

      console.log({title, hl, url, img, desc, mall})
    })
  }
})複製程式碼

簡單解釋下,removeTagText是直接擴充套件了cheerio的一個方法,目的是去掉類似

再特價:QuanU 全友 布藝沙發組合<span class="z-highlight">2798元包郵(需定金99元,3.1付尾款)</span>複製程式碼

裡面span之後的文字。執行後得到下面的結果:

用 Node 抓站(一):怎麼寫出自己滿意的程式碼

怎麼寫出自己滿意的程式碼

從上面需求來看,只需要提取列表頁面的商品資訊,而取到資料之後,使用cheerio進行了解析,然後通過一些「選擇器」對資料進行「提取加工」,得到想要的資料。

重點是選擇器提取加工,如果想要的欄位多了,那麼程式碼會越寫越多,維護困難,最重要的是「不環保」,今天抓什麼值得買,明天抓惠惠網,程式碼還要copy一份改一改!一來二去,抓的越多,那麼程式碼越亂,想想哪天不用request了,是不是要挨個修改呢?所以要抓重點,從最後需要的資料結構入手,關注選擇器提取加工

handlerMap

從最後需要的資料結構入手,關注選擇器提取加工。我設計一種物件結構,作為引數傳入,這個引數我起名:handlerMap,最後實現一個spider的函式,用法如下:

spider(url, callback, handlerMap)複製程式碼

從目標資料結構出發,最後資料什麼樣子,那麼handlerMap結構就是什麼樣子,key就是最後輸出資料的key,是由selectorhandler兩個key組成的物件,類似我們需要最後產出的資料是:

[{
  title: ``,
  ht: ``,
  url: ``,
  img: ``,
  mall: ``,
  desc: ``
}, {item2..}...]複製程式碼

那麼需要的handlerMap就是:

{
  title: {
    selector: `.itemName a`,
    handler: `removeTagText`
  },
  ht: {
    selector: `.itemName a span`,
    handler: `text`
  },
  url: {
    selector: `.itemName a`,
    handler: `atrr:href`
  },
  img: {
    selector: `img`,
    handler: `attr:src`
  },
  mall: {
    selector: `.botPart a.mall`,
    handler: `text`
  },
  desc: {
    selector: `.lrInfo`,
    handler: function (data){
      return data.replace(/<a.+?>閱讀全文</a>/g, ``)
    }
  }
}複製程式碼

再酷一點,就是簡寫方法:url:".itemName a!attr:href”,另外再加上如果抓取的是JSON資料,也要一起處理的情況。經過分析之後,開始改造程式碼,程式碼最後分為了兩個模組:

  • spider.js:包裝request 模組,負責抓取頁面將頁面交給parser.js解析出來想要的資料
  • parser.js:負責解析handlerMap,同時支援json和html兩種型別的頁面進行解析

雖然增加不少程式碼工作量,但是抽象後的程式碼在使用的時候就更加方便了,自己還是別人在使用的時候,不用關心程式碼實現,只需要關注抓取的頁面url、要提取的頁面內容和資料得到後的繼續操作即可,使用起來要比之前混雜在一起的程式碼更加清晰簡潔;並且抓取任意頁面都不需要動核心的程式碼,只需要填寫前面提到的handlerMap

總結

其實Node抓取頁面很簡單,本文只是通過一個簡單的抓取任務,不斷深入思考,進行抽象,寫出自己滿意的程式碼,以小見大,希望本文對讀者有所啟發?

今天到此結束,完成一個基礎抓取的庫,有空繼續介紹Node抓站的知識,歡迎大家交流討論

本文的完整程式碼,在github/ksky521/mpdemo/ 對應文章名資料夾下可以找到

-eof-
@三水清
未經允許,請勿轉載,不用打賞,喜歡請轉發和關注

感覺有用,歡迎關注我的公眾號,每週一篇原創技術文章

用 Node 抓站(一):怎麼寫出自己滿意的程式碼
關注三水清

相關文章