Scrapy-redis分布式爬虫详解

1. 分布式爬虫原理

Scrapy单机爬虫有一个本地爬取队列Queue,如果新的Request生成就会放到队列里面,随后Request被Scheduler调度,之后Request交给Downloader执行。分布式爬虫有多个Scheduler和多个Downloader,而爬取队列始终为一个,也就是共享爬取队列,这样才能保证Scheduler从队列里调度某个Request之后,其他的Scheduler不会重复调取此Request,就可以做到多个Scheduler同步爬取。
我们需要做的就是在多台主机上同时运行爬虫任务,共享一个爬取队列,各台主机有自己的Scheduler和Downloader,这个共享的爬取队列就是使用Redis来完成的。

2.scrapy-redis 源码解析

Github地址:https://github.com/rmax/scrapy-redis/tree/master/src
组件
各个组件功能介绍。
#####2.1 connection.py
负责根据setting中配置实例化redis连接。被dupefilter和scheduler调用,总之涉及到redis存取的都要使用到这个模块。

# 这里引入了redis模块,这个是redis-python库的接口,用于通过python访问redis数据库,
# 这个文件主要是实现连接redis数据库的功能,这些连接接口在其他文件中经常被用到

import redis
import six

from scrapy.utils.misc import load_object

DEFAULT_REDIS_CLS = redis.StrictRedis

# 可以在settings文件中配置套接字的超时时间、等待时间等
# Sane connection defaults.
DEFAULT_PARAMS = {
    'socket_timeout': 30,
    'socket_connect_timeout': 30,
    'retry_on_timeout': True,
}

# 要想连接到redis数据库,和其他数据库差不多,需要一个ip地址、端口号、用户名密码(可选)和一个整形的数据库编号
# Shortcut maps 'setting name' -> 'parmater name'.
SETTINGS_PARAMS_MAP = {
    'REDIS_URL': 'url',
    'REDIS_HOST': 'host',
    'REDIS_PORT': 'port',
}


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
    ``DEFAULT_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_PARAMS : dict, optional
        Additional client parameters.
    """
    params = DEFAULT_PARAMS.copy()
    params.update(settings.getdict('REDIS_PARAMS'))
    # XXX: Deprecate REDIS_* settings.
    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'])

    # 返回的是redis库的Redis对象,可以直接用来进行数据操作的对象
    return get_redis(**params)


# Backwards compatible alias.
from_settings = get_redis_from_settings


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 = kwargs.pop('redis_cls', DEFAULT_REDIS_CLS)
    url = kwargs.pop('url', None)


    if url:
        return redis_cls.from_url(url, **kwargs)
    else:
        return redis_cls(**kwargs)

#####2.2 duperfilter.py
负责执行Request的去重。Scrapy单机去重的过程就是利用集合元素的不重复性来实现,有一个request_fingerprint()方法就是Request指纹的方法,其内部使用hashlib的sha1()方法,计算的字段包括Request的Method,URL,Body,Headers这几部分内容,只要有一点不同那么计算的结果就不一样,计算得到的是加密后的字符串,也就是指纹。每个Request都有一个独有的指纹,判定字符串重复比判定Request对象是否重复要简单得多,所以指纹可以作为Request是否重复得依据。
Scrapy去重得实现:

def __init__(self):
    self.fingerprints =set()
def request_seen(self, request):
    fp = self.request_fingerprint(request)
    if fp  in self.fingerprints:
        return True
    self.fingerprints.add(fp)

检测指纹是否存在于fingerprints变量中,该变量为一个集合,如果指纹存在就返回True,否则把这个指纹加入到集合中。
对于分布式爬虫来说,我们可以利用redis得集合作为指纹集合,那么这样去重集合也是利用Redis共享的。每台主机将新生成的Request指纹与集合对比,如果指纹已经存在,就说明Request是重复的。这样,利用同样的原理不同的存储结构就实现了分布式Request的去重。
duperfilter.py

import logging
import time

from scrapy.dupefilters import BaseDupeFilter
from scrapy.utils.request import request_fingerprint

from .connection import get_redis_from_settings


DEFAULT_DUPEFILTER_KEY = "dupefilter:%(timestamp)s"

logger = logging.getLogger(__name__)


# TODO: Renam
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值