尝试使用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部分可以使用以下类型。
-
- 文字列型
-
- リスト型(文字列の配列)
-
- セット型(重複なしの配列)
-
- ソート済みセット型(順番ありのセット型)
- ハッシュ型(連想配列)
确认动作
这次我打算尝试使用集合类型作为任务列表。步骤如下所示。
-
- 将URL列表添加到集合中
-
- 获取集合的副本并进行循环
- 如果处理成功,则从集合中删除项
以下是一個簡單的測試(使用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来玩耍和赚钱