我有兴趣使用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,因为我只是重新删除相同的页面。我在这里做错了吗?
答案 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)
并且没有物品被刮掉。