专栏首页0x0001Node.js 抓取数据过程的进度保持

Node.js 抓取数据过程的进度保持

最近自己有个批量调用 API 抓取数据的需求,类似爬虫抓数据的感觉。听到爬虫二字,我们常常想到的是 Python, Beautiful Soup 之流,而对于简单地抓取数据这种需求来说,一个小米加步枪就能干掉的东西,拉个加农炮来,显得有些大材小用。实际上,只需要围绕着 抓取->格式转换处理->保存 这简单三步,然后用合适的工具或编程语言实现就好了。

驱动整个批量抓取过程的核心在于一个循环,把所有要访问的 URL 放在一个数组,循环遍历一下。对于我这样搞前端的来说,结合现代 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));
  }
})();

简简单单一个循环,就可以解决这个问题,但问题来了,万一中途出错退出,再次启动,脚本得重头开始跑,这显然有点不够智能,有没有办法实现在程序中断过后再次启动时让程序恢复上次的进度?

想起 SICP 讲到的递归与迭代的思维。迭代,实际上是用固定数目的状态变量表示当前程序的状态的计算过程。迭代计算过程中,程序根据之前设定好的规则从一个状态转移到下一个状态,直到状态不再满足某个设定条件才结束。实现上来说,“迭代”二字指的是用来表示状态的变量的迭代更新。由此可见,我们的关注点应该聚焦在状态(state)上,for 循环本身也是服务于迭代计算过程的一种语法糖而已。

于是我们很容易可以看出,这个简单循环过程所迭代更新的状态变量只有 current,代表当前抓取的 URL 在数组的位置。这个变量存在于内存,而内存中的状态随着程序的中止而消失,所以关键在于如何把这个状态固定到磁盘或数据库等地方。这里能想到的思路是,在程序启动时把状态加载进来,在状态更新的同时把它固定下来。

在这里,我把这个状态变量序列化成 JSON,然后存储到文件,实现状态的固定。

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

对于本文这个小需求来说,这样做已经够用,但扩展一下之后,还是有一些问题的,当状态变得复杂,需要更多的状态变量表示的时候,可能会导致持久化的语句遍布整个迭代过程中的每一个涉及到状态改变的地方,代码的可读性也降低了很多,让人不容易抓住重点。有没有什么办法把这些操作集中起来?想到了 Vue.js 的 MVVM 模型,它可以通过监视一个 Object 的变化而驱动视图的变化,或许我们可以实现类似的一些监听和触发机制,在变化的时候实现保存呢?

搜索发现,ES6 的 Proxy 可以满足这个需求,通过 Proxy 对象,把真正用来保存状态的对象包裹起来,只要定义一个 set 方法,在接到对象的改变的请求的时候,加入这个持久化操作就好了。另外,由于可能有多级的 Object 的存在,所以也对子对象递归加入 Proxy 的监控。

// 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;

然后把循环里面的 current 换成 state.current,小爬虫就可以放飞自我,随意中止,再也不用担心跑的过程出问题而需要重来了~

当然,这里的 saveState 的实现可以很多样,不一定要写入文件,还可以改成 Redis, Sqlite 什么的。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Typecho1.0使用CDN后获取真实ip的方法

    看来开发团队是考虑到了这个问题的,只是没有说出来而已。。typecho的文档还需完善啊⊙▽⊙虽然说代码注释是最好的文档,但是对于我们这些半吊子菜鸟来说还是挺需要...

    zgq354
  • 在网页中提取链接的“三板斧”

    宿舍有个树莓派常年开着吃灰,装了 Transmission 用来挂种子。由于挂下来的资源大部分都是视频类型,所以也不必下载下来。于是我配置了一个开了 autoi...

    zgq354
  • 第一个插件Html5AudioPlayer

    前几天逛博客的时候看着Wordpress可以插个挺漂亮的h5播放器进文章。。感觉十分不爽,Typecho似乎并没有这种功能,在网上找了好久都没发现Typecho...

    zgq354
  • vue中router与route

    $router对象是全局路由的实例,是router构造方法的实例,主要是实现路由跳转。 路由实例方法: 1、push

    刘亦枫
  • 行业报告 | 2017-2018《创客中国》创业者生存状态报告

    数据猿
  • 【DB笔试面试402】​下列哪个参数用于确定是否要导入整个导出文件()

    逻辑导入工具imp或数据泵impdp中导入全库采用的是full参数。所以,本题的答案为C。

    小麦苗DBA宝典
  • 基于区块链技术的数据共享赋能AI驱动网络

    人工智能和机器学习算法的最新发展为网络自动化提供了动力。最近,移动网络运营商(MNO)正在使用以人工智能为基础的模块,通过在其租用/自有区域内授权的数据进行网络...

    区块链大本营
  • openfst 1.6.1编译过程中失败的修改(short-path.h)

    近期想升级一下KALDI这个程序,在编译openfst 1.6.1过程中,发现这个编译过程已经和原来的不太一样。 网上没有可参照的地方。 只能从原始的文档上进行...

    sparkexpert
  • C++反汇编第六讲,认识C++中的Try catch语法,以及在反汇编中还原

          C++反汇编第六讲,认识C++中的Try catch语法,以及在反汇编中还原 我们以前讲SEH异常处理的时候已经说过了,C++中的Try catch...

    IBinary
  • 共享学习:蚂蚁金服提出全新数据孤岛解决方案

    随着人工智能的兴起,数据的质量和数量,已经成为影响机器学习模型效果最重要的因素之一,因此通过数据共享的模式来「扩展」数据量、从而提升模型效果的诉求也变得越发强烈...

    用户2769421

扫码关注云+社区

领取腾讯云代金券