Scrapy架构及部分源码解析

  • Scrapy架构分析
  • Spider及CrawlSpider源码分析
  • Middlewares运作原理及部分源码分析
  • Pipelines运作原理及部源码分析

Scrapy架构

Scrapy是用Twisted编写的,Twisted是一个流行的事件驱动的Python网络框架。因此,它使用非阻塞(也称为异步)代码实现并发。Scrapy官方文档中的系统架构图:
在这里插入图片描述
Scrapy中的数据流动由执行引擎控制,过程如下:

  1. EngineSpider中获取爬取的初始Requests(请求);
  2. EngineScheduler中调度Requests,并请求下一个要爬取的Requests;
  3. Scheduler返回下一个Requests给Engine
  4. Engine通过Downloader Middlewares将请求发送给Downloader
  5. 一旦页面完成下载,Downloader将当前页面生成一个Response(响应),并通过Downloader Middlewares将其发送到Engine
  6. EngineDownloader接收Response,通过Spider Middlewares处理并发送给Spider进行处理;
  7. Spider处理Response,并通过Spider MiddlewaresEngine返回爬取的Item和新的Requests;
  8. EngineSpider返回的Item发送到Item Pipelines,然后将Spider返回的Requests发送到Scheduler,并请求可能的下一个Requests进行爬取;
  9. 重复以上步骤,直到Scheduler中没有更多的Requests。

Scrapy Engine

引擎(Engine)负责控制系统所有组件之间的数据流,并在发生某些操作时触发事件;同时,引擎也是程序的入口。

调度器(Scheduler)

Scheduler从Engine接收请求(Resquest)并将它们入队,以便之后Engine请求它们时提供给Engine。

下载器(Downloader)

Downloader负责获取页面数据并提供给引擎,而后将网站的响应结果对象提供给Spider。

蜘蛛(Spiders)

Spider是用户编写用于分析响应(Response)结果并从中提取Item或跟进的URL的类。

数据管道(Item Pipeline)

Item Pipeline负责处理(如清理、验证、持久化等)被Spider提取出来的Item。

下载中间件(DownLoader Midderwares)

DownLoader Midderwares是位于Engine和Downloader之间的特定钩子,处理Engine传递给Downloader的Request以及Downloader传递给Engine的Response。其提供了一个简便的机制,通过插入自定义代码来扩展Scrapy的功能。

Spider中间件(Spideer Midderwares)

Spider Midderwares是位于Engine和Spider之间的特定钩子,处理Spider的输入(Response)和输出(Item/Request)。

Spiders

Spider类定义了如何爬取网站,包括爬取得动作(是否跟进链接)以及如何从网页中提取结构化数据(Item)。总之,Spider是定义爬取的动作及分析网页的地方。

Spider爬取流程如下:

  1. 首先生成爬取的初始请求,然后指定一个回调函数;要执行的第一个请求是通过调用start_requests()方法获得的,该方法(默认)为start_urls中指定的URL生成请求,并将parse方法作为请求的回调函数;
  2. 在回调函数中,解析Response(网页相应),并返回提取数据的dict、item对象、Request对象或这些对象的iterable。Request还包含回调(默认parse,可通过callback指定),交由Scrapy下载,然后由指定的回调处理它们的响应;
  3. 在回调函数中,解析(css、xpath等)页面内容,并使用解析的数据生成Item;
  4. 最后,从spider返回的Item通常被持久化到数据库(在Pipeline中)或使用feed exports写入文件。

scrapy.Spider

Spider类提供了蜘蛛的最基本的行为与特性,其他蜘蛛都必须继承自该类(包括Scrapy自带的蜘蛛及用户自己编写的蜘蛛)。Spider并没有提供太多特殊的功能,其仅仅请求给定的start_urls/strat_requests,并根据返回的结果调用parse方法对返回结果深入爬取或提取目标数据。我们通过分析Spider代码来理解Spider的行为,源码如下:

class Spider(object_ref):
    """Base class for scrapy spiders. All spiders must inherit from this
    class.
    """

    name = None
    custom_settings = None

    def __init__(self, name=None, **kwargs):
        if name is not None:
            self.name = name
        elif not getattr(self, 'name', None):
            raise ValueError("%s must have a name" % type(self).__name__)
        self.__dict__.update(kwargs)
        if not hasattr(self, 'start_urls'):
            self.start_urls = []

    @property
    def logger(self):
        logger = logging.getLogger(self.name)
        return logging.LoggerAdapter(logger, {
   'spider': self})

    def log(self, message, level=logging.DEBUG, **kw):
        """Log the given message at the given log level

        This helper wraps a log call to the logger within the spider, but you
        can use it directly (e.g. Spider.logger.info('msg')) or use any other
        Python logger too.
        """
        self.logger.log(level, message, **kw)

    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        spider = cls(*args, **kwargs)
        spider._set_crawler(crawler)
        return spider

    def set_crawler(self, crawler):
        warnings.warn("set_crawler is deprecated, instantiate and bound the "
                      "spider to this crawler with from_crawler method "
                      "instead.",
                      category=ScrapyDeprecationWarning, stacklevel=2)
        assert not hasattr(self, 'crawler'), "Spider already bounded to a " \
                                             "crawler"
        self._set_crawler(crawler)

    def _set_crawler(self, crawler):
        self.crawler = crawler
        self.settings = crawler.settings
        crawler.signals.connect(self.close, signals.spider_closed)

    def start_requests(self):
        cls = self.__class__
        if method_is_overridden(cls, Spider, 'make_requests_from_url'):
            warnings.warn(
                "Spider.make_requests_from_url method is deprecated; it "
                "won't be called in future Scrapy releases. Please "
                "override Spider.start_requests method instead (see %s.%s)." % (
                    cls.__module__, cls.__name__
                ),
            )
            for url in self.start_urls:
                yield self.make_requests_from_url(url)
        else:
            for url in self.start_urls:
                yield Request(url, dont_filter=True)

    def make_requests_from_url(self, url):
        """ This method is deprecated. """
        return Request(url, dont_filter=True)

    def parse(self, response):
        raise NotImplementedError('{}.parse callback is not defined'.format(self.__class__.__name__))

    @classmethod
    def update_settings(cls, settings):
        settings.setdict(cls.custom_settings or {
   }, priority='spider')

    @classmethod
    def handles_request(cls, request):
        return url_is_from_spider(request.url, cls)

    @staticmethod
    def close(spider, reason):
        closed = getattr(spider, 'closed', None)
        if callable(closed):
            return closed(reason)

    def __str__(self):
        return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))

    __repr__ = __str__
start_requests(self)

我们着重看一下start_requests方法,Spider类的入口方法就是start_requests,此方法会向start_urls发起请求(Request),请求的响应(Response)会传给回调(callback)函数,默认是parse方法。

start_requests函数非常简单,它首先判断make_requests_from_url方法是否被重载,如果没有被重载,直接向start_urls发起Request;如果被重载,就通过make_requests_from_url向start_urls发起Request。

    def start_requests(self):
        cls = self.__class__
        if method_is_overridden(cls, Spider, 'make_requests_from_url'):
            warnings.warn(
                    "Spider.make_requests_from_url method is deprecated; it "
                    "won't be called in future Scrapy releases. Please "
                    "override Spider.start_requests method instead (see %s.%s)." % (
                        cls.__module__, cls.__name__
                    ),
                )
            for url in sel
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值