关于Scrapy的start_requests中的所有Requests不一口气加入请求队列这件事

Scrapy源码阅读记录

文章目录

因为爬虫需求比较特殊(毕设要做社交网络相关的内容),网上的博客写的都比较拉,互相抄来抄去,找不到有用的东西,只好去啃源码。

主要围绕着 scrapy.core包,具体流程就是从爬虫运行的顺序开始分析,各个从上至下基本上是按顺序来的。

关于Scrapy的start_requests中的所有Requests不一口气加入请求队列这件事

; 爬虫启动

这部分没找到详细的流程,大致是执行了 scrapy.cmdlineexecute方法。

Crawler类

职责

这个类负责初始启动scrapy的2个部件,包括 engine用户自定义的spider

此外还提供一个CrawlRunner类给用户做参考,让用户可以自定义爬虫爬取的调度方法

主要方法

  1. 构造方法 从settings.py读取各种有关设置,并通过反射设置日志、统计数据。
  2. crawl方法
    @defer.inlineCallbacks
    def crawl(self, *args, **kwargs):
        assert not self.crawling, "Crawling already taking place"
        self.crawling = True

        try:
            self.spider = self._create_spider(*args, **kwargs)
            self.engine = self._create_engine()

            start_requests = iter(self.spider.start_requests())

            yield self.engine.open_spider(self.spider, start_requests)
            yield defer.maybeDeferred(self.engine.start)
        except Exception:

            if six.PY2:
                exc_info = sys.exc_info()

            self.crawling = False
            if self.engine is not None:
                yield self.engine.close()

            if six.PY2:
                six.reraise(*exc_info)
            raise

    def _create_spider(self, *args, **kwargs):

        return self.spidercls.from_crawler(self, *args, **kwargs)

    def _create_engine(self):

        return ExecutionEngine(self, lambda _: self.stop())

Engine类

职责

这个类负责各种调度,包括request的出入、response的出入等。

主要方法

  1. 构造方法 设置了 crawler(这里的crawler就是上面提到过的Crawler类对象的引用)和engine的状态,以及从settings.py读取设置来生成相应的 scheduler的配置(注意是配置,scheduler对应的队列啥的还没配套)、 downloader,以及处理parse函数响应返回的 ItemRequestScraper类对象
  2. open_spider方法
@defer.inlineCallbacks
    def open_spider(self, spider, start_requests=(), close_if_idle=True):
        assert self.has_capacity(), "No free spider slot when opening %r" % \
            spider.name
        logger.info("Spider opened", extra={'spider': spider})

        nextcall = CallLaterOnce(self._next_request, spider)

        scheduler = self.scheduler_cls.from_crawler(self.crawler)

        start_requests = yield self.scraper.spidermw.process_start_requests(start_requests, spider)

        slot = Slot(start_requests, close_if_idle, nextcall, scheduler)
        self.slot = slot
        self.spider = spider

        yield scheduler.open(spider)
        yield self.scraper.open_spider(spider)
        self.crawler.stats.open_spider(spider)
        yield self.signals.send_catch_log_deferred(signals.spider_opened, spider=spider)

        slot.nextcall.schedule()
        slot.heartbeat.start(5)
  1. _next_requests方法 非常重要,这个方法写明了start_requests里面的请求和parse函数里面的请求有何不同。
def _next_request(self, spider):
    slot = self.slot
    if not slot:
        return

    if self.paused:
        return

    while not self._needs_backout(spider):

        if not self._next_request_from_scheduler(spider):
            break
"""
    如果start_requests里面还有请求并且downloder、scraper还能承受
    一般来说如果队列不空而是刚刚塞满,是不会执行下面这个if里面的内容的,因为网络延迟比cpu处理慢太多了
"""
    if slot.start_requests and not self._needs_backout(spider):
        try:

            request = next(slot.start_requests)
            except StopIteration:
                slot.start_requests = None
            except Exception:
                slot.start_requests = None
                logger.error('Error while obtaining start requests',
                             exc_info=True, extra={'spider': spider})

            else:

                self.crawl(request, spider)

    if self.spider_is_idle(spider) and slot.close_if_idle:
        self._spider_idle(spider)

 def _needs_backout(self, spider):
"""
    当爬虫在运行、没有关闭、downloader和scraper还能继续往处理队列种塞东西的时候,返回False
"""
    slot = self.slot
    return not self.running \
        or slot.closing \
        or self.downloader.needs_backout() \
        or self.scraper.slot.needs_backout()

def crawl(self, request, spider):
    assert spider in self.open_spiders, \
    "Spider %r not opened when crawling: %s" % (spider.name, request)

    self.schedule(request, spider)
    self.slot.nextcall.schedule()

def schedule(self, request, spider):
    self.signals.send_catch_log(signal=signals.request_scheduled,
                                request=request, spider=spider)

    if not self.slot.scheduler.enqueue_request(request):
        self.signals.send_catch_log(signal=signals.request_dropped,
                                    request=request, spider=spider)

  1. _next_request_from_scheduler方法 从scheduler的队列中取得下一个Request
def _next_request_from_scheduler(self, spider):
    slot = self.slot

    request = slot.scheduler.next_request()
    if not request:
        return

    d = self._download(request, spider)
    d.addBoth(self._handle_downloader_output, request, spider)
    d.addErrback(lambda f: logger.info('Error while handling downloader output',
                                       exc_info=failure_to_exc_info(f),
                                       extra={'spider': spider}))
    d.addBoth(lambda _: slot.remove_request(request))
    d.addErrback(lambda f: logger.info('Error while removing request from slot',
                                       exc_info=failure_to_exc_info(f),
                                       extra={'spider': spider}))
    d.addBoth(lambda _: slot.nextcall.schedule())
    d.addErrback(lambda f: logger.info('Error while scheduling new request',
                                       exc_info=failure_to_exc_info(f),
                                       extra={'spider': spider}))
    return d

def _handle_downloader_output(self, response, request, spider):
    assert isinstance(response, (Request, Response, Failure)), response

    if isinstance(response, Request):
        self.crawl(response, spider)
        return

    d = self.scraper.enqueue_scrape(response, request, spider)
    d.addErrback(lambda f: logger.error('Error while enqueuing downloader output',
                                        exc_info=failure_to_exc_info(f),
                                        extra={'spider': spider}))
    return d

def _download(self, request, spider):

    slot = self.slot
    slot.add_request(request)
    def _on_success(response):
        assert isinstance(response, (Response, Request))
        if isinstance(response, Response):
            response.request = request
            logkws = self.logformatter.crawled(request, response, spider)
            logger.log(*logformatter_adapter(logkws), extra={'spider': spider})
            self.signals.send_catch_log(signal=signals.response_received, \
                                        response=response, request=request, spider=spider)
        return response

    def _on_complete(_):
        slot.nextcall.schedule()
        return _

    dwld = self.downloader.fetch(request, spider)
    dwld.addCallbacks(_on_success)
    dwld.addBoth(_on_complete)
    return dwld

Scraper类

职责

处理spider的IO,包括进入spider和从spider出来的数据,可以视作 spidermiddleware的控制器

主要方法

  1. _scrape_next方法
def _scrape_next(self, spider, slot):

    while slot.queue:
        response, request, deferred = slot.next_response_request_deferred()
        self._scrape(response, request, spider).chainDeferred(deferred)

def _scrape(self, response, request, spider):
    """Handle the downloaded response or failure through the spider
        callback/errback"""
    assert isinstance(response, (Response, Failure))

    dfd = self._scrape2(response, request, spider)
    dfd.addErrback(self.handle_spider_error, request, response, spider)

    dfd.addCallback(self.handle_spider_output, request, response, spider)
    return dfd
  1. handle_spider_output方法 比较重要,这里可以了解到为什么parse函数能够一口气处理很多的Request和item。
def handle_spider_output(self, result, request, response, spider):
    if not result:
        return defer_succeed(None)
    it = iter_errback(result, self.handle_spider_error, request, response, spider)

    dfd = parallel(it, self.concurrent_items,
                   self._process_spidermw_output, request, response, spider)
    return dfd

def _process_spidermw_output(self, output, request, response, spider):
    """Process each Request/Item (given in the output parameter) returned
        from the given spider
"""
    if isinstance(output, Request):

        self.crawler.engine.crawl(request=output, spider=spider)
        elif isinstance(output, (BaseItem, dict)):
            self.slot.itemproc_size += 1
            dfd = self.itemproc.process_item(output, spider)
            dfd.addBoth(self._itemproc_finished, output, response, spider)
            return dfd
        elif output is None:
            pass
        else:
            typename = type(output).__name__
            logger.error('Spider must return Request, BaseItem, dict or None, '
                         'got %(typename)r in %(request)s',
                         {'request': request, 'typename': typename},
                         extra={'spider': spider})

结论

Scrapy的start_requests方法相当于森林中所有树的根,当爬完第一棵树的时候才会考虑进行下一棵树的爬取。因此,对于后续的爬取存在循环回调且树根的优先级比循环爬取的节点更高的时候,应该考虑利用parse函数处理yield的特性,将森林中所有的根节点先以一定的优先级加入scheduler的队列中,避免出现吊死在一颗树上的情况。

更通俗的说

scrapy的start_requests里面如果有循环发送请求的情况,实际上scrapy是不会先执行完start_requests的,也就是说里面写了

def start_requests(self):
    for url in urls:
        yield Request(url=url)

像这样的代码,scheduler的队列中并不会存在urls中所有的请求!这是个大坑

Original: https://blog.csdn.net/deltapluskai/article/details/113919109
Author: deltapluskai
Title: 关于Scrapy的start_requests中的所有Requests不一口气加入请求队列这件事

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/789951/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 【数据库第五天】

    ### 回答1: 《 数据库_系统工程师五天修练》是一本关于 _数据库_系统工程师职业技能培训的书籍,通过五天的修炼,帮助读者系统地学习和掌握 _数据库_系统工程师的知识和技能。 …

    Python 2023年8月18日
    068
  • Linux文件系统

    5.创建文逻辑卷件系统 mkfs.ext4 /dev/vgname/lvname 6.挂载逻辑卷到虚拟目录 mount /dev/vgname/lvname /mnt Origin…

    Python 2023年10月18日
    058
  • Python核心编程 第三版 的一些小issue

    第一章正则表达式 1.3 1.3.12 使用sub()和subn()搜&#x7D22…

    Python 2023年5月24日
    0116
  • 安装Scrapy

    首先要安装好Python环境,以前Scrapy只能应用于Python2,现在Python3也能了 Python3下载安装好后配置好环境变量path后 @安装方法首先安装wheelp…

    Python 2023年10月7日
    034
  • 深度学习之图像分类(十八)– Vision Transformer(ViT)网络详解

    深度学习之图像分类(十八)Vision Transformer(ViT)网络详解 目录 * – 深度学习之图像分类(十八)Vision Transformer(ViT)…

    Python 2023年9月27日
    0130
  • 初学开发框架—-Django(1)创建django项目

    所有的操作都是在pycharm上实现的,没有用过的自行百度安装教程进行下载安装 创建pycharm虚拟开发环境(包括安装django第三方库) 创建新的pychrm项目,File-…

    Python 2023年8月5日
    053
  • 【二】conda环境下的pip

    文章目录 前言 Anaconda prompt PS 前言 Anaconda可以作为多个Python解释环境的管理系统,能够很方便地为不同的解释器安装需要的模块和库。除了Anaco…

    Python 2023年9月7日
    063
  • Python中的文件

    目标: 文件的概念 文件的基本操作 文件/文件夹的常用操作 文本文件的编码方式 1.文件的概念 1.1文件的概念和作用 计算机的文件,就是存储在某种长期存储设备上的一段数据 长期存…

    Python 2023年6月3日
    0123
  • Python爬虫速度很慢?并发编程了解一下吧

    Original: https://www.cnblogs.com/pythonQqun200160592/p/15498561.htmlAuthor: python可乐编程Tit…

    Python 2023年5月25日
    0103
  • pytest

    一、目录结构 cases—测试用例 core—–核心源码 util——工具包 report—–存…

    Python 2023年9月12日
    039
  • pygame之surface

    Surface定义 Surface是一块矩形显示区域,用来显示任意图像,具有固定的宽、高、像素格式。粗略可以理解为画布,可以在上面绘制图像。此外还可以将一个Surface作为图像复…

    Python 2023年9月18日
    058
  • 【JS 逆向百例】cnki 学术翻译 AES 加密分析

    关注微信公众号:K哥爬虫,QQ交流群:808574309,持续分享爬虫进阶、JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处…

    Python 2023年5月25日
    083
  • 你给文字描述,AI艺术作画,精美无比!附源码,快来试试!

    💡 作者:韩信子@ShowMeAI📘 深度学习实战系列:https://www.showmeai.tech/tutorials/42📘 TensorFlow 实战系列:https:…

    Python 2023年9月28日
    082
  • 【小程序】低代码+小游戏=小游戏可视化开发

    🥳 作者:伯子南😎 坚信: 好记性不如乱笔头,独乐乐不如众乐乐💪 个人主页:https://blog.csdn.net/qq_34577234?spm=1010.2135.3001…

    Python 2023年11月5日
    062
  • 低代码多分支协同开发的建设与实践

    作者:黄也(胖丁) 引言 随着低代码的普及,在低代码平台上构建企业级应用逐渐成为生产趋势。同时,随着低代码技术的提升,越来越多的复杂应用在低代码平台中完成。在其研发生命周期中,低代…

    Python 2023年10月24日
    037
  • Python GUI tkinter 开发连连看小游戏

    完整的源码:Python GUI tkinter 开发连连看小游戏 源码 游戏的三点要素 地图 地图背 景是10*10的方格 每个方格内随机填充一 个蔬菜或水果 音效 背景音乐 鼠…

    Python 2023年9月18日
    061
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球