当咱们在爬取网页的时候可能会遇到一个调转链接会在不一样页面出现,这个时候若是咱们的爬虫程序不能识别出python
该连接是已经爬取过的话,就会形成一种重复没必要要的爬取。因此咱们要对咱们即将要爬取的网页进行过滤,把重数据库
复的网页连接过滤掉。后端
去重处理能够避免将重复性的数据保存到数据库中以形成大量的冗余性数据。不要在得到爬虫的结果后进行内容过框架
滤,这样作只不过是避免后端数据库出现重复数据。scrapy
去重处理对于一次性爬取是有效的,但对于增量式爬网则偏偏相反。对于持续性长的增量式爬网,应该进行"前置过ide
滤",这样能够有效地减小爬虫出动的次数。在发出请求以前检查询爬虫是否曾爬取过该URL,若是已爬取过,则让爬函数
虫直接跳过该请求以免重复出动爬虫。url
Scrapy 提供了一个很好的请求指纹过滤器(Request Fingerprint duplicates filter)
debug
scrapy.dupefilters.ReppupeFilter ,当它被启用后,会自动记录全部成功返回响应的请求的URL,并将其以文件日志
(requests.seen)
方式保存在项目目录中。请求指纹过滤器的原理是为每一个URL生成一个指纹并记录下来,一旦
当前请求的URL在指纹库中有记录,就自动跳过该请求。
默认状况下这个过滤器是自动启用的,固然也能够根据自身的需求编写自定义的过滤器。
默认过滤器开启的地方:
def start_requests(self): for url in self.start_urls: # 每个url封装成Request对象,交给调度器 # 这里的dont_filter=True 就是默认开启scrapy自带的过滤器 yield Request(url, dont_filter=True)
这是一个基类,scrapy自带的指纹过滤器就是继承这个类,而后重写这些方法实现的。
若是你常常看源码的话,你话发现不少功能的实现都有一个最基础的基类,而后实现功能的那个类,继承它
并对他的方法进行重写。scrapy框架中就是这样的。
class BaseDupeFilter: # 基本的过滤器 @classmethod def from_settings(cls, settings): """这个方法能够从settings.py中获取数据""" return cls() def request_seen(self, request): # 对 request 去重的方法 return False def open(self): # can return deferred 爬虫打开的时候 pass def close(self, reason): # can return a deferred 爬虫关闭的时候 pass def log(self, request, spider): # log that a request has been filtered 爬虫的日志 pass
继承BaseDupeFilter类,而后对内部重写。
class RFPDupeFilter(BaseDupeFilter): """Request Fingerprint duplicates filter 指纹过滤器 对整个request的去重""" # 默认的话是一个指纹过滤器, 会对整个request对象进行过滤 (url/method/params...) def __init__(self, path=None, debug=False): self.file = None # 内存型的集合 存在于内存 self.fingerprints = set() self.logdupes = True self.debug = debug self.logger = logging.getLogger(__name__) if path: self.file = open(os.path.join(path, 'requests.seen'), 'a+') # 打开文件 self.file.seek(0) self.fingerprints.update(x.rstrip() for x in self.file) # 更新文件 @classmethod def from_settings(cls, settings): # 从配置文件中取到要应用的过滤器。 debug = settings.getbool('DUPEFILTER_DEBUG') return cls(job_dir(settings), debug) def request_seen(self, request): # 拿到request 传到 request_fingerprint方法 摘要出来的指纹 fp fp = self.request_fingerprint(request) # 若是指纹在集合中 if fp in self.fingerprints: # 返回 True return True # 不在就追加到集合 self.fingerprints.add(fp) if self.file: # 指纹写入到文件 self.file.write(fp + '\n') def request_fingerprint(self, request): # 返回请求生成的指纹 return request_fingerprint(request) def close(self, reason): # 爬虫结束关闭存放指纹的文件 if self.file: self.file.close() def log(self, request, spider): # 爬虫日志 if self.debug: msg = "Filtered duplicate request: %(request)s (referer: %(referer)s)" args = {'request': request, 'referer': referer_str(request)} self.logger.debug(msg, args, extra={'spider': spider}) elif self.logdupes: msg = ("Filtered duplicate request: %(request)s" " - no more duplicates will be shown" " (see DUPEFILTER_DEBUG to show all duplicates)") self.logger.debug(msg, {'request': request}, extra={'spider': spider}) self.logdupes = False spider.crawler.stats.inc_value('dupefilter/filtered', spider=spider)
执行流程:
细心的小伙伴可能发现了,这个和pipeline类源码的执行流程差很少,对没错就是差很少。
因为 scrapy.dupefilters.RFPDupeFilter 采用文件方式保存指纹库,对于增量爬取且只用于短时间运行的项目还能
应对。一旦遇到爬取量巨大的场景时,这个过滤器就显得不太适用了,由于指纹库文件会变得愈来愈大,过滤器在启动时会一次性将指纹库中全部的URL读入,致使消耗大量内存。
因此咱们状况下,在使用scrapy过滤器的时候,都是本身从新自定义。
虽然自带的过滤器很差用,可是咱们能够用Scrapy提供的 request_fingerprint
函数为请求生成指纹,而后将
指纹写入内存,这样会从内存中存取数据会很快。而后写好的这个类,位置能够随便放,可是必定要在settings.py
文件中重新指定过滤器。
# 这里是放在了当前项目的中间件里面了. DUPEFILTER_CLASS = 'qd_04_english.middlewares.URLFilter'
# 过滤器先启动,再执行爬虫 import hashlib from scrapy.dupefilters import BaseDupeFilter class URLFilter(BaseDupeFilter): """根据URL过滤""" @classmethod def from_settings(cls, settings): # 从settings里面取到配置文件 debug = settings.getbool('DUPEFILTER_DEBUG') return cls() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 过滤url的集合 self.url_set = set() def request_seen(self, request): """对每个请求进行过滤""" url = self.request_fingerprint(request) if url in self.url_set: # 返回True就表明这个url已经被请求过了 return True else: self.url_set.add(request.url) def request_fingerprint(self, request): # 返回由url摘要后的字符串 return hashlib.md5(request.url.encode()).hexdigest()
注意:start_urls 中的请求,默认是不过滤的。
以前咱们在管道中,讲到的数据去重,是对结果的去重,这里咱们讲的过滤是对请求的去重。
必定必定要会看源码,会自定义一些组件。由于自带的公司通常都不会用的,由于很差用。