尝试使用redis来管理任务列表

如果只需要本题的答案,请跳过这一块内容。

我几乎每天都从某个网站上爬取数据,并将其存入数据库中。有时一天要获取超过300页的数据。

作为网络爬虫的一种方式,我会意识到“不要随意访问”,因此我决定将请求的时间间隔设定为大约2秒。

换句话说,如果从300页开始提取,至少需要300×2=600秒=10分钟的时间。

首先,创建一个包含300个URL的任务列表,并按顺序进行访问。

如果在途中通信中断或URL输入错误,将会产生错误并需要重新尝试。

在进行再次处理时,只需处理未获取的URL,并省略任务清单中已成功的项。

由于数据库的设计更改,以前有一个名为LastUpdate的字段,我们以此作为参考进行重新获取,但现在无法再使用相同的方法。

我思考了一种解决这个问题的方法,即任务清单的管理方式。

    • 別にタスクリスト専用のtableを作ってDBで管理する

 

    タスクリスト専用のファイルを使って読み書きする

这样的地方可以吗?

然而,任务清单是一次性使用的。无论是在数据库还是文件中丢弃它都很麻烦。而且,我不敢将已保存在数据库中的内容删除。

因此,我听说过这个名字但从未使用过的Redis,决定尝试一下。

想做的事情

fetchWithUrl()という関数があるとします。引数にURLを渡すとスクレイピングしてDB保存する関数です

urlListというタスクリストがあるとします。複数のURLが入っている配列です。

urlListをloopして fetchWithUrl()に渡していきます。

fetchWithUrl()が成功したら urlListからurlを削除します。
この一連のプロセスが異常終了した時の為に urlListを半永続化させたい

为了实现持久性,我们将使用Redis。

安装程序

如果你在Mac上安装了Homebrew,你可以使用一个命令来进行安装。

brew install redis

在macOS(Sierra)上,用5分钟创建Redis环境的步骤 | 语法错误。

我正在使用Node.js,但模块的安装只需要一行代码。

npm install redis

Redis (红软)

Redis的数据结构

Redis是一种键值存储(Key-Value Store)的数据库,意味着可以通过key: value的形式来设置值。

value部分可以使用以下类型。

    • 文字列型

 

    • リスト型(文字列の配列)

 

    • セット型(重複なしの配列)

 

    • ソート済みセット型(順番ありのセット型)

 

    ハッシュ型(連想配列)

确认动作

这次我打算尝试使用集合类型作为任务列表。步骤如下所示。

    1. 将URL列表添加到集合中

 

    1. 获取集合的副本并进行循环

 

    如果处理成功,则从集合中删除项

以下是一個簡單的測試(使用Node.js和CoffeeScript)。

为了进行异步处理,使用了async。

async_ = require 'async'

describe "redis", ->
  it "test",(done) ->
    key = 'testSet'
    redis = require('redis').createClient()
    redis.sadd key,['a','b','c'],(err)->
      throw err if err
      redis.sinter key,(err,arr)->
        throw err if err
        console.log arr
        async_.eachSeries arr, (item, next) ->
          redis.srem key,item,(err)->
            redis.sinter key,(err,newArr)->
              throw err if err
              console.log newArr
              next()
        , (err)->
          done()

# [ 'a', 'b', 'c' ]
# [ 'b', 'c' ]
# [ 'c' ]
# []

由于嵌套太严重,我将尝试用async.waterfall重新绘制。

 async_ = require 'async'

 describe "redis", ->
  it "test",(done) ->
    key = 'testSet'
    redis = require('redis').createClient()
    async_.waterfall [
      (next)-> redis.sadd key,['a','b','c'],(err,res)->
        assert.isNull(err)
        next()
      (next)-> redis.sinter key,(err,arr)->
        assert.isNull(err)
        console.log arr
        next(null,arr)
      (arr,next)-> async_.eachSeries arr, (item, next_) ->
        redis.srem key,item,(err)->
          redis.sinter key,(err,newArr)->
            assert.isNull(err)
            console.log newArr
            next_()
      ,(err)->next()
    ],(err)->done()

# [ 'a', 'b', 'c' ]
# [ 'b', 'c' ]
# [ 'c' ]
# []

在上述中,我们使用了以下的Redis方法。

添加(key, member,callback)

给key添加一个成员(类型为set)

烧结(钥匙,回调函数)

使用指定的键获取集合对象。

在强调回调函数的情况下,从键名为key的有序集合中移除成员为member的元素。

使用给定的key和member来删除set中的元素。


最初先在任务列表中使用sadd添加[‘a’,’b’,’c’]。

使用async_.eachSeries函数来遍历并获取该项(从任务列表中传递并进行个别处理)。

如果个别处理顺利完成,那么就使用srem删除项目,这就是我的想法。

下一个选项是将item设为b的情况下不删除它。这可以被理解为个别处理失败的情景。

describe "redis", ->

  it "test",(done) ->
    key = 'testSet'
    redis = require('redis').createClient()
    async_.waterfall [
      (next)-> redis.sadd key,['a','b','c'],(err,res)->
        assert.isNull(err)
        next()
      (next)-> redis.sinter key,(err,arr)->
        assert.isNull(err)
        console.log arr
        next(null,arr)
      (arr,next)-> async_.eachSeries arr, (item, next_) ->
        # ここを修正
        if item isnt 'b'
          redis.srem key,item,(err)->
            redis.sinter key,(err,newArr)->
              assert.isNull(err)
              console.log newArr
              next_()
        else
          next_()
      ,(err)->next()
    ],(err)->done()

# [ 'a', 'b', 'c' ]
# [ 'b', 'c' ]
# [ 'b' ]

因为b的情况下未执行srem操作,所以仍然留在任务列表中。


由於執行了上述代碼並且暫時結束了進程,假設任務列表是一個變量,那麼它應該已經消失了。但由於是redis,它應該仍然存在。

从上述代码中去除sadd的部分和srem的部分,然后再次运行一遍。

describe "redis", ->

  it "test",(done) ->
    key = 'testSet'
    redis = require('redis').createClient()
    async_.waterfall [
      # (next)-> redis.sadd key,['a','b','c'],(err,res)->
      #   assert.isNull(err)
      #   next()
      (next)-> redis.sinter key,(err,arr)->
        assert.isNull(err)
        console.log arr
        next(null,arr)
      # (arr,next)-> async_.eachSeries arr, (item, next_) ->
      #   if item isnt 'b'
      #     redis.srem key,item,(err)->
      #       redis.sinter key,(err,newArr)->
      #         assert.isNull(err)
      #         console.log newArr
      #         next_()
      #   else
      #     next_()
      # ,(err)->next()
    ],(err)->done()

# [ 'b' ]

B仍然留下了。

这种方式看起来适合用在任务清单上。

综述

听说Redis在Twitter上也被使用已经有一段时间了,但是我对该如何使用它一直不太清楚,因此以前从未使用过。

这一次我尝试了一下[即使进程结束也能保持的变量]的感觉,但出乎意料地很方便。

我特别喜欢使用只有一行代码的redis = require(‘redis’).createClient()进行设置完成。

然而,也许有比Redis更新更方便的技术存在,如果您了解的话,能告诉我就非常高兴了。

请参考以下网站

NodeRedis/node_redis: 用于 Node.js 的 Redis 客户端

Redis 2.0.3 文档 — 命令参考指南

用Node.js数据库:使用Redis来玩耍和赚钱

广告
将在 10 秒后关闭
bannerAds