目录
spiders: 赋予 Scrapy spiders 远程调度
Connection————建立redis连接
SETTINGS_PARAMS_MAP: 用于将 Redis 参数名映射到 redis 库的参数名。
get_redis_from_settings 函数: 从 Scrapy 的 settings 对象获取连接 参数并调用
get_redis 建立 Redis 连接。
get_redis 函数: 辅助函数,从传入参数建立 Redis 连接。
代码:
import six
from scrapy.utils.misc import load_object
from . import defaults
# Shortcut maps 'setting name' -> 'parmater name'.
#这是一个参数映射,左边设置名称,右边是参数名称
SETTINGS_PARAMS_MAP = {
'REDIS_URL': 'url',
'REDIS_HOST': 'host',
'REDIS_PORT': 'port',
'REDIS_ENCODING': 'encoding',
}
#获取链接redis参数
def get_redis_from_settings(settings):
"""Returns a redis client instance from given Scrapy settings object.
This function uses ``get_client`` to instantiate the client and uses
``defaults.REDIS_PARAMS`` global as defaults values for the parameters. You
can override them using the ``REDIS_PARAMS`` setting.
Parameters
----------
settings : Settings
A scrapy settings object. See the supported settings below.
Returns
-------
server
Redis client instance.
Other Parameters
----------------
REDIS_URL : str, optional
Server connection URL.
REDIS_HOST : str, optional
Server host.
REDIS_PORT : str, optional
Server port.
REDIS_ENCODING : str, optional
Data encoding.
REDIS_PARAMS : dict, optional
Additional client parameters.
"""
#将默认的redis链接参数拷贝一份给params
params = defaults.REDIS_PARAMS.copy()
#如果setting中没有定义REDIS_PARAMS,则使用默认的参数
params.update(settings.getdict('REDIS_PARAMS'))
# XXX: Deprecate REDIS_* settings.
#将setting中的REDIS_URL,REDIS_HOST,REDIS_PORT,REDIS_ENCODING,更新进入params中,没有则使用默认的
for source, dest in SETTINGS_PARAMS_MAP.items():
val = settings.get(source)
if val:
params[dest] = val
# Allow ``redis_cls`` to be a path to a class.
if isinstance(params.get('redis_cls'), six.string_types):
params['redis_cls'] = load_object(params['redis_cls'])
#get_redis(**params) = redis_cls(from_setting) = redis链接
#from_setting() = get_redis_from_setting() = get_redis(**params) = redis_cls(**kwargs) = redis链接
return get_redis(**params)
# Backwards compatible alias.
from_settings = get_redis_from_settings
#获取redis的链接
def get_redis(**kwargs):
"""Returns a redis client instance.
Parameters
----------
redis_cls : class, optional
Defaults to ``redis.StrictRedis``.
url : str, optional
If given, ``redis_cls.from_url`` is used to instantiate the class.
**kwargs
Extra parameters to be passed to the ``redis_cls`` class.
Returns
-------
server
Redis client instance.
"""
#从参数字典中获取redis_cls,没有则使用默认的
#redis_cls = defaults.RENIS_CLS = Redis
redis_cls = kwargs.pop('redis_cls', defaults.REDIS_CLS)
url = kwargs.pop('url', None)
if url:
#使用url连接redis,优先使用
return redis_cls.from_url(url, **kwargs)
else:
#使用参数连接redis,Redis(**kwargs) 返回redis连接
return redis_cls(**kwargs)
defaults: 默认参数配置
Redis 连接参数
REDIS_CLS
REDIS_ENCODING
REDIS_PARAMS
PIPELINE_KEY: 数据存储在 Redis 的 key
SCHEDULER_*: 参与任务调度的 Redis key,包括 requests 存储,调度队 列类型选择, 去重指纹存储
START_URLS_*: 任务注入 Redis 的 key 和 数据类型
代码:
import redis
# For standalone use.
DUPEFILTER_KEY = 'dupefilter:%(timestamp)s'
PIPELINE_KEY = '%(spider)s:items'
REDIS_CLS = redis.StrictRedis
REDIS_ENCODING = 'utf-8'
# Sane connection defaults.
REDIS_PARAMS = {
'socket_timeout': 30,
'socket_connect_timeout': 30,
'retry_on_timeout': True,
'encoding': REDIS_ENCODING,
}
SCHEDULER_QUEUE_KEY = '%(spider)s:requests'
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter'
SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
START_URLS_KEY = '%(name)s:start_urls'
START_URLS_AS_SET = False
dupefilter: 实现 request 去重
RFPDupeFilter类继承来自Scrapy中的BaseDupeFilter类。
Scrapy去重采用的是集合实现的,Scrapy分布式中去重就要利用共享集合, 采用Redis 的集合数据结构。
request_seen()方法和Scrapy中的request_seen()方法相似。这里的集合 操作的是server 对象的sadd()方法操作。Scrapy中的是数据结构,这里 换成了数据库的存储方式。
鉴别重复的方式还是使用指纹,指纹依靠request_fingerprint()方法来获 取。获取指纹后 直接向集合中添加指纹,添加成功返回1,判定结果返 回False就是不重复。
这里完成利用Redis的集合完成指纹的记录和重复的验证。
### request对象什么时候入队
- dont_filter = True ,构造请求的时候,把dont_filter置为True,该 url会被反复抓取(url 地址对应的内容会更新的情况)
- 一个全新的url地址被抓到的时候,构造request请求
- url地址在start_urls中的时候,会入队,不管之前是否请求过
- 构造start_url地址的请求时候,dont_filter = True
```python
def enqueue_request(self, request):
if not request.dont_filter and self.df.request_seen(request):
# dont_filter=False Ture True request指纹已经存在 #不会 入队
# dont_filter=False Ture False request指纹已经存在 全新 的url #会入队
# dont_filter=Ture False #会入队
self.df.log(request, self.spider)
return False
self.queue.push(request) #入队
return True
```
### scrapy_redis去重方法
- 使用sha1加密request得到指纹
- 把指纹存在redis的集合中
- 下一次新来一个request,同样的方式生成指纹,判断指纹是否存在reids 的集合中
### 生成指纹
```python
fp = hashlib.sha1()
fp.update(to_bytes(request.method)) #请求方法
fp.update(to_bytes(canonicalize_url(request.url))) #url
fp.update(request.body or b'') #请求体
return fp.hexdigest()
```
### 判断数据是否存在redis的集合中,不存在插入
```python
added = self.server.sadd(self.key, fp)
return added != 0
代码:
import logging
import time
from scrapy.dupefilters import BaseDupeFilter
from scrapy.utils.request import request_fingerprint
from . import defaults
from .connection import get_redis_from_settings
logger = logging.getLogger(__name__)
# TODO: Rename class to RedisDupeFilter.
class RFPDupeFilter(BaseDupeFilter):
"""Redis-based request duplicates filter.
This class can also be used with default Scrapy's scheduler.
"""
logger = logger
def __init__(self, server, key, debug=False):
"""Initialize the duplicates filter.
Parameters
----------
server : redis.StrictRedis
The redis server instance.
key : str
Redis key Where to store fingerprints.
debug : bool, optional
Whether to log filtered requests.
"""
self.server = server
self.key = key
self.debug = debug
self.logdupes = True
#类方法 cls当前类本身 RFPDupeFilter
#初始化init中的值
@classmethod
def from_settings(cls, settings):
"""Returns an instance from given settings.
This uses by default the key ``dupefilter:<timestamp>``. When using the
``scrapy_redis.scheduler.Scheduler`` class, this method is not used as
it needs to pass the spider name in the key.
Parameters
----------
settings : scrapy.settings.Settings
Returns
-------
RFPDupeFilter
A RFPDupeFilter instance.
"""
#获取redis的链接 默认是0库
server = get_redis_from_settings(settings)
# XXX: This creates one-time key. needed to support to use this
# class as standalone dupefilter with scrapy's default scheduler
# if scrapy passes spider on open() method this wouldn't be needed
# TODO: Use SCRAPY_JOB env as default and fallback to timestamp.
#defaults.DUPEFILTER_KEY = 'dupefilter:%(timestamp)s' % {'timestamp': int(time.time())}
#获取过滤的key值 这个键里包含了唯一的时间戳
key = defaults.DUPEFILTER_KEY % {
'timestamp': int(time.time())}
#debug默认是false
debug = settings.getbool('DUPEFILTER_DEBUG')