增量爬取和去重
增量爬取
當(dāng)一個站點有數(shù)據(jù)更新的時候赃绊,需要進行增量爬取杆逗,通常有以下集中情況
某個特定頁面數(shù)據(jù)更新
新增了頁面
情況1的時候霜瘪,我們對此特定頁面的內(nèi)容做哈希喧半,當(dāng)然要去除動態(tài)變化的那一部分,比如有的頁面有驗證碼或者日期材义,程序定期執(zhí)行均抽,在執(zhí)行的最開始檢測此頁面的哈希值跟上次抓取是否有變化,如果有變化就開始抓取其掂。
情況2的時候油挥,我們會對頁面入口內(nèi)容做哈希,并且存儲分頁面的URL哈希值款熬,如果頁面入口哈希值發(fā)生變化深寥,獲取新增的頁面url列表,在這里需要用到url的去重贤牛,和數(shù)據(jù)去重類似惋鹅,采用redis集合類型處理。
redis集合類型不允許添加重復(fù)的數(shù)據(jù)殉簸,當(dāng)添加重復(fù)的時候時闰集,返回0沽讹,并且添加失敗。我們將所有的url list存入redis集合武鲁,當(dāng)頁面入口變化時爽雄,進行頁面url去重,只抓取新增的頁面沐鼠。
爬取結(jié)果去重
結(jié)果去重常用的有兩種方法:
- 布隆過濾器
- redis集合
布隆過濾器
其中布隆過濾器是通過寫文件的方式盲链,多個進程使用需要添加同步和互斥,較為繁瑣迟杂,不推薦多線程/進程的時候使用,另外寫文件是磁盤I/O操作本慕,耗費時間長排拷,可以累積到一定數(shù)量再一次寫入,或者利用上下文管理器在程序結(jié)束或異常退出時一次性寫入锅尘。
class Spider(object):
def __init():
# 布容過濾器初始化
self.burongname = 'test.bl'
if not os.path.isfile(self.burongname):
self.bl = BloomFilter(capacity=100000, error_rate=0.000001)
else:
with open(self.burongname, 'rb') as f:
self.bl = BloomFilter.fromfile(f)
def __enter__(self):
u"""
上下文管理器進入入口
"""
return self
def __exit__(self, *args):
u"""
上下文管理器监氢,退出出口
"""
if self.conn is not None:
self.conn.close()
with open(self.burongname, 'wb') as f:
self.fingerprints.tofile(f)
def get_infos(self):
"""
抓取主函數(shù)
"""
# 布隆過濾器使用部分, x為抓取到得數(shù)據(jù)
x = json.dumps(i)
if x not in self.bl:
self.bl.add(x)
if __name__ == '__main__':
with Spider() as MSS:
MSS.get_infos()
上下文管理器,在主函數(shù)執(zhí)行之前執(zhí)行 def enter ,在程序運行結(jié)束或異常退出時執(zhí)行def exit藤违, 上下文管理器還可以用來統(tǒng)計程序執(zhí)行的時間浪腐。
redis集合
使用redis集合去重能夠支持多線程多進程.
利用redis集合無重復(fù)數(shù)據(jù)的特點,在redis建立集合顿乒,往其中添加數(shù)據(jù)的sha1值议街,添加成功返回1,表示無重復(fù)璧榄,添加失敗返回0特漩,表示集合中已經(jīng)有重復(fù)數(shù)據(jù)
使用: 步驟:1. 建立redis連接池 2. 重復(fù)檢查
下面的例子是接口,并提供example骨杂。
[Redis]
server=192.168.0.100
pass=123@123
# -*- coding:utf-8 -*-
"""
File Name : 'distinct'.py
Description:
Author: 'chengwei'
Date: '2016/6/2' '11:45'
python: 2.7.10
"""
import sys
import hashlib
import os
import codecs
import ConfigParser
import redis
reload(sys)
sys.setdefaultencoding('utf-8')
"""
利用redis的集合不允許添加重復(fù)元素來進行去重
"""
def example():
pool, r = redis_init()
temp_str = "aaaaaaaaa"
result = check_repeate(r, temp_str, 'test:test')
if result == 0:
# do what you want to do
print u"重復(fù)"
else:
# do what you want to do
print u"不重復(fù)"
redis_close(pool)
def redis_init(parasecname="Redis"):
"""
初始化redis
:param parasecname:
:return: redis連接池
"""
cur_script_dir = os.path.split(os.path.realpath(__file__))[0]
cfg_path = os.path.join(cur_script_dir, "db.conf")
cfg_reder = ConfigParser.ConfigParser()
secname = parasecname
cfg_reder.readfp(codecs.open(cfg_path, "r", "utf_8"))
redis_host = cfg_reder.get(secname, "server")
redis_pass = cfg_reder.get(secname, "pass")
# redis
pool = redis.ConnectionPool(host=redis_host, port=6379, db=0, password=redis_pass)
r = redis.Redis(connection_pool=pool)
return pool, r
def sha1(x):
sha1obj = hashlib.sha1()
sha1obj.update(x)
hash_value = sha1obj.hexdigest()
return hash_value
def check_repeate(r, check_str, set_name):
"""
向redis集合中添加元素涂身,重復(fù)則返回0,不重復(fù)則添加成功搓蚪,并返回1
:param r:redis連接
:param check_str:被添加的字符串
:param set_name:項目所使用的集合名稱蛤售,建議如下格式:”projectname:task_remove_repeate“
:return:
"""
hash_value = sha1(check_str)
result = r.sadd(set_name, hash_value)
return result
def redis_close(pool):
"""
釋放redis連接池
:param pool:
:return:
"""
pool.disconnect()
if __name__ == '__main__':
example()