国产欧美日韩第一页|日本一二三不卡视频|在线精品小视频,亚洲第一免费播放区,metcn人体亚洲一区,亚洲精品午夜视频

幫助中心 >  技術(shù)知識庫 >  網(wǎng)站相關(guān) >  程序開發(fā) >  Node.js 抓取數(shù)據(jù)過程的進(jìn)度保持

Node.js 抓取數(shù)據(jù)過程的進(jìn)度保持

2019-06-05 15:42:00 8364

有個批量調(diào)用 API 抓取數(shù)據(jù)的需求,類似爬蟲抓數(shù)據(jù)的感覺。聽到爬蟲二字,我們常常想到的是 Python, Beautiful Soup 之流,而對于簡單地抓取數(shù)據(jù)這種需求來說,一個小米加步槍就能干掉的東西,拉個加農(nóng)炮來,顯得有些大材小用。實際上,只需要圍繞著 抓取->格式轉(zhuǎn)換處理->保存 這簡單三步,然后用合適的工具或編程語言實現(xiàn)就好了。

驅(qū)動整個批量抓取過程的核心在于一個循環(huán),把所有要訪問的 URL 放在一個數(shù)組,循環(huán)遍歷一下。對于我這樣搞前端的來說,結(jié)合現(xiàn)代 JS 的 async/await 很容易就可以寫出類似下方的代碼(這里我用了 Axios 庫處理 HTTP 請求)。

// Input
let read = fs.readFileSync('url-list.txt', 'utf-8');
let urlList = read.split('\n');

(async () => {
  for (let current = 0; current < urlList.length; current++) {
    const url = urlList[current];
    console.log(current, url);
    // get
    let { data } = await Axios.get(url);
    fs.writeFileSync(`result/${url}`, JSON.stringify(data));
  }
})();

簡簡單單一個循環(huán),就可以解決這個問題,但問題來了,萬一中途出錯退出,再次啟動,腳本得重頭開始跑,這顯然有點不夠智能,有沒有辦法實現(xiàn)在程序中斷過后再次啟動時讓程序恢復(fù)上次的進(jìn)度?

想起 SICP 講到的遞歸與迭代的思維。迭代,實際上是用固定數(shù)目的狀態(tài)變量表示當(dāng)前程序的狀態(tài)的計算過程。迭代計算過程中,程序根據(jù)之前設(shè)定好的規(guī)則從一個狀態(tài)轉(zhuǎn)移到下一個狀態(tài),直到狀態(tài)不再滿足某個設(shè)定條件才結(jié)束。實現(xiàn)上來說,“迭代”二字指的是用來表示狀態(tài)的變量的迭代更新。由此可見,我們的關(guān)注點應(yīng)該聚焦在狀態(tài)(state)上,for 循環(huán)本身也是服務(wù)于迭代計算過程的一種語法糖而已。

于是我們很容易可以看出,這個簡單循環(huán)過程所迭代更新的狀態(tài)變量只有 current,代表當(dāng)前抓取的 URL 在數(shù)組的位置。這個變量存在于內(nèi)存,而內(nèi)存中的狀態(tài)隨著程序的中止而消失,所以關(guān)鍵在于如何把這個狀態(tài)固定到磁盤或數(shù)據(jù)庫等地方。這里能想到的思路是,在程序啟動時把狀態(tài)加載進(jìn)來,在狀態(tài)更新的同時把它固定下來。

在這里,我把這個狀態(tài)變量序列化成 JSON,然后存儲到文件,實現(xiàn)狀態(tài)的固定。

// Input
let read = fs.readFileSync('url-list.txt', 'utf-8');
let urlList = read.split('\n');
let current = JSON.parse(fs.readFileSync('state', 'utf-8'));

(async () => {
  for (; current < urlList.length; current++) {
    const url = urlList[current];
    console.log(current, url);
    // get
    let { data } = await Axios.get(url);
    fs.writeFileSync(`result/${url}`, JSON.stringify(data));
    // save state
    fs.writeFileSync(`state`, JSON.stringify(current));
  }
})();

對于本文這個小需求來說,這樣做已經(jīng)夠用,但擴(kuò)展一下之后,還是有一些問題的,當(dāng)狀態(tài)變得復(fù)雜,需要更多的狀態(tài)變量表示的時候,可能會導(dǎo)致持久化的語句遍布整個迭代過程中的每一個涉及到狀態(tài)改變的地方,代碼的可讀性也降低了很多,讓人不容易抓住重點。有沒有什么辦法把這些操作集中起來?想到了 Vue.js 的 MVVM 模型,它可以通過監(jiān)視一個 Object 的變化而驅(qū)動視圖的變化,或許我們可以實現(xiàn)類似的一些監(jiān)聽和觸發(fā)機(jī)制,在變化的時候?qū)崿F(xiàn)保存呢?

搜索發(fā)現(xiàn),ES6 的 Proxy 可以滿足這個需求,通過 Proxy 對象,把真正用來保存狀態(tài)的對象包裹起來,只要定義一個 set 方法,在接到對象的改變的請求的時候,加入這個持久化操作就好了。另外,由于可能有多級的 Object 的存在,所以也對子對象遞歸加入 Proxy 的監(jiān)控。

// save state
const Store = {
  fileName: 'state',
  _state: {},
  init: function () {
    if (fs.existsSync(this.fileName)) {
      let content = fs.readFileSync(this.fileName, 'utf-8');
      if (content) {
        this._state = JSON.parse(content);
      }
    }
    // state
    this.state = new Proxy(this._state, this.proxyHandler);
  },
  saveState: function () {
    // save
    fs.writeFileSync(this.fileName, JSON.stringify(this._state));
  },
  proxyHandler: {
    set: (target, key, value) => {
      // 遞歸 Proxy
      if (typeof value === "object") {
        value = new Proxy(value, this.proxyHandler);
      }
      target[key] = value;
      Store.saveState();
      return true;
    }
  }
};

Store.init();
const state = Store.state;

然后把循環(huán)里面的 current 換成 state.current,小爬蟲就可以放飛自我,隨意中止,再也不用擔(dān)心跑的過程出問題而需要重來了~

當(dāng)然,這里的 saveState 的實現(xiàn)可以很多樣,不一定要寫入文件,還可以改成 Redis, Sqlite 什么的。


提交成功!非常感謝您的反饋,我們會繼續(xù)努力做到更好!

這條文檔是否有幫助解決問題?

非常抱歉未能幫助到您。為了給您提供更好的服務(wù),我們很需要您進(jìn)一步的反饋信息:

在文檔使用中是否遇到以下問題: