咸糖记录编程的地方

念念不忘,必有回响。

目录
scrapy-redis 源码分析
/  

scrapy-redis 源码分析

scrapy-redis

##Scheduler
scrapy-redis 主要重写的scrapy默认的Scheduler

    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

enqueue_request这个方法主要是用于判断url是否重复
如果不重复就插入队列
next_request这个方法主要是从队列里面取出请求 交给引擎取

    def request_seen(self, request):
        """Returns True if request was already seen.

        Parameters
        ----------
        request : scrapy.http.Request

        Returns
        -------
        bool

        """
        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

    def request_fingerprint(self, request):
        """Returns a fingerprint for a given request.

        Parameters
        ----------
        request : scrapy.http.Request

        Returns
        -------
        str

        """
        return request_fingerprint(request)

这是redis 的指纹判断 代码十分简洁并且巧妙
首先调用scrapy 自带的 request_fingerprint方法 这个方法主要是将
url进行哈西 还比较url是否重复
这时候肯定有同学站出来问 直接比较字符串不就行类吗?
但是这种情况有个会有个特殊情况 url的参数位置不一样
但是是同样的url 这样爬虫还是会一直爬取
所以scrapy引入了哈希散列的方式
就算url在不同位置 但是还是能够产生一样的哈西地址
就达到了去除重复的效果

还有一个要点需要注意 在每次重新爬去的时候一定要清空

smembers jdspider:dupefilter
flushdb

如果没有加入start_requests 是无法调用enqueue_request这个方法的

##queue

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)
        #使用zadd 插入元素

    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])

scrapy-redis默认的queue队列 继承自base队列
request编码成redis可以存入的字符串形式 将score取负值然后传入redis 的request池

##spider

 crawler.signals.connect(self.spider_idle, signal=signals.spider_idle)
    def schedule_next_requests(self):
        """Schedules a request if available"""
        # TODO: While there is capacity, schedule a batch of redis requests.

        #从调度器中去出req 对象
        for req in self.next_requests():
            self.crawler.engine.crawl(req, spider=self)

    def spider_idle(self):
        """Schedules a request if available, otherwise waits."""
        # XXX: Handle a sentinel to close the spider.
        self.schedule_next_requests()
        raise DontCloseSpider

首先在为爬虫绑定spider_idle的信号量
主要用来判断爬虫的调度池是否为空 如果调度池为空
就调用下面的方法从出口函数中获取新的start_url进行爬取

    def next_requests(self):
        """Returns a request to be scheduled or none."""
        #REDIS_START_URLS_AS_SET 初始化url池 用来放入口url的
        use_set = self.settings.getbool('REDIS_START_URLS_AS_SET', defaults.START_URLS_AS_SET)
        fetch_one = self.server.spop if use_set else self.server.lpop
        # XXX: Do we need to use a timeout here?
        found = 0
        # TODO: Use redis pipeline execution.
        while found < self.redis_batch_size:
            data = fetch_one(self.redis_key)
            if not data:
                # Queue empty.
                break
            req = self.make_request_from_data(data)
            if req:
                yield req
                found += 1
            else:
                self.logger.debug("Request not made from data: %r", data)

        if found:
            self.logger.debug("Read %s requests from '%s'", found, self.redis_key)

关于scrapy当初的迷惑现在也明了了

就是在用户仔细编写的爬虫文件中 每一个scrapy.Reqeust()都实例化了一个Request对象
传入给enqueue_requests这个存在于调度器中的方法 根据Request的优先级使用next_request
这个方法取出Request对象,然后调度器在使用slot去异步下载Request对象,再根据Request对象的
回调函数把产生的response传入目标方法


标题:scrapy-redis 源码分析
作者:xiantang
地址:http://xiantang.info/articles/2019/06/03/1559551056368.html

评论