Scrapy-Redis中的Dupefilter无法按预期工作

时间:2017-05-04 15:34:29

标签: python redis scrapy

我有兴趣使用Scrapy-Redis在Redis中存储已删除的项目。特别是,Redis-based request duplicates filter似乎是一个有用的功能。

首先,我按照https://doc.scrapy.org/en/latest/intro/tutorial.html#extracting-data-in-our-spider对蜘蛛进行了调整,如下所示:

import scrapy
from tutorial.items import QuoteItem

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
        'http://quotes.toscrape.com/page/2/',
    ]

    custom_settings = {'SCHEDULER': 'scrapy_redis.scheduler.Scheduler',
                       'DUPEFILTER_CLASS': 'scrapy_redis.dupefilter.RFPDupeFilter',
                       'ITEM_PIPELINES': {'scrapy_redis.pipelines.RedisPipeline': 300}}

    def parse(self, response):
        for quote in response.css('div.quote'):
            item = QuoteItem()
            item['text'] = quote.css('span.text::text').extract_first()
            item['author'] = quote.css('small.author::text').extract_first()
            item['tags'] = quote.css('div.tags a.tag::text').extract()
            yield item

我在命令行使用scrapy startproject tutorial生成项目,并在QuoteItem中将items.py定义为

import scrapy

class QuoteItem(scrapy.Item):
    text = scrapy.Field()
    author = scrapy.Field()
    tags = scrapy.Field()

基本上,我已经在" Usage"中实施了设置。 settings per-spider中自述文件的一部分,并使蜘蛛yield成为Item对象而不是常规Python字典。 (我认为有必要触发Item Pipeline)。

现在,如果我从命令行使用scrapy crawl quotes抓取蜘蛛,然后执行redis-cli,我会看到quotes:items密钥:

127.0.0.1:6379> keys *
1) "quotes:items"

这是一个长度为20的列表:

127.0.0.1:6379> llen quotes:items
(integer) 20

如果我再次运行scrapy crawl quotes,则列表的长度加倍为40:

127.0.0.1:6379> llen quotes:items
(integer) 40

但是,我希望quotes:items的长度仍然是20,因为我只是重新删除相同的页面。我在这里做错了吗?

2 个答案:

答案 0 :(得分:2)

Scrapy-redis不会自动过滤重复的项目。

(请求)dupefilter是关于爬行的请求。您想要的似乎与deltafetch中间件类似:https://github.com/scrapy-plugins/scrapy-deltafetch

您需要调整deltafetch以使用分布式存储,也许redis'位图功能适合这种情况。

答案 1 :(得分:0)

以下是我最终解决问题的方法。首先,正如我在另一个问题How to implement a custom dupefilter in Scrapy?中指出的那样,使用start_urls类变量会导致start_requests的实现,其中产生的Request对象具有dont_filter=True。要禁用此功能并使用默认的dont_filter=False,我直接实施了start_requests

import scrapy
from tutorial.items import QuoteItem

class QuotesSpider(scrapy.Spider):
    name = "quotes"

    custom_settings = {
                       'SCHEDULER': 'scrapy_redis.scheduler.Scheduler',
                       'DUPEFILTER_CLASS': 'tutorial.dupefilter.RedisDupeFilter',
                       'ITEM_PIPELINES': {'scrapy_redis.pipelines.RedisPipeline': 300}
                       }

    def start_requests(self):
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        for quote in response.css('div.quote'):
            item = QuoteItem()
            item['text'] = quote.css('span.text::text').extract_first()
            item['author'] = quote.css('small.author::text').extract_first()
            item['tags'] = quote.css('div.tags a.tag::text').extract()
            yield item

其次,正如Rolando所指出的那样,指纹不会默认持续存在于不同的抓取中。为了实现这一点,我将Scrapy-Redis'RFPDupeFilter类子类化为

import scrapy_redis.dupefilter
from scrapy_redis.connection import get_redis_from_settings


class RedisDupeFilter(scrapy_redis.dupefilter.RFPDupeFilter):
    @classmethod
    def from_settings(cls, settings):
        server = get_redis_from_settings(settings)
        key = "URLs_seen"                               # Use a fixed key instead of one containing a timestamp
        debug = settings.getbool('DUPEFILTER_DEBUG')
        return cls(server=server, key=key, debug=debug)

    def request_seen(self, request):
        added = self.server.sadd(self.key, request.url)
        return added == 0

    def clear(self):
        pass                                            # Don't delete the key from Redis

主要区别在于:(1)key设置为固定值(不包含时间戳)和(2)clear方法,在Scrapy-Redis的实现中删除来自Redis的key被有效禁用。

现在,当我第二次运行scrapy crawl quotes时,我看到了预期的日志输出

2017-05-05 15:13:46 [scrapy_redis.dupefilter] DEBUG: Filtered duplicate request <GET http://quotes.toscrape.com/page/1/> - no more duplicates will be shown (see DUPEFILTER_DEBUG to show all duplicates)

并且没有物品被刮掉。