1. 程式人生 > >scrapy框架中Spider原始碼解析

scrapy框架中Spider原始碼解析

scrapy框架中Spider原始碼解析

一、scrapy架構

在講解spider類之前,我們先來了解下scrapy這個框架的整體架構
請看下面scrapy工作流程圖
在這裡插入圖片描述

1.scrapy引擎(Scrapy Engine)

引擎負責控制資料流在系統中所有元件中流動,並在相應動作發生時觸發事件。

2.排程器(Scheduler)

排程器從引擎接受request並將他們入隊,以便之後引擎請求他們時提供給引擎

3.下載器(Downloader)

下載器負責獲取頁面資料並提供給引擎,而後提供給spider。

4.蜘蛛(Spiders)

Spider是Scrapy使用者編寫用於分析response並提取item(即獲取到的item)或額外跟進的URL的類。 每個spider負責處理一個特定(或一些)網站。

5.管道(Item Pipeline)

Item Pipeline負責處理被spider提取出來的item。典型的處理有清理、 驗證及持久化(例如存取到資料庫中)

6.下載器中介軟體(Downloader Middlewares)

下載器中介軟體是在引擎及下載器之間的特定鉤子(specific hook),處理Downloader傳遞給引擎的response(也包括引擎傳遞給下載器的Request)。 其提供了一個簡便的機制,通過插入自定義程式碼來擴充套件Scrapy功能。

7.spider中介軟體(Spider Middlewares)

Spider中介軟體是在引擎及Spider之間的特定鉤子(specific hook),處理spider的輸入(response)和輸出(items及requests)。 其提供了一個簡便的機制,通過插入自定義程式碼來擴充套件Scrapy功能。

scrapy架構工作流程(資料流向)

Scrapy中的資料流由執行引擎控制,其過程如下:

1.引擎開啟一個網站(open a domain),找到處理該網站的Spider並向該spider請求第一個要爬取的URL(s)。
2. 引擎從Spider中獲取到第一個要爬取的URL並在排程器(Scheduler)以Request排程。
3. 引擎向排程器請求下一個要爬取的URL。
4. 排程器返回下一個要爬取的URL給引擎,引擎將URL通過下載中介軟體(請求(request)方向)轉發給下載器(Downloader)。
5. 一旦頁面下載完畢,下載器生成一個該頁面的Response,並將其通過下載中介軟體(返回(response)方向)傳送給引擎。
6. 引擎從下載器中接收到Response並通過Spider中介軟體(輸入方向)傳送給Spider處理。
7. Spider處理Response並返回爬取到的Item及(跟進的)新的Request給引擎。
8. 引擎將(Spider返回的)爬取到的Item給Item Pipeline,將(Spider返回的)Request給排程器。
(從第二步)重複直到排程器中沒有更多地request,引擎關閉該網站。

二、spider原始碼解析

Spider是最基本的類,所有爬蟲必須繼承這個類。
Spider類主要用到的函式及呼叫順序為:
(1)init()方法: 初始化爬蟲名字和start_urls列表。

注:這裡爬蟲名稱是必須的,而且必須是唯一的
def __init__(self, name=None, **kwargs):
        if name is not None:
            self.name = name
        elif not g`在這裡插入程式碼片`etattr(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 = []

(2)start_requests()方法:spider發起請求時會呼叫make_requests_from_url()生成Requests物件交給Scrapy下載並返回Response物件交給解析函式處理。

注:start_requests()方法只調用一次
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)

(3)parse()方法:解析下載器返回的response,並返回Item或Requests(需指定回撥函式)。Item傳給Item pipline進行資料的持久化儲存,Requests交由Scrapy下載,並由指定的回撥函式處理,一直進行迴圈,直到處理完所有的資料為止。

重點:這個內容需要我們自己去寫。parse()是預設的Request物件回撥函式,解析返回的response物件, 注意回撥函式的寫法,是函式地址(callback=parse或者callback=None)。
    def parse(self, response):
        raise NotImplementedError

三、spider全部原始碼展示及詳情解析

"""
Base class for Scrapy spiders
See documentation in docs/topics/spiders.rst
"""
import logging
import warnings
 
from scrapy import signals
from scrapy.http import Request
from scrapy.utils.trackref import object_ref
from scrapy.utils.url import url_is_from_spider
from scrapy.utils.deprecate import create_deprecated_class
from scrapy.exceptions import ScrapyDeprecationWarning
from scrapy.utils.deprecate import method_is_overridden
 
#所有爬蟲的基類,使用者定義的爬蟲必須從這個類繼承
class Spider(object_ref):
    """Base class for scrapy spiders. All spiders must inherit from this
    class.
    """
 
    #1、定義spider名字的字串。spider的名字定義了Scrapy如何定位(並初始化)spider,所以其必須是唯一的。
    #2、name是spider最重要的屬性,而且是必須的。一般做法是以該網站的域名來命名spider。例如我們在爬取豆瓣讀書爬蟲時使用‘name = "douban_book_spider"’  
    name = None
    custom_settings = None
 
    #初始化爬蟲名字和start_urls列表。上面已經提到。
    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)
 
        #初始化start_urls列表,當沒有指定的URL時,spider將從該列表中開始進行爬取。 因此,第一個被獲取到的頁面的URL將是該列表之一,後續的URL將會從獲取到的資料中提取。  
        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)
 
    #該方法將讀取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 self.start_urls:
                yield self.make_requests_from_url(url)
        else:
            for url in self.start_urls:
                yield Request(url, dont_filter=True)
 
    #1、start_requests()中呼叫,實際生成Request的函式。
    #2、Request物件預設的回撥函式為parse(),提交的方式為get。
    def make_requests_from_url(self, url):
        """ This method is deprecated. """
        return Request(url, dont_filter=True)
 
    #預設的Request物件回撥函式,處理返回的response。  
    #生成Item或者Request物件。這個類需要我們自己去實現。
    def parse(self, response):
        raise NotImplementedError
 
    @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__
 
 
BaseSpider = create_deprecated_class('BaseSpider', Spider)
 
 
class ObsoleteClass(object):
    def __init__(self, message):
        self.message = message
 
    def __getattr__(self, name):
        raise AttributeError(self.message)
 
spiders = ObsoleteClass(
    '"from scrapy.spider import spiders" no longer works - use '
    '"from scrapy.spiderloader import SpiderLoader" and instantiate '
    'it with your project settings"'
)
 
# Top-level imports
from scrapy.spiders.crawl import CrawlSpider, Rule
from scrapy.spiders.feed import XMLFeedSpider, CSVFeedSpider
from scrapy.spiders.sitemap import SitemapSpider
 
作者:小怪聊職場
連結:https://www.jianshu.com/p/d492adf17312
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。

注:某些內容參考自csdn部分大佬部落格