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

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

2019-06-05 15:42:00 8363

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

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

// 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));
  }
})();

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

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

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

在這里,我把這個(gè)狀態(tài)變量序列化成 JSON,然后存儲(chǔ)到文件,實(shí)現(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));
  }
})();

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

搜索發(fā)現(xiàn),ES6 的 Proxy 可以滿足這個(gè)需求,通過 Proxy 對(duì)象,把真正用來保存狀態(tài)的對(duì)象包裹起來,只要定義一個(gè) set 方法,在接到對(duì)象的改變的請(qǐng)求的時(shí)候,加入這個(gè)持久化操作就好了。另外,由于可能有多級(jí)的 Object 的存在,所以也對(duì)子對(duì)象遞歸加入 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 的實(shí)現(xiàn)可以很多樣,不一定要寫入文件,還可以改成 Redis, Sqlite 什么的。


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

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

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

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