class Scheduler(object): def __init__(self, stats_collector): # 3.2.1-3: 调度器中接受统计器对象,使用统计器对象,对入对的请求数量和过滤掉的请求数量进行统计 self.stats_collector = stats_collector # 准备队列,来缓存请求对象 self.queue = Queue() # 2.0.2-5:统计总的请求数量 # self.total_request_count = 0 # 定义set集合,用于存储指纹数据 # 2.修改init方法,创建去重容器 self.filter_container = FilterContainer() # 定义变量,用于统计过滤掉多少请求 # self.filter_request_count = 0 def clear(self): """清除Redis中的指纹和请求数据""" if settings.SCHEDULE_PERSIST: # 清空redis队列中的数据 self.queue.clear() # 清空指纹数据 self.filter_container.clear() def add_request(self, request): # 3.2.1-4:使用stats_collector来统计总入对请求数量和过滤掉的请求数量 # 3.3.1-2:只有需要过滤.并且重复了,才需要过滤 if not request.dont_filter and self.seen_request(request): # 如果重复,就记录日志 logger.info('过滤掉重复请求{}'.format(request.url)) # self.filter_request_count += 1 self.stats_collector.incr_filter_request_count() return # 添加请求对象 self.queue.put(request) # print('endend..') # 2.0.2-5:每次添加请求的时候,就让total_request_count + 1 # 此时的请求数量为入对的请求数量 # self.total_request_count += 1 self.stats_collector.incr_total_request_count() def get_request(self): # 获取请求对象 return self.queue.get() def seen_request(self, request): # 用于爬虫请求是否已经被爬取过了 # 获取请求获取请求对应的指纹 fp = self._gen_fp(request) # 判断fp是否在容器中 if self.filter_container.exists(fp): # 返回True,说明这个请求重复 return True self.filter_container.add_fp(fp) # 返回False 说明这个请求不重复 return False def _gen_fp(self, request): """ 根据请求对象生成指纹 :param request: 请求对象 :return: 请求对应的指纹 思路: 1.明确需要使用哪些数据生成指纹 1.URL,方法名,params,data 2.准备数据 3.把数据添加到sha1算法中 4.通过sha1获取16进制的指纹 """ # 2.准备数据 # 对url进行规范化处理 url = canonicalize_url(request.url) # 方法名 method = request.method.upper() # GET的请求参数 params = request.params if request.params else {} # 但是字典是无序的,我们把他转换成元祖再排序 params = sorted(params.items(), key=lambda x: x[0]) # POST请求的请求体数据 data = request.data if request.data else {} # 但是字典是无序的,我们把他转换成元祖再排序 data = sorted(data.items(), key=lambda x: x[0]) # 获取sha1对象 sha1 = hashlib.sha1() # 更新数据 sha1.update(self.get_bytes_from_str(url)) sha1.update(self.get_bytes_from_str(method)) sha1.update(self.get_bytes_from_str(str(params))) sha1.update(self.get_bytes_from_str(str(data))) # 获取16进制的指纹数据 return sha1.hexdigest() # py3中字符串默认是str,str就是一个unicode的字符串 # py2中字符串str是二进制数据,解码后是unicode类型 def get_bytes_from_str(self, s): if six.PY3: # 如果是py3,如果是字符串str就进行编码 if isinstance(s, str): return s.encode('utf8') else: return s else: # py2,如果是str类型就直接返回,decode()之后变为unicode if isinstance(s, str): return s else: # 在py2中encode默认使用ascli码进行编码的,所以此处不能省 return s.decode('utf8')
class Scheduler(object): def __init__(self, stats_collector): # 3.2.1-3: 调度器中接收统计器对象, 使用统计器对象, 对入队请求数量和过滤掉请求数量进行统计 self.stats_collector = stats_collector # 准备队列, 来缓存请求对象 self.queue = Queue() # 2.0.2-5: 统计总的请求数量 # self.total_request_count = 0 # 定义set集合, 用于存储指纹数据 # 2. 修改init方法, 创建去重容器 self.filter_container = FilterContainer() # 定义变量, 用于统计过滤掉多少请求 # self.filter_reqeust_count = 0 def clear(self): """请求Redis中的指纹和请求数据""" if settings.SCHEDULE_PERSIST: # 清空Redis队列中数据 self.queue.clear() # 请求空指纹数据 self.filter_container.clear() def add_request(self, request): # 3.2.1-4: 使用stats_collector来通过入队请求数量和过滤掉请求数量 # 3.3.1-2: 只有需要过滤 并且 已经重复, 才过滤 if not request.dont_filter and self.seen_request(request): # 如果重复, 就记录日志 logger.info('过滤掉重复请求:{}'.format(request.url)) # self.filter_reqeust_count += 1 self.stats_collector.incr_filter_request_count() return # print(request.url) # 添加请求对象 self.queue.put(request) # print('添加请求:{}'.format(request.url)) # 2.0.2-5: 每次添加请求的时候, 就让total_request_count增加1 # 此处请求数量, 为入队请求数量 # self.total_request_count += 1 self.stats_collector.incr_total_request_count() def get_request(self): # print("获取请求") # 获取请求对象 req = self.queue.get() # print("取出了:{}".format(req.url)) return req def seen_request(self, request): # 用于爬虫请求是否已经爬取过了, 待实现 # 根据请求获取该请求对应指纹 fp = self._gen_fp(request) # 判断fp是否在容器中 if self.filter_container.exists(fp): # 返回True,说明这个请求重复了 return True # 如果不重复就来到这里, 把指纹添加过滤容器中 self.filter_container.add_fp(fp) # 返回False, 就表示不重复 return False def _gen_fp(self, request): """ 根据请求对象, 生成指纹 :param request: 请求对象 :return: 请求对应指纹 思路: 1. 明确需要使用那些数据生成指纹 1. URL,方法名,params,data 2. 准备数据 3. 把数据添加到sha1算法中 4. 通过sha1获取16进制的指纹 """ # 2. 准备数据 # 对URL进行规范化处理 url = canonicalize_url(request.url) # 方法名 method = request.method.upper() # GET的请求参数 params = request.params if request.params else {} # 但是字典是无序, 我们把转换为元祖在排序 params = sorted(params.items(), key=lambda x: x[0]) # POST请求的请求体数据 data = request.data if request.data else {} # 但是字典是无序, 我们把转换为元祖在排序 data = sorted(data.items(), key=lambda x: x[0]) # 3. 获取sha1算法对象 sha1 = hashlib.sha1() # 更新数据 sha1.update(self.get_bytes_from_str(url)) sha1.update(self.get_bytes_from_str(method)) sha1.update(self.get_bytes_from_str(str(params))) sha1.update(self.get_bytes_from_str(str(data))) # 获取十六进制的指纹数据 return sha1.hexdigest() def get_bytes_from_str(self, s): if six.PY3: # 如果是py3, 如果是str类型就需要进行编码 if isinstance(s, str): return s.encode('utf8') else: return s else: # 如果是py2, 如果是str类型就直接返回 if isinstance(s, str): return s else: # 在py2中encode默认使用ascii码进行编码的,此处不能省 return s.encode('utf8')
class Scheduler(object): def __init__(self, stats_collector): # 创建队列, 用于缓存请求对象 self.queue = Queue() # 创建一个去重容器: 使用set集合 self.filter_container = FilterContainer() # 接收引擎传递过来统计器对象 # 用于统计总请求数量和重复的请求数量 self.stats_collector = stats_collector def clear(self): # 如果是分布时候, 清空请求队列和指纹容器 if settings.SCHEDULER_PERSIST: # 清空请求队列 self.queue.clear() # 清空指纹容器 self.filter_container.clear() def add_request(self, request): # 如果该请求需要被过滤 并且 重复了, 才过滤该请求 if not request.dont_filter and self.filter_request(request): # 统计被过滤掉请求数量 # 让统计器中repeat_request_nums_key对应值增加1 self.stats_collector.incr( self.stats_collector.repeat_request_nums_key) # 如果请求已经存在, 就不在入队了 return # 如果dont_filter为true就会直接入队 # 如果来到这里, 说明该请求不重复. # 把请求对象, 添加到队列中 self.queue.put(request) # 每次添加一个请求, 到队列中, 就让请求总数量增加1 self.stats_collector.incr(self.stats_collector.request_nums_key) def get_request(self): # 从队列中, 取出请求对象, 并返回 return self.queue.get() def filter_request(self, request): # 实现请求去重, 如果该请求需要过滤就返回True, 否则返回False # 1. 把请求对象生成一个指纹 fp = self.__gen_fp(request) # 2. 如果指纹fp, 在去重容器中, 说明该请求已经存在了, 此时返回True if self.filter_container.exists(fp): logger.info("被过滤掉请求: {}".format(request.url)) return True # 3. 如果代码能来到这里, 说明这个指纹在指纹容器中不存在 # 把该指纹添加到指纹容器中 self.filter_container.add_fp(fp) # 返回False, 说明该请求是一个全新请求 return False def __gen_fp(self, request): """ 把请求对象生成一个指纹字符串 :param request: 请求对象 :return: 指纹字符串 """ # 1. 请求方法 method = request.method.upper() # 2. 请求的URL, 对URL进行规范化处理 url = canonicalize_url(request.url) # 3. 请求参数: params, 对字典参数进行排序 params = sorted(request.params.items(), key=lambda x: x[0]) # 4. 请求体 data = sorted(request.data.items(), key=lambda x: x[0]) # 创建sha1算法对象 sha1 = hashlib.sha1() # 向sha1算法中添加需要生成指纹的数据 # 把方法名添加到sha1算法中 sha1.update(self.__str_to_bytes(method)) # 把请求的URL, 添加到sha1算法中 sha1.update(self.__str_to_bytes(url)) # 把请求的params参数, 添加到sha1算法中 sha1.update(self.__str_to_bytes(str(params))) # 把请求的data, 添加到sha1算法中 sha1.update(self.__str_to_bytes(str(data))) # 使用sha1算法中数据, 生成一个指纹字符串 return sha1.hexdigest() def __str_to_bytes(self, s): if six.PY3: # 如果是python3: str类型: Unicode的字符串, bytes类型: 二进制 return s.encode('utf8') if isinstance(s, str) else s else: # 如果是python2: str类型: 是二进制数据, unicode类型: 是字符串; # python2: 默认编码方式 ASCII码, 所以此处必须指定 return s if isinstance(s, str) else s.encode('utf8')
class Scheduler(object): """调度器 1. 缓存请求 2. 请求去重 """ def __init__(self, stats_collector): # 接收传递过来的统计器对象 self.stats_collector = stats_collector # 创建队列对象,用于缓存请求 self.queue = Queue() # 创建指纹容器,用于存储指纹数据 self.__filter_container = FilterContainer() def add_request(self, request): """添加请求到请求对列中""" # 如果需要过滤,并且是重复请求才过滤 if not request.dont_filter and self.__filter_request(request): # 如果请求需要过滤,记录日志,直接返回 logger.info('过滤掉了重复的请求:%s' % request.url) self.stats_collector.incr( self.stats_collector.repeat_request_nums_key) return self.queue.put(request) # 每添加一次请求,就让总请求数量加1 self.stats_collector.incr(self.stats_collector.request_nums_key) def get_request(self): # 从队列中获取请求,并返回请求 return self.queue.get() def __filter_request(self, request): """ 请求去重 :return: 如果是True,说明这个请求重复了,需要过滤,否则不用过滤 """ # 1.获取请求对应指纹 fp = self.__get_fp(request) # 如果指纹在容器里,说明这个请求已经重复了 if self.__filter_container.exists(fp): return True # 能来到这里,说明这个请求是全新的,把指纹添加到指纹容器中 self.__filter_container.add_fp(fp) return False def __get_fp(self, request): """ 生成请求对应指纹 :param request: 请求对象 :return: 指纹 """ # 创建sha1算法对象 s = sha1() # 添加请求方法名到sha1算法中 s.update(self.__to_bytes(request.method.upper())) # 添加请求URL名,到sha1算法中 s.update(self.__to_bytes(canonicalize_url(request.url))) # 添加请求参数名到sha1算法中 # 对请求参数进行排序 params = sorted(request.params.items(), key=lambda x: x[0]) s.update(self.__to_bytes(str(params))) # 添加请求体名导sha1算法中 data = sorted(request.data.items(), key=lambda x: x[0]) s.update(self.__to_bytes(str(data))) return s.hexdigest() @staticmethod def __to_bytes(i): """无论是py2还是py3,都把字符串转换为二进制""" if six.PY2: # py2中,str类型是二进制数据,unicode类型是字符串,默认使用ASCII编码 return i if isinstance(i, str) else i.encode('utf8') else: # py3中,str类型是字符串,bytes是二进制类型 return i.encode('utf8') if isinstance(i, str) else i def clear(self): """当分布式的时候,清空指纹和请求对列""" if settings.SCHEDULER_PERSIST: # 清空请求队列 self.queue.clear() # 清空指纹数据 self.__filter_container.clear()