用 Node 抓站(一):怎麼寫出本身滿意的代碼

若是隻寫怎麼抓取網頁,確定會被吐槽太水,知足不了讀者的逼格要求,因此本文會經過不斷的審視代碼,作到令本身滿意(擼碼也要不斷迸發新想法!html

本文目標:抓取什麼值得買網站國內優惠的最新商品,而且做爲對象輸出出來,方便後續入庫等操做git

抓取經常使用到的npm模塊

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

  • 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內容npm

cheerio 示例

var request = require('request')
var cheerio = require('cheerio')

cheerio.prototype.removeTagText = function () {
  var html = this.html()
  return html.replace(/<([\w\d]+)\b[^<]+?<\/\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\b.+?>閱讀全文<\/a>/g, '')
      var mall = $('.botPart a.mall', item).text().trim()

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

簡單解釋下,removeTagText是直接擴展了cheerio的一個方法,目的是去掉相似json

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

裏面span以後的文字。執行後獲得下面的結果:promise

怎麼寫出本身滿意的代碼

從上面需求來看,只須要提取列表頁面的商品信息,而取到數據以後,使用cheerio進行了解析,而後經過一些「選擇器」對數據進行「提取加工」,獲得想要的數據。數據結構

重點是選擇器提取加工,若是想要的字段多了,那麼代碼會越寫越多,維護困難,最重要的是「不環保」,今天抓什麼值得買,明天抓惠惠網,代碼還要copy一份改一改!一來二去,抓的越多,那麼代碼越亂,想一想哪天不用request了,是否是要挨個修改呢?因此要抓重點,從最後須要的數據結構入手,關注選擇器提取加工ide

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\b.+?>閱讀全文<\/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-
@三水清
未經容許,請勿轉載,不用打賞,喜歡請轉發和關注

感受有用,歡迎關注個人公衆號,每週一篇原創技術文章

關注三水清
相關文章
相關標籤/搜索