精华内容
下载资源
问答
  • scrapy-redis

    2019-11-08 09:17:32
    scrapy-redis 一、特点与构架 二、安装与使用 三、常用设置 四、redis中储存的数据

    scrapy-redis

    一、特点与构架

    在这里插入图片描述

    二、安装与使用

    在这里插入图片描述

    三、常用设置

    在这里插入图片描述

    四、redis中储存的数据

    在这里插入图片描述

    五、练习

    5.1 爬虫文件

    在这里插入图片描述

    5.2 settings文件

    在这里插入图片描述

    5.3 pipelines文件

    在这里插入图片描述

    5.4 效果展示

    在这里插入图片描述

    展开全文
  • Scrapy-redis

    2019-10-08 06:54:48
    scrapy-redis是一个基于redisscrapy组件,通过它可以快速实现简单分布式爬虫程序,该组件本质上提供了三大功能: scheduler - 调度器 dupefilter - URL去重规则(被调度器使用) pipeline - 数据持久化 ...

    Redis

    scrapy-redis是一个基于redis的scrapy组件,通过它可以快速实现简单分布式爬虫程序,该组件本质上提供了三大功能:

    • scheduler - 调度器
    • dupefilter - URL去重规则(被调度器使用)
    • pipeline   - 数据持久化

    安装与开启redis 

    1 #redis安装路劲:https://github.com/ServiceStack/redis-windows
    2 
    3 路劲+/redis-server /etc/redis/6379.conf
    View Code

    1.用redis去重url

    情况一:
    只用redis去重:
    # ############ 连接redis 信息 #################
    REDIS_HOST = '127.0.0.1'                            # 主机名
    REDIS_PORT = 6379                                   # 端口
    # REDIS_URL = 'redis://user:pass@hostname:9001'       # 连接URL(优先于以上配置)
    REDIS_PARAMS  = {}                                  # Redis连接参数             默认:REDIS_PARAMS = {'socket_timeout': 30,'socket_connect_timeout': 30,'retry_on_timeout': True,'encoding': REDIS_ENCODING,})
    # REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块  默认:redis.StrictRedis
    REDIS_ENCODING = "utf-8"
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    配置文件
    class RFPDupeFilter(BaseDupeFilter):
        def __init__(self, server, key, debug=False):
            self.server = server
            self.key = key
    
        @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):
    
            fp = self.request_fingerprint(request)
            # This returns the number of values added, zero if already exists.
            added = self.server.sadd(self.key, fp)
            return added == 0 
            # 添加成功返回1,失败返回0
    
        def request_fingerprint(self, request):
    
            return request_fingerprint(request)
    
        def close(self, reason=''):
        clear()
    
        def clear(self):
            self.server.delete(self.key)
    
            if self.debug:
                msg = "Filtered duplicate request: %(request)s"
                self.logger.debug(msg, {'request': request}, 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
    源码

    相对比scrapy的去重源码:

    1.scrapy去重规则是如何实现的?
    class RFPDupeFilter(BaseDupeFilter):
    
        """Request Fingerprint duplicates filter"""
    
        def __init__(self, path=None, debug=False):
            self.file = None
            self.fingerprints = set()
    
        @classmethod
        def from_settings(cls, settings):
            debug = settings.getbool('DUPEFILTER_DEBUG')
            return cls(job_dir(settings), debug)
    
        def request_seen(self, request):
                将request对象转换成唯一标识.
            fp = self.request_fingerprint(request)
    
              判断在集合中是否存在,如果存在返回True,标识已经访问过
            if fp in self.fingerprints:
                return True
              之前没有访问过,将url添加到记录中.
            self.fingerprints.add(fp)
            if self.file:
                self.file.write(fp + os.linesep)
        def request_fingerprint(self, request):
            return request_fingerprint(request)
    
        def close(self, reason):
            if self.file:
                self.file.close()
    1.scrapy去重规则是如何实现的?

     

    2.redis调度器

    scrapy-redis中的调度器是如何实现的?
    将请求通过pickle进行序列化,然后添加到redis:列表
    或者有序集合中.(根据配置文件)
    SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"

    去重+调度器
    # ############ 连接redis 信息 #################
    REDIS_HOST = '127.0.0.1'                            # 主机名
    REDIS_PORT = 6379                                   # 端口
    # REDIS_URL = 'redis://user:pass@hostname:9001'       # 连接URL(优先于以上配置)
    REDIS_PARAMS  = {}                                  # Redis连接参数             默认:REDIS_PARAMS = {'socket_timeout': 30,'socket_connect_timeout': 30,'retry_on_timeout': True,'encoding': REDIS_ENCODING,})
    # REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块  默认:redis.StrictRedis
    REDIS_ENCODING = "utf-8"
    
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    
    # 有引擎来执行:自定义调度器
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue'  # 默认使用优先级队列(默认广度优先),其他:PriorityQueue(有序集合),FifoQueue(列表)、LifoQueue(列表)
    SCHEDULER_QUEUE_KEY = '%(spider)s:requests'  # 调度器中请求存放在redis中的key
    SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"  # 对保存到redis中的数据进行序列化,默认使用pickle
    SCHEDULER_PERSIST = True  # 是否在关闭时候保留原来的调度器和去重记录,True=保留,False=清空
    SCHEDULER_FLUSH_ON_START = False  # 是否在开始之前清空 调度器和去重记录,True=清空,False=不清空
    # SCHEDULER_IDLE_BEFORE_CLOSE = 10  # 去调度器中获取数据时,如果为空,最多等待时间(最后没数据,未获取到)。
    SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter'  # 去重规则,在redis中保存时对应的key  chouti:dupefilter
    SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'  # 去重规则对应处理的类
    DUPEFILTER_DEBUG = False
    去重+调度器配置文件

     

    调度器的源码是如何实现的?

    主要是enqueue_request()

    next_request()两个方法

    import importlib
    import six
    
    from scrapy.utils.misc import load_object
    
    from . import connection, defaults
    
    
    # TODO: add SCRAPY_JOB support.
    redis调度器源码
    class Scheduler(object):
    
    
        def __init__(self, server,
                     persist=False,
                     flush_on_start=False,
                     queue_key=defaults.SCHEDULER_QUEUE_KEY,
                     queue_cls=defaults.SCHEDULER_QUEUE_CLASS,
                     dupefilter_key=defaults.SCHEDULER_DUPEFILTER_KEY,
                     dupefilter_cls=defaults.SCHEDULER_DUPEFILTER_CLASS,
                     idle_before_close=0,
                     serializer=None):
            if idle_before_close < 0:
                raise TypeError("idle_before_close cannot be negative")
    
            self.server = server
            self.persist = persist
            self.flush_on_start = flush_on_start
            self.queue_key = queue_key
            self.queue_cls = queue_cls
            self.dupefilter_cls = dupefilter_cls
            self.dupefilter_key = dupefilter_key
            self.idle_before_close = idle_before_close
            self.serializer = serializer
            self.stats = None
    
        def __len__(self):
            return len(self.queue)
    
        @classmethod
        def from_settings(cls, settings):
            kwargs = {
                'persist': settings.getbool('SCHEDULER_PERSIST'),
                'flush_on_start': settings.getbool('SCHEDULER_FLUSH_ON_START'),
                'idle_before_close': settings.getint('SCHEDULER_IDLE_BEFORE_CLOSE'),
            }
    
            # If these values are missing, it means we want to use the defaults.
            optional = {
                # TODO: Use custom prefixes for this settings to note that are
                # specific to scrapy-redis.
                'queue_key': 'SCHEDULER_QUEUE_KEY',
                'queue_cls': 'SCHEDULER_QUEUE_CLASS',
                'dupefilter_key': 'SCHEDULER_DUPEFILTER_KEY',
                # We use the default setting name to keep compatibility.
                'dupefilter_cls': 'DUPEFILTER_CLASS',
                'serializer': 'SCHEDULER_SERIALIZER',
            }
            for name, setting_name in optional.items():
                val = settings.get(setting_name)
                if val:
                    kwargs[name] = val
    
            # Support serializer as a path to a module.
            if isinstance(kwargs.get('serializer'), six.string_types):
                kwargs['serializer'] = importlib.import_module(kwargs['serializer'])
    
            server = connection.from_settings(settings)
            # Ensure the connection is working.
            server.ping()
    
            return cls(server=server, **kwargs)
    
        @classmethod
        def from_crawler(cls, crawler):
            instance = cls.from_settings(crawler.settings)
            # FIXME: for now, stats are only supported from this constructor
            instance.stats = crawler.stats
            return instance
    
        def open(self, spider):
            self.spider = spider
    
            try:
                self.queue = load_object(self.queue_cls)(
                    server=self.server,
                    spider=spider,
                    key=self.queue_key % {'spider': spider.name},
                    serializer=self.serializer,
                )
            except TypeError as e:
                raise ValueError("Failed to instantiate queue class '%s': %s",
                                 self.queue_cls, e)
    
            try:
                self.df = load_object(self.dupefilter_cls)(
                    server=self.server,
                    key=self.dupefilter_key % {'spider': spider.name},
                    debug=spider.settings.getbool('DUPEFILTER_DEBUG'),
                )
            except TypeError as e:
                raise ValueError("Failed to instantiate dupefilter class '%s': %s",
                                 self.dupefilter_cls, e)
    
            if self.flush_on_start:
                self.flush()
            # notice if there are requests already in the queue to resume the crawl
            if len(self.queue):
                spider.log("Resuming crawl (%d requests scheduled)" % len(self.queue))
    
        def close(self, reason):
            if not self.persist:
                self.flush()
    
        def flush(self):
            self.df.clear()
            self.queue.clear()
        
        *******
        def enqueue_request(self, request):
            if not request.dont_filter and self.df.request_seen(request):
                self.df.log(request, self.spider)
                return False
            if self.stats:
                self.stats.inc_value('scheduler/enqueued/redis', spider=self.spider)
            self.queue.push(request)
            return True
        *******
        def next_request(self):
            block_pop_timeout = self.idle_before_close
            request = self.queue.pop(block_pop_timeout)
            if request and self.stats:
                self.stats.inc_value('scheduler/dequeued/redis', spider=self.spider)
            return request
    
        def has_pending_requests(self):
            return len(self) > 0
            
            
    相关Queue源码
    
    class Base(object):
        """Per-spider base queue class"""
    
        def __init__(self, server, spider, key, serializer=None):
            if serializer is None:
                # Backward compatibility.
                # TODO: deprecate pickle.
                serializer = picklecompat
            if not hasattr(serializer, 'loads'):
                raise TypeError("serializer does not implement 'loads' function: %r"
                                % serializer)
            if not hasattr(serializer, 'dumps'):
                raise TypeError("serializer '%s' does not implement 'dumps' function: %r"
                                % serializer)
    
            self.server = server
            self.spider = spider
            self.key = key % {'spider': spider.name}
            self.serializer = serializer
    
        def _encode_request(self, request):
            """Encode a request object"""
            obj = request_to_dict(request, self.spider)
            return self.serializer.dumps(obj)
    
        def _decode_request(self, encoded_request):
            """Decode an request previously encoded"""
            obj = self.serializer.loads(encoded_request)
            return request_from_dict(obj, self.spider)
    
        def __len__(self):
            """Return the length of the queue"""
            raise NotImplementedError
    
        def push(self, request):
            """Push a request"""
            raise NotImplementedError
    
        def pop(self, timeout=0):
            """Pop a request"""
            raise NotImplementedError
    
        def clear(self):
            """Clear queue/stack"""
            self.server.delete(self.key)
    
    class FifoQueue(Base):
        """Per-spider FIFO queue"""
    
        def __len__(self):
            """Return the length of the queue"""
            return self.server.llen(self.key)
    
        def push(self, request):
            """Push a request"""
            self.server.lpush(self.key, self._encode_request(request))
    
        def pop(self, timeout=0):
            """Pop a request"""
            if timeout > 0:
                data = self.server.brpop(self.key, timeout)
                if isinstance(data, tuple):
                    data = data[1]
            else:
                data = self.server.rpop(self.key)
            if data:
                return self._decode_request(data)
    
    class PriorityQueue(Base):
        """Per-spider priority queue abstraction using redis' sorted set"""
    
        def __len__(self):
            """Return the length of the queue"""
            return self.server.zcard(self.key)
    
        def push(self, request):
            """Push a request"""
            data = self._encode_request(request)
            score = -request.priority
            # We don't use zadd method as the order of arguments change depending on
            # whether the class is Redis or StrictRedis, and the option of using
            # kwargs only accepts strings, not bytes.
            self.server.execute_command('ZADD', self.key, score, data)
    
        def pop(self, timeout=0):
            """
            Pop a request
            timeout not support in this queue class
            """
            # use atomic range/remove using multi/exec
            pipe = self.server.pipeline()
            pipe.multi()
            pipe.zrange(self.key, 0, 0).zremrangebyrank(self.key, 0, 0)
            results, count = pipe.execute()
            if results:
                return self._decode_request(results[0])
    
    class LifoQueue(Base):
        """Per-spider LIFO queue."""
    
        def __len__(self):
            """Return the length of the stack"""
            return self.server.llen(self.key)
    
        def push(self, request):
            """Push a request"""
            self.server.lpush(self.key, self._encode_request(request))
    
        def pop(self, timeout=0):
            """Pop a request"""
            if timeout > 0:
                data = self.server.blpop(self.key, timeout)
                if isinstance(data, tuple):
                    data = data[1]
            else:
                data = self.server.lpop(self.key)
    
            if data:
                return self._decode_request(data)
    
    
    # TODO: Deprecate the use of these names.
    SpiderQueue = FifoQueue
    SpiderStack = LifoQueue
    SpiderPriorityQueue = PriorityQueue
    View Code

     

    3.pipelines

    使用scrapy-redis内置的pipeline做持久化,就是将item对象保存到redis的列表中:
    配置文件中:

    单独使用pipelins

    # ############ 连接redis 信息 #################
    REDIS_HOST = '127.0.0.1'                            # 主机名
    REDIS_PORT = 6379                                   # 端口
    # REDIS_URL = 'redis://user:pass@hostname:9001'       # 连接URL(优先于以上配置)
    REDIS_PARAMS  = {}                                  # Redis连接参数             默认:REDIS_PARAMS = {'socket_timeout': 30,'socket_connect_timeout': 30,'retry_on_timeout': True,'encoding': REDIS_ENCODING,})
    # REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块  默认:redis.StrictRedis
    REDIS_ENCODING = "utf-8"
    
    #使用scrapy-redis内置的pipeline做持久化,就是将item对象保存到redis的列表中:
    #配置文件中:
    ITEM_PIPELINES = {
       'scrapy_redis.pipelines.RedisPipeline': 300,
    }
    配置pipelines

    如果是去重+调度器+pipelines

    # ############ 连接redis 信息 #################
    REDIS_HOST = '127.0.0.1'                            # 主机名
    REDIS_PORT = 6379                                   # 端口
    # REDIS_URL = 'redis://user:pass@hostname:9001'       # 连接URL(优先于以上配置)
    REDIS_PARAMS  = {}                                  # Redis连接参数             默认:REDIS_PARAMS = {'socket_timeout': 30,'socket_connect_timeout': 30,'retry_on_timeout': True,'encoding': REDIS_ENCODING,})
    # REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块  默认:redis.StrictRedis
    REDIS_ENCODING = "utf-8"
    
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    
    # 有引擎来执行:自定义调度器
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.LifoQueue'  # 默认使用优先级队列(默认广度优先),其他:PriorityQueue(有序集合),FifoQueue(列表)、LifoQueue(列表)
    SCHEDULER_QUEUE_KEY = '%(spider)s:requests'  # 调度器中请求存放在redis中的key
    SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"  # 对保存到redis中的数据进行序列化,默认使用pickle
    SCHEDULER_PERSIST = True  # 是否在关闭时候保留原来的调度器和去重记录,True=保留,False=清空
    SCHEDULER_FLUSH_ON_START = False  # 是否在开始之前清空 调度器和去重记录,True=清空,False=不清空
    # SCHEDULER_IDLE_BEFORE_CLOSE = 10  # 去调度器中获取数据时,如果为空,最多等待时间(最后没数据,未获取到)。
    SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter'  # 去重规则,在redis中保存时对应的key  chouti:dupefilter
    SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'  # 去重规则对应处理的类
    DUPEFILTER_DEBUG = False
    
    
    #使用scrapy-redis内置的pipeline做持久化,就是将item对象保存到redis的列表中:
    #配置文件中:
    ITEM_PIPELINES = {
       'scrapy_redis.pipelines.RedisPipeline': 300,
    }
    配置

     

    4.通过scrapy-redis去redis中获取

    实现了分布式爬取,如果没有任务的话,爬取的服务器就夯住

    只要别的服务器往爬虫服务器发送任务,就继续爬取

    在spiders里

    from scrapy_redis.spiders import RedisSpider
    class ChoutiSpider(RedisSpider):
        name = 'chouti'
        allowed_domains = ['chouti.com']
    
        def parse(self, response):
            pass
    View Code

    新建一个.py设置起始url(相当于另外一台服务器)

    import redis
    
    conn=redis.Redis(host='127.0.0.1',port=6379)
    
    conn.lpush('chouti:start_urls','https://dig.chouti.com/')
    View Code

    最后进行设置配置文件

    # 配置文件
     # 起始url的最大数量
    REDIS_START_URLS_BATCH_SIZE=1
    
    # 把起始url放到redis列表
    REDIS_START_URLS_AS_SET=False
    
    # 把起始url放到redis集合
    REDIS_START_URLS_AS_SET=True
    配置文件

     

    5.大文件下载

    from twisted.web.client import Agent, getPage, ResponseDone, PotentialDataLoss
    
    from twisted.internet import defer, reactor, protocol
    from twisted.web._newclient import Response
    from io import BytesIO
    
    
    class _ResponseReader(protocol.Protocol):
    
        def __init__(self, finished, txresponse, file_name):
            self._finished = finished
            self._txresponse = txresponse
            self._bytes_received = 0
            self.f = open(file_name, mode='wb')
    
        def dataReceived(self, bodyBytes):
            self._bytes_received += len(bodyBytes)
    
            # 一点一点的下载
            self.f.write(bodyBytes)
    
            self.f.flush()
    
        def connectionLost(self, reason):
            if self._finished.called:
                return
            if reason.check(ResponseDone):
                # 下载完成
                self._finished.callback((self._txresponse, 'success'))
            elif reason.check(PotentialDataLoss):
                # 下载部分
                self._finished.callback((self._txresponse, 'partial'))
            else:
                # 下载异常
                self._finished.errback(reason)
    
            self.f.close()
    
    
    class BigfilePipeline(object):
        def process_item(self, item, spider):
            # 创建一个下载文件的任务
    
            if item['type'] == 'file':
                agent = Agent(reactor)
                d = agent.request(
                    method=b'GET',
                    uri=bytes(item['url'], encoding='ascii')
                )
                # 当文件开始下载之后,自动执行 self._cb_bodyready 方法
                d.addCallback(self._cb_bodyready, file_name=item['file_name'])
    
                return d
            else:
                return item
    
        def _cb_bodyready(self, txresponse, file_name):
            # 创建 Deferred 对象,控制直到下载完成后,再关闭链接
            d = defer.Deferred()
            d.addBoth(self.download_result)  # 下载完成/异常/错误之后执行的回调函数
            txresponse.deliverBody(_ResponseReader(d, txresponse, file_name))
            return d
    
        def download_result(self, response):
            pass
    View Code

     

    转载于:https://www.cnblogs.com/chenxuming/p/9278985.html

    展开全文
  • scrapy-redis分布式爬虫框架+示例
  • Scrapy-Redis

    2019-04-09 23:35:00
    Scrapy-Redis:分布式爬虫 概念: 分布式爬虫是由一组通过网络进行通信,为了完成共同的爬虫任务而协调工作的计算机节点组成的系统.分布式爬虫是将多台主机组合起来,共同完成一个爬取任务.这样大大提高了爬取效率. 为...

    Scrapy-Redis:分布式爬虫

    概念:

    分布式爬虫是由一组通过网络进行通信,为了完成共同的爬虫任务而协调工作的计算机节点组成的系统.分布式爬虫是将多台主机组合起来,共同完成一个爬取任务.这样大大提高了爬取效率.

    为什么使用:
    效率:单机CPU处理能力不够,单机的带宽(网速)
    安全: 将爬虫任务分开 当一个任务出现问题不至于其他任务结束

    Scrapy是不支持分布式的,那么需要改造两个方面是scrapy支持分布式.

    共享请求队列
    - 将原来保存在Scrapy调度器里面的请求共享出来 这样其他的爬虫就可以得到这个请求

    共享指纹集合
    - 当请求进入Redis的时候 要进行去重 判断指纹是否存在 如果已经存在就不会放进来
    - 其实每一个请求进入Redis的时候都会做成一个指纹 不管请求之前是否存在 都会做成指纹
    * 请求不存在–> 将指纹添加进来 将请求页添加进来 用于下次判断
    * 请求存在 --> 指纹之前已经有啦 不再添加请求

    通过Redis服务器,所有的电脑端的爬虫共享了一个爬取队列.

    789

    四个组件:

    Scheduler(调度器)

    ​ Scrapy-redis将Scrapy queue换成了redis数据库,这样就是支持共享的队列.

    ​ Redis中支持多种数据结构(列表和集合),集合元素是无序且不重复的.

    ​ 采用Redis 请求(优先级–>列表(LIFO, FIFO) 集合分数(优先)) 指纹

    Duplication Filter(去重)

    ​ scrapy自带去重功能,使用的是python中的集合类型.

    ​ 计算指纹集合
    每个请求都会计算指纹
    scrapy中原来的指纹集合会和redis中的指纹集合进行比对,如果没有现在的指纹 将指纹和请求都添加进Redis

    ​ 如果指纹集合已经存在 将指纹和请求都不添加 其他爬虫就不用爬虫 说明已经有爬虫采集过了 或者正准备采集

    Item Pipeline 管道

    scrapy-redis的 Item Pipline将爬取到的Item保存在redis的Items queue中,可以很方便的根据key取得item

    Base Spider

    由于scrapy-redis中读取发送请求都是直接通过redis,所以要重写爬虫类,不用继承Spider类,而是继承RedisSpdier (Spider)和RedisCrawlSpider (CrawlSpider)

    实现前提:配置Redis

    123

    master端可以没有python和scrapy,只需要有redis,能够分配url就ok

    因为分布式的实现需要Master,Slaver的通讯 也就是Slaver端要从Master端提取请求 放入请求 所以需要连接Master端的服务器才可以通讯
    实现Scrapy-Redis分布式需要做的第一件事就是将Redis服务器再Master和Slaver端之间相互Ping通(能相互连接)

    步骤:

    1.安装redis

    非windos用户,要检查是否有/etc/redis/redis.conf,如果没有 ,拷贝一份该文件到此目录下

    2.修改配置文件(建议复制一份修改好再使用)

    ​ 1)打开你的redis.conf配置文件,windows用户会有两个配置文件,建议修改redis.windows.conf这个文件,另一个是服务开启的时候默认加载的文件,

    ​ 2)将 bind 127.0.0.1 改为bind 0.0.0.0 或者注释掉 (使非本地也可以连接redis),然后再重启redis服务,指定配置文件

    ​ 3)daemonize no表示Redis默认不作为守护进程运行,即将显示Redis启动提示画面;daemonize yes则默认后台运行(可以不设置,Windows不支持)

    这就配置好了,接下来Slave端启动redis-cli -h 192.168.99.1 -h 参数表示连接到指定主机的redis数据库

    注意:slaver端不需要启动redis服务,master启动server就可以.

    实现分布式步骤:

    ​ 0.先按照普通的Scrapy正常写就可以了 写好以后能跑通就可以实施分布式.

      1. 将爬虫主文件中继承自Scrapy中的scrapy.Spider 或 CrawlSpider 替换成 Scrapy-Redis的 RedisSpider 或 RedisCrawlSpider。
    
         ```python
         from scrapy_redis.spiders import RedisCrawlSpider
    
         class MoviespiderSpider(RedisCrawlSpider):
         ```
    
         ​
    
     	2. 初始的start_urls改为redis_key。后面的Redis中的网址 是需要Master或者Slaver发送的
    
         ```python
         # 这不是网址,是redis中的键,可以随意设定
         redis_key = 'movieSpider:start_urls'
         ```
    
         ​
    
      3. 在settings.py文件中修改Scrapy自带的调度器类和去重类为Scrapy-Redis提供的类
    
      ```python
    

    SCHEDULER = “scrapy_redis.scheduler.Scheduler” # 调度器,必须添加

    DUPEFILTER_CLASS = “scrapy_redis.dupefilter.RFPDupeFilter” #去重的,必须添加

    SCHEDULER_PERSIST = True # 可选 选择以后会记录指纹持久化指纹(断点续爬)

      ```
    
    1. 在settings.py文件中配置Redis(两种方式)

      1). 单独配置(Scrapy-Redis优先使用)

                  ```python
      

      #在settings文件中设置
      REDIS_URL = redis://[password]@host:port
      REDIS_URL = redis://192.168.xx.xxx:6379
      ```

      2). 分开配置

      REDIS_HOST = '127.0.0.1'
      REDIS_PORT = 6379
      REDIS_PASSWORD = 'foobared'
      
    2. 保存数据到Redis数据库

      # 把数据默认添加到redis数据库中,在sett管道中激活
      'scrapy_redis.pipelines.RedisPipeline': 400,  #管道不用写
      

    3. 运行

      1. 进入scrapy,运行爬虫文件,然后在master的redis客户端中放入键值(设定的键和你需要爬取的第一个网址)
      lpush 键  网址
      

      也可以写一个py文件运行

      #写一个简单py程序
      import redis
      rediscli.lpush('redis-key',url)
      

      123123

    爬虫就运行起来了.

    展开全文
  • Scrapy-Redis入门实战

    万次阅读 热门讨论 2019-05-05 19:28:13
    目录 ...scrapy-redis是一个基于redisscrapy组件,用于快速实现scrapy项目的分布式部署和数据爬取,其运行原理如下图所示。 Scrapy-Redis特性 分布式爬取 你可以启动多个共享同一redis队列...

    目录

    简介

    Scrapy-Redis特性

    Scrapy-Redis示例

    开发环境

    创建项目

    定义Item

    创建Spider

    修改配置

    启动爬虫


    简介

    scrapy-redis是一个基于redis的scrapy组件,用于快速实现scrapy项目的分布式部署和数据爬取,其运行原理如下图所示。

    Scrapy-Redis特性

    分布式爬取

    你可以启动多个共享同一redis队列的爬虫实例,多个爬虫实例将各自提取到或者已请求的Requests在队列中统一进行登记,使得Scheduler在请求调度时能够对重复Requests进行过滤,即保证已经由某一个爬虫实例请求过的Request将不会再被其他的爬虫实例重复请求。

    分布式数据处理

    将scrapy爬取到的items汇聚到同一个redis队列中,意味着你可以根据你的需要启动尽可能多的共享这个items队列的后处理程序。

    Scrapy即插即用组件

    Scheduler调度器 + Duplication重复过滤器、Item Pipeline、基础Spider爬虫

    Scrapy-Redis示例

    本文将以爬取京东所有图书分类下的图书信息为例对Scrapy-Redis的用法进行示例。

    开发环境

    • Python 3.7
    • Redis 3.2.100

    下面列举出了 Python 中 Scrapy-Redis 所需要的各个模块及其版本:

    • redis 2.10.6
    • redis-py-cluster 1.3.6
    • scrapy-redis 0.6.8
    • scrapy-redis-cluster 0.4

    在开发之前需要先安装好以上模块,以scrapy-redis-cluster模块为例,使用pip进行安装的命令如下:

    pip install scrapy-redis-cluster # 安装模块
    pip install scrapy-redis-cluster==0.4 # 安装模块时指定版本
    pip install --upgrade scrapy-redis-cluster # 升级模块版本

    创建项目

    在Windows命令行执行如下命令完成项目创建:

    d:\scrapy>scrapy startproject jd_book

    执行完该命令后,将会在当前目录下创建包含下列内容的 jd_book 目录:

    定义Item

    在items.py中把我们将要爬取的图书字段预先定义好。

    # -*- coding: utf-8 -*-
    
    import scrapy
    
    class JdBookItem(scrapy.Item):
        b_cate = scrapy.Field() # 图书所属一级分类名称
        s_cate = scrapy.Field() # 图书所属二级分类名称
        s_href = scrapy.Field() # 图书所属二级分类地址
        book_name = scrapy.Field() # 名称
        book_img = scrapy.Field() # 封面图片地址
        book_author = scrapy.Field() # 作者
        book_press = scrapy.Field() # 出版社
        book_publish_date = scrapy.Field() # 出版日期
        book_sku = scrapy.Field() # 商品编号
        book_price = scrapy.Field() # 价格

    创建Spider

    在Windows命令行执行如下命令完成Spider创建:

    d:\scrapy\jd_book>cd jd_book
    d:\scrapy\jd_book>scrapy genspider jdbook jd.com

    执行完该命令后,将会在 jd_book 的 spiders 目录下生成一个 jdbook.py 文件 :

     jdbook.py的完整爬虫代码如下。

    # -*- coding: utf-8 -*-
    import scrapy
    import json
    import urllib
    from copy import deepcopy
    from jd_book.items import JdBookItem
    
    class JdbookSpider(scrapy.Spider):
        name = 'jdbook'
        allowed_domains = ['jd.com','3.cn']
        start_urls = ['https://book.jd.com/booksort.html']
    
        def parse(self, response): # 处理图书分类页
            dt_list = response.xpath("//div[@class='mc']/dl/dt") # 提取一级分类元素
            for dt in dt_list:
                item = JdBookItem()
                item["b_cate"] = dt.xpath("./a/text()").extract_first() # 提取一级分类名称
                em_list = dt.xpath("./following-sibling::dd[1]/em") # 提取二级分类元素
                for em in em_list:
                    item["s_cate"] = em.xpath("./a/text()").extract_first() # 提取二级分类名称
                    item["s_href"] = em.xpath("./a/@href").extract_first() # 提取二级分类地址
                    if item["s_href"] is not None:
                        item['s_href'] = "https:" + item['s_href'] # 补全二级分类地址
                        yield scrapy.Request(item['s_href'], callback=self.parse_book_list, meta={"item": deepcopy(item)})
    
        def parse_book_list(self, response): # 处理二级分类下图书列表页
            item = response.meta['item']
            li_list = response.xpath("//div[@id='plist']/ul/li") # 提取所有的图书元素
            for li in li_list:
                item["book_img"] = li.xpath(".//div[@class='p-img']//img/@data-lazy-img").extract_first()
                if item["book_img"] is None:
                    item["book_img"] = li.xpath(".//div[@class='p-img']//img/@src").extract_first()
                if item["book_img"] is not None:
                    item["book_img"] = "https:"+item["book_img"]
                item["book_name"] = li.xpath(".//div[@class='p-name']/a/em/text()").extract_first().strip()
                item["book_author"] = li.xpath(".//span[@class='author_type_1']/a/text()").extract()
                item["book_press"] = li.xpath(".//span[@class='p-bi-store']/a/@title").extract_first()
                item["book_publish_date"] = li.xpath(".//span[@class='p-bi-date']/text()").extract_first().strip()
                item["book_sku"] = li.xpath("./div/@data-sku").extract_first()
                price_url = "https://p.3.cn/prices/mgets?skuIds=j_{}".format(item["book_sku"]) # 提取图书价格请求地址
                yield scrapy.Request(price_url, callback=self.parse_book_price, meta={"item": deepcopy(item)})
    
            # 提取列表页下一页地址
            next_url = response.xpath("//a[@class='pn-next']/@href").extract_first()
            if next_url is not None:
                next_url = urllib.parse.urljoin(response.url, next_url)
                # yield scrapy.Request(next_url,callback=self.parse_book_list,meta={"item":item})
    
        def parse_book_price(self, response):
            item = response.meta['item']
            item["book_price"] = json.loads(response.body.decode())[0]["op"]
            yield item

    修改配置

    在settings.py 中增加Scrapy-Redis相关配置。

    # -*- coding: utf-8 -*-
    
    BOT_NAME = 'jd_book'
    
    SPIDER_MODULES = ['jd_book.spiders']
    NEWSPIDER_MODULE = 'jd_book.spiders'
    
    # Crawl responsibly by identifying yourself (and your website) on the user-agent
    USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
    
    # Obey robots.txt rules
    ROBOTSTXT_OBEY = False
    
    
    ######################################################
    ##############下面是Scrapy-Redis相关配置################
    ######################################################
    
    # 指定Redis的主机名和端口
    REDIS_HOST = 'localhost'
    REDIS_PORT = 6379
    
    # 调度器启用Redis存储Requests队列
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    
    # 确保所有的爬虫实例使用Redis进行重复过滤
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    
    # 将Requests队列持久化到Redis,可支持暂停或重启爬虫
    SCHEDULER_PERSIST = True
    
    # Requests的调度策略,默认优先级队列
    SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
    
    # 将爬取到的items保存到Redis 以便进行后续处理
    ITEM_PIPELINES = {
        'scrapy_redis.pipelines.RedisPipeline': 300
    }

    启动爬虫

    至此京东图书项目就算配置完成了,你可以将项目部署到多台服务器中去,并使用如下命令来启动爬虫:

     

    d:\scrapy\jd_book>scrapy crawl jdbook

    爬取到的图书数据结构如下:

    相应地,在Redis数据库中同时生成了如下3个键:

    其中,jdbook:requests 中保存了待爬取的Request对象;jdbook:dupefilter 中保存了已经爬取过的Request对象的指纹;jdbook:items中保存了爬取到的Item对象。

     通过上述京东图书项目不难看出,scrapy-redis项目与普通的scrapy项目相比,除了在settings.py配置时额外增加了一些scrapy-redis的专属配置外,其他环节完全相同。

    参考文章

     https://scrapy-redis.readthedocs.io/en/stable/index.html

    展开全文
  • 使用 scrapy-redis实现分布式爬虫

    万次阅读 多人点赞 2018-07-15 22:14:39
    Scrapyscrapy-redis的区别 Scrapy 是一个通用的爬虫框架,但是不支持分布式,Scrapy-redis是为了更方便地实现Scrapy分布式爬取,而提供了一些以redis为基础的组件(仅有组件)。 pip install scrapy-redis ...
  • scrapy-redis-examples 一个与redis集成的scrapy项目。 在scrapy工作中,我们可以使用redis做很多事情。 记住不要用它来做任何违法的事情! ####Usage sudo apt-get install scrapy-0.2x sudo apt-get install ...
  • Scrapy-Redis Scrapy的基于Redis的组件。 免费软件:MIT许可证 文档: : 。 Python版本:2.7、3.4+ 特征 分布式抓取/抓取 ... redis-py > = 2.10 用法 在您的项目中使用以下设置: # Enables scheduling stori
  • 只需将该项目放到不同的机器,简单配置一下redis /mysql 就可以运行,实现分布式抓取数据,需配置相同的环境scrapy/scrapy-redis/itemadapter/redis/mysql
  • 先说下自己的环境,redis是部署在centos上的,爬虫运行在windows上, 1. 安装redis yum install -y redis ...5. 开始创建scrapy-redis的相关,和普通的scrapy一样的创建,只不过修改setting.py文件,添加一行 REDIS
  • Scrapy-Redis源码解读

    万次阅读 2019-05-07 14:54:32
    在上一章《Scrapy-Redis入门实战》中,我们在一个普通的Scrapy项目的settings.py文件中仅额外增加了如下几个配置就使项目实现了基于Redis的Requests请求过滤和Items持久化两大功能。 ############################...
  • scrapy-redis 0.6.8包

    2018-04-02 19:42:49
    scrapy-redisscrapy去重操作和redis链接的必要模块,使用pip安装经常不成功可以尝试这个
  • 主要给大家介绍了关于scrapy-redis源码分析之发送POST请求的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用scrapy-redis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
  • 所谓的scrapy-Redis实际上就是scrapy+redis,其中对redis的操作采用redis-py客户端。下面这篇文章详细介绍了Scrapy-redis爬虫分布式爬取的分析和实现,需要的朋友可以参考借鉴,下面来一起看看吧。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,739
精华内容 4,295
关键字:

scrapy-redis

redis 订阅