scrapy默认去重

 

作者:乌尔班
链接:https://www.zhihu.com/question/19793879/answer/312467126
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

增量爬取,一般两类情况:1.一个网站出现了新的页面,2.一个老页面内容变更了。

无论哪一种,增量的前提都是已经存下已经爬取好的信息(至于哪些要存,下面说),当出现新的情况时,重复数据不会抓取,直接抓取新的东西。


第一种,一个网站出现了新页面

1.在分析之前,先看一下scrapy的去重策略:

scrapy通过request_fingerprint函数,对Request对象生成指纹,看注释:

# 该函数在scrapy/utils/request.py文件中
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()
        """计算指纹时,请求方法(如GET、POST)被计算在内"""
        fp.update(to_bytes(request.method)) 

        """下面这句有意思,canonicalize_url()将url规范化,意味着
          http://www.example.com/query?id=111&cat=222
          http://www.example.com/query?cat=222&id=111
        这样参数位置变化,但参数值不变的网址,表示的仍是同一个网址,符合现实逻辑。
         """
        fp.update(to_bytes(canonicalize_url(request.url)))

        """request.body的属性是字符串:
        一般GET方法的body为空字符串,不考虑;
        而POST方法要上传一个字典data(类型是dict),
        要经过urllib.parse.urlencode()函数转换后才能变成request.body
       """
        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]
"""我们甚至可以根据需求将request.meta的内容作为指纹计算的一部分"""

scrapy生成的唯一指纹,存在内存的一个集合里,即set。如果下一次请求产生的指纹在这个set里面,请求被判定为重复,这次请求就被忽略,也就是所谓的去重了。

从上面可以可出,scrapy认为,如果url/POST data/method都一致,这个请求就是重复的,这适合绝大多数情况。

需要提一下:上述的处理方式,意味着想要变更request的指纹就要改变request,即是在downloaderMiddleware的process_request方法中变更。

 

2.下面就是举例了

一个网站,本来一共有10页,过段时间之后变成了100页。假设,已经爬取了前10页,为了增量爬取,我们现在只想爬取第11-100页。

因此,为了增量爬取,我们需要将前10页请求的指纹保存下来。

以下命令是将内存中的set里指纹保存到本地硬盘的一种方式。

scrapy crawl somespider -s JOBDIR=crawls/somespider-1

但还有更常用的,是将scrapy中的指纹存在一个redis数据库中,这个操作已经有造好轮子了,即scrapy-redis库。

scrapy-redis库将指纹保存在了redis数据库中,是可以持久保存的。(基于此,还可以实现分布式爬虫,那是另外一个用途了)

scrapy-redis库不仅存储了已请求的指纹,还存储了带爬取的请求,这样无论这个爬虫如何重启,每次scrapy从redis中读取要爬取的队列,将爬取后的指纹存在redis中。如果要爬取的页面的指纹在redis中就忽略,不在就爬取。

第二种,一个老页面内容变更了(不适用的情况来了)

1.爬取修改中的博客这种情况。

刚开始爬时,博客写了一段就交了,后来某天,这个博客又续写了,这就是网页url没变,内容变了的情况,一般这种网页不常抓。

根据增量爬取的思路,需要保存老页面的信息,如果再次访问这个页面,内容变化,就更新内容即可。

有个小TIP,比如一个页面是文字,很大,可以将文字哈希,下一次爬取的页面哈希值与上一次相同就说明页面没有更改。

2.ajax页面中,一个页面的内容是不断更新的。

比如豆瓣TOP100 的电影之类的,url/参数/请求方法 可能都一样,但内容是变更的。

ajax请求获得的json文件一般会有一个唯一id,如果没有可以自己造一个,将唯一id记录在redis数据库中,如果这个json返回值已经爬取过,丢弃即可。一般没有办法直接通过fingerprint过滤,总是要加入其它一些人为的考虑在里面。

因为是将东西爬取下来之后才去重,意味着此步骤在pipeline的process_item方法中进行。

### Scrapy 内置中间件的使用与配置 Scrapy 是一个高层次的 Python 爬虫框架,用于抓取 Web 站点并提取结构化数据[^1]。为了防止爬虫复抓取相同的 URL,Scrapy 提供了内置的机制,通过 `DUPEFILTER_CLASS` 配置项实现。默认情况下,Scrapy 使用基于内存的过滤器 `scrapy.dupefilters.RFPDupeFilter`,它可以有效避免复请求。 #### 配置中间件 在 Scrapy 的 `settings.py` 文件中,可以通过以下配置项启用或自定义中间件: - **DUPEFILTER_CLASS** 默认值为 `scrapy.dupefilters.RFPDupeFilter`,表示使用默认过滤器。如果需要替换为其他实现(如基于 Redis 的过滤器),可以修改此值。例如,使用 `scrapy_redis.dupefilter.RFPDupeFilter` 来实现分布式爬虫中的功能[^4]。 ```python # settings.py DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter' # 默认值 ``` - **DUPEFILTER_DEBUG** 设置为 `True` 可以在日志中输出被过滤掉的复请求信息,方便调试。默认值为 `False`。 ```python # settings.py DUPEFILTER_DEBUG = True ``` #### 自定义逻辑 如果需要扩展或修改默认逻辑,可以通过继承 `RFPDupeFilter` 类来实现自定义的过滤器。以下是一个简单的示例: ```python from scrapy.dupefilters import RFPDupeFilter class CustomDupeFilter(RFPDupeFilter): def request_fingerprint(self, request): # 自定义指纹生成逻辑 return super().request_fingerprint(request) + request.meta.get('custom_key', '') ``` 然后在 `settings.py` 中指定自定义的过滤器: ```python # settings.py DUPEFILTER_CLASS = 'myproject.dupefilters.CustomDupeFilter' ``` #### 基于 Redis 的分布式 对于分布式爬虫场景,可以结合 `scrapy-redis` 扩展实现基于 Redis 的功能。具体步骤如下: 1. 安装 `scrapy-redis` 扩展: ```bash pip install scrapy-redis ``` 2. 修改 `settings.py` 配置文件,启用 Redis 过滤器: ```python # settings.py DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' SCHEDULER = 'scrapy_redis.scheduler.Scheduler' SCHEDULER_PERSIST = True REDIS_URL = 'redis://localhost:6379' # Redis 连接地址 ``` 3. 如果需要进一步优化性能,可以结合布隆过滤器(Bloom Filter)实现高效的功能[^3]。以下是一个示例: ```python from scrapy_redis_bloomfilter.dupefilter import RFPDupeFilter as BloomDupeFilter from scrapy.utils.project import get_project_settings class CustomBloomDupeFilter(BloomDupeFilter): def __init__(self, server, key, debug=False, bit=20, hash_number=6): super().__init__(server, key, debug, bit, hash_number) # 在 settings.py 中指定自定义的布隆过滤器 DUPEFILTER_CLASS = 'myproject.dupefilters.CustomBloomDupeFilter' BLOOMFILTER_BIT = 20 BLOOMFILTER_HASH_NUMBER = 6 ``` #### 注意事项 - 默认过滤器基于内存实现,适合单机爬虫场景。对于分布式爬虫,建议使用基于 Redis 的方案。 - 如果爬取网站 URL 数量非常庞大,可以考虑使用布隆过滤器来减少内存占用和提高性能[^4]。 - 自定义逻辑时,需确保生成的请求指纹(Request Fingerprint)能够唯一标识每个请求。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值