为什么要自定义过滤规则呢? 首先,我们需要过滤,但是不是说抓一次就不抓了,因为我们的抓取是一段时间抓取一次
自定义策略如下:
首先我试图直接继承RFPDupeFilter
在settings.py同级的目录下新建dupefilter.py文件,按照网上说的方法,写了内容如下
from scrapy.dupefilter import RFPDupeFilter
import hashlib
from scrapy.utils.request import request_fingerprint
from scrapy.dupefilter import BaseDupeFilter
class URLFilter(RFPDupeFilter):
def __init(self):
RFPDupeFilter.__init__(self)
def request_seen(self, request):
fp = self.request_fingerprint(request)
added = self.server.sadd(self.key, fp)
return added == 0
在settings.py中添加
DUPEFILTER_CLASS = 'CrawlBaiduMobile.dupefilter.URLFilter'
但是启动spider会报如下错误:
ValueError: ("Failed to instantiate dupefilter class '%s': %s", 'CrawlBaiduMobile.dupefilter.URLFilter', TypeError("__init__() got an unexpected keyword argument 'debug'",))
或者报
ValueError: ("Failed to instantiate dupefilter class '%s': %s", 'CrawlBaiduMobile.dupefilter.URLFilter', TypeError("__init__() got an unexpected keyword argument 'key'",))
含义其实也很明显, 调用RFPDupeFilter的构造方法时出错了,可能是我的版本比较新吧。
于是我找到了RFPDupeFilter的源码,写了一个基本和它一致的, 但是过滤策略有些不同
from scrapy_redis.dupefilter import RFPDupeFilter
from scrapy_redis.connection import get_redis_from_settings
from scrapy.dupefilters import BaseDupeFilter
from scrapy.utils.request import request_fingerprint
from scrapy_redis import defaults
import hashlib
class URLFilter(BaseDupeFilter):
def __init__(self, server, key, debug=False):
self.server = server
self.key = key
self.debug = debug
self.logdupes = True
self.timeout = 60*60*24*10 #设置过期时间10天,10天内重复的url不重复抓取
@classmethod
def from_settings(cls, settings):
server = get_redis_from_settings(settings)
key = defaults.DUPEFILTER_KEY % {'timestamp': int(time.time())}
debug = settings.getbool('DUPEFILTER_DEBUG')
return cls(server, key=key, debug=debug)
@classmethod
def from_crawler(cls, crawler):
return cls.from_settings(crawler.settings)
def request_seen(self, request):
key = self.request_fingerprint(request)
if self.server.get(key):
return True
else:
self.server.set(key, 1, self.timeout)
def request_fingerprint(self, request):
return hashlib.md5(request.url).hexdigest()
我依然用了redis存储缓存key,但是用的字符串,没有再用有序集合,因为我们的策略是一段时间内不重复抓取,所以需要给抓取过的key设置一个缓存时间
此外,缓存key的生成方式也做了修订,改用了md5算法生成了一个40位的key
在实验过程中发现一个问题,start_urls中的url没有进这个过滤器
首先看下start_urls中的初始url是如何被构造成Request的
def start_requests(self):
for url in self.start_urls:
yield self.make_requests_from_url(url)
def make_requests_from_url(self, url):
return Request(url, dont_filter=True)
在start_requests中会将start_urls里面的url(当然也可以是redis中的),通过make_requests_from_url方法构造成Request对象
但是传参dont_filter=True,也就是不经过过滤器的,所以默认情况下,初始start_urls中的url不会经过上面配置的过滤器
可以在spider中覆写这个方法, 设置dont_filter=False:
class CrawlBaiduMobile(RedisCrawlSpider):
#some other code
def make_requests_from_url(self, url):
return Request(url, dont_filter=False)
此外,图片下载的Request也没有进这个过滤器
配置页面中的图片下载:
class CrawlImagePipeline(ImagesPipeline):
def get_media_requests(self, item, info):
if isinstance(item, CrawlImageItem):
for image_url in item['image_urls']:
self.default_headers['referer'] = image_url
yield Request(image_url, headers=self.default_headers, dont_filter=False)
这里也用了yield Request, 但是这个Request不会进入到上面配置的过滤器中, 即使给了dont_filter=False,当然更具体是什么原因,我没有深究了, 因为我们的系统对于抓取图片有不同的过滤规则,的确也不需要经过这个过滤器
图片下载应该是有一套自己的排重策略,就是用过期时间,可以在settings.py中配置,
IMAGES_EXPIRES = 90 #90天内重复的图片不会再下载, 如果服务重启了,之前保存的下载过的图片会重新下载