scrapy去重与scrapy_redis去重与布隆过滤器

栏目: 编程工具 · 发布时间: 5年前

内容简介:在开始介绍scrapy的去重之前,先想想我们是怎么对requests对去重的。requests只是下载器,本身并没有提供去重功能。所以我们需要自己去做。很典型的做法是事先定义一个去重队列,判断抓取的url是否在其中,如下:此时的集合是保存在内存中的,随着爬虫抓取内容变多,该集合会越来越大,有什么办法呢?

scrapy去重与scrapy_redis去重与布隆过滤器

这是崔斯特的第九十一篇原创文章

在开始介绍scrapy的去重之前,先想想我们是怎么对requests对去重的。requests只是下载器,本身并没有提供去重功能。所以我们需要自己去做。很典型的做法是事先定义一个去重队列,判断抓取的url是否在其中,如下:

crawled_urls = set()


def check_url(url):
    if url not in crawled_urls:
        return True
    return False

此时的集合是保存在内存中的,随着爬虫抓取内容变多,该集合会越来越大,有什么办法呢?

接着往下看,你会知道的。

scrapy的去重

scrapy对request不做去重很简单,只需要在request对象中设置 dont_filter 为True,如

yield scrapy.Request(url, callback=self.get_response, dont_filter=True)

看看源码是如何做的, 位置

_fingerprint_cache = weakref.WeakKeyDictionary()
def request_fingerprint(request, include_headers=None):
    if include_headers:
        include_headers = tuple(to_bytes(h.lower())
                                 for h in sorted(include_headers))
    cache = _fingerprint_cache.setdefault(request, {})
    if include_headers not in cache:
        fp = hashlib.sha1()
        fp.update(to_bytes(request.method))
        fp.update(to_bytes(canonicalize_url(request.url)))
        fp.update(request.body or b'')
        if include_headers:
            for hdr in include_headers:
                if hdr in request.headers:
                    fp.update(hdr)
                    for v in request.headers.getlist(hdr):
                        fp.update(v)
        cache[include_headers] = fp.hexdigest()
    return cache[include_headers]

注释过多,我就删掉了。谷歌翻译 + 人翻

返回请求指纹

请求指纹是唯一标识请求指向的资源的哈希。 例如,请使用以下两个网址:

http://www.example.com/query?id=111&cat=222
http://www.example.com/query?cat=222&id=111

即使这两个不同的URL都指向相同的资源并且是等价的(即,它们应该返回相同的响应)

另一个例子是用于存储会话ID的cookie。 假设以下页面仅可供经过身份验证的用户访问:

http://www.example.com/members/offers.html

许多网站使用cookie来存储会话ID,这会随机添加字段到HTTP请求,因此在计算时应该被忽略指纹。

因此,计算时默认会忽略request headers。 如果要包含特定headers,请使用include_headers参数,它是要计算Request headers的列表。

其实就是说:scrapy使用sha1算法,对每一个request对象加密,生成40为十六进制数,如:’fad8cefa4d6198af8cb1dcf46add2941b4d32d78’。

我们看源码,重点是一下三行

fp = hashlib.sha1()
fp.update(to_bytes(request.method))
fp.update(to_bytes(canonicalize_url(request.url)))
fp.update(request.body or b'')

如果没有自定义headers,只计算method、url、和二进制body,我们来计算下,代码:

print(request_fingerprint(scrapy.Request('http://www.example.com/query?id=111&cat=222')))
print(request_fingerprint(scrapy.Request('http://www.example.com/query?cat=222&id=111')))
print(request_fingerprint(scrapy.Request('http://www.example.com/query')))

输出:

fad8cefa4d6198af8cb1dcf46add2941b4d32d78
fad8cefa4d6198af8cb1dcf46add2941b4d32d78
b64c43a23f5e8b99e19990ce07b75c295165a923

可以看到第一条和第二条的密码是一样的,是因为调用了 canonicalize_url 方法,该方法返回如下

>>>import w3lib.url
>>>
>>># sorting query arguments
>>>w3lib.url.canonicalize_url('http://www.example.com/do?c=3&b=5&b=2&a=50')
'http://www.example.com/do?a=50&b=2&b=5&c=3'
>>>
>>># UTF-8 conversion + percent-encoding of non-ASCII characters
>>>w3lib.url.canonicalize_url(u'http://www.example.com/r\u00e9sum\u00e9')
'http://www.example.com/r%C3%A9sum%C3%A9'
>>>

scrapy的去重默认会保存到内存中,如果任务重启,会导致内存中所有去重队列消失

scrapy-redis的去重

scrapy-redis重写了scrapy的调度器和去重队列,所以需要在settings中修改如下两列

# Enables scheduling storing requests queue in redis.
SCHEDULER = "scrapy_redis.scheduler.Scheduler"

# Ensure all spiders share same duplicates filter through redis.
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

一般我们会在 redis 中看到这两个,分别是去重队列和种子链接

scrapy去重与scrapy_redis去重与布隆过滤器

先看看代码: 重要代码

def request_seen(self, request):
    """Returns True if request was already seen.
    Parameters
    ----------
    request : scrapy.http.Request
    Returns
    -------
    bool
    """
    fp = self.request_fingerprint(request)
    # This returns the number of values added, zero if already exists.
    added = self.server.sadd(self.key, fp)
    return added == 0

def request_fingerprint(self, request):
    """Returns a fingerprint for a given request.
    Parameters
    ----------
    request : scrapy.http.Request
    Returns
    -------
    str
    """
    return request_fingerprint(request)

首先拿到scrapy.http.Request会先调用self.request_fingerprint去计算,也就是scrapy的sha1算法去加密,然后会向redis中添加该指纹。

该函数的作用是:计算该请求指纹,添加到redis的去重队列,如果已经存在该指纹,返回True。

我们可以看到,只要有在settings中添加 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" ,就会在redis中新加一列去重队列,说下这样做的优劣势:

  1. 优点:将内存中的去重队列序列化到redis中,及时爬虫重启或者关闭,也可以再次使用,你可以使用SCHEDULER_PERSIST来调整缓存
  2. 缺点:如果你需要去重的指纹过大,redis占用空间过大。8GB=8589934592Bytes,平均一个去重指纹40Bytes,约可以存储214,748,000个(2亿)。所以在做关系网络爬虫中,序列化到redis中可能并不是很好,保存在内存中也不好,所以就产生了布隆过滤器。

布隆过滤器

它的原理是将一个元素通过 k 个哈希函数,将元素映射为 k 个比特位,在 bitmap 中把它们置为 1。在验证的时候只需要验证这些比特位是否都是 1 即可,如果其中有一个为 0,那么元素一定不在集合里,如果全为 1,则很可能在集合里。(因为可能会有其它的元素也映射到相应的比特位上)

同时这也导致不能从 Bloom filter 中删除某个元素,无法确定这个元素一定在集合中。以及带来了误报的问题,当里面的数据越来越多,这个 可能 在集合中的靠谱程度就越来越低。(由于哈希碰撞,可能导致把不属于集合内的元素认为属于该集合)

scrapy去重与scrapy_redis去重与布隆过滤器

布隆过滤器的缺点是错判,就是说,不在里面的,可能误判成在里面,但是在里面的,就一定在里面,而且无法删除其中数据。

>>>import pybloomfilter
>>>fruit = pybloomfilter.BloomFilter(100000, 0.1, '/tmp/words.bloom')
>>>fruit.update(('apple', 'pear', 'orange', 'apple'))
>>>len(fruit)
3
>>>'mike' in fruit
False
>>>'apple' in fruit
True

python3使用 pybloomfilter 的例子。

那么如何在scrapy中使用布隆过滤器呢,崔大大已经写好了,地址: ScrapyRedisBloomFilter ,已经打包好,可以直接安装

pip install scrapy-redis-bloomfilter

在settings中这样配置:

# Ensure use this Scheduler
SCHEDULER = "scrapy_redis_bloomfilter.scheduler.Scheduler"

# Ensure all spiders share same duplicates filter through redis
DUPEFILTER_CLASS = "scrapy_redis_bloomfilter.dupefilter.RFPDupeFilter"

# Redis URL
REDIS_URL = 'redis://localhost:6379/0'

# Number of Hash Functions to use, defaults to 6
BLOOMFILTER_HASH_NUMBER = 6

# Redis Memory Bit of Bloomfilter Usage, 30 means 2^30 = 128MB, defaults to 30
BLOOMFILTER_BIT = 30

# Persist
SCHEDULER_PERSIST = True

其实也是修改了调度器与去重方法,有兴趣的可以了解下。


以上所述就是小编给大家介绍的《scrapy去重与scrapy_redis去重与布隆过滤器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构、算法与应用(原书第2版)

数据结构、算法与应用(原书第2版)

Sartaj Sahni / 王立柱、刘志红 / 机械工业出版社 / 2015-4 / 79.00元

《数据结构、算法与应用——C++语言描述》是享有盛誉的数据结构教科书的第2版。它完整地包含了基本数据结构的内容,是CS2课程的理想用书。作者Sartaj Sahni通过循循善诱的讲解、直观具体的讨论和基于现实的应用,让读者轻松、愉快地学习。新版书着重利用标准模板库(STL),把书中开发的数据结构和算法与相应的STL实现方法相互关联。本书还增加了很多新的实例和练习题。 书中的应用实例是它的特色......一起来看看 《数据结构、算法与应用(原书第2版)》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试