scrapy 源碼閱讀筆記(1)-- Spider

數據流向

關于Spider

在我看來,Spider主要負責Request的生成,和Response的處理(解析)。不過除了這兩個功能外,如果想在多場景下合理定制Spider,必須對每一個屬性/方法都有所了解(最好閱讀源代碼)。一下是我的一些總結。(必須是指就流程而言)

屬性方法 功能 必須 描述
name id scrapy識別spider的id
start_urls 種子 scrapy推薦的爬蟲入口
allowd_doamins 正則 過濾非匹配url
custom_settings 參數 對于每個spider獨有的配置參數
crawler 交互 綁定spider,與engine交互
settings 配置 導入參數
logger 日志 直接來自python標準庫
from_crawler 實例化入口 scrapy風格的實例化入口
start_requests 初始化 scrapy推薦的初始Request生成器
make_requests_from_url 構造Request, dont_filter
parse 解析 Request的callback,返回Item或者Request
log 日志 封裝logger
close 信號處理 可定制,處理spider_closed信號

分析

  • parse(response)
    作為Request的回掉函數,功能簡單,邏輯清晰,不過大概是 開發 最費時的一步,也非常容易把代碼寫丑。歸根結底是 解析字段數據清洗 的復雜。

scrapy推薦的做法是將解析/清洗方案抽象出來,利用Itemloader導入,代碼可以異常簡潔。我正在計劃,將字段定義/解析規則/清晰規則序列化到數據庫,每次針對url的特征(例如domain,path),進行選擇。

  • crawler
    了解scrapy如何運作,躲不開河玩意兒。scrapy具有模塊化的特征,但在很多模塊/插件初始化的時候都通過crawler導入settings;你想在后臺調用spider,也需要對crawler有所了解。在我看來,crawler就是用來管理Spider,封裝了spider初始化,啟動,終止的api。如果足夠好奇,仔細看看scrapy.crawler.CrawlerProcess。俺3個月前第一次嘗試將scrapy掛到flask上時差點被搞死(關于twisted的一堆報錯)。

提示:單獨開進程執行。

  • from_crawler(crawler, *args, **kwargs)
    scrapy 推薦的代碼風格,用于實例化某個對象(中間件,模塊)

"This is the class method used by Scrapy to create your spiders". crawler 常常出現在對象的初始化,負責提供crawler.settings

  • close
    從scrapy的風格來看,是一個 異步信號處理器 。更常見的方案是在from_settings 中利用信號(Signal)和指定方法關聯。使用它可以做一些有趣的事兒,比如在爬蟲終止時給自己發一封郵件;定時統計爬蟲的進展。
    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        spider = super(Filter, cls).from_crawler(crawler, *args, **kwargs)
        crawler.signals.connect(spider.spider_closed, signal=signals.spider_opened)
        crawler.signals.connect(spider.spider_closed, signal=signals.spider_closed)
        return spider
  • other spider
    scrapy還維護了 CrawlSpider, XMLFeedSpider, SitemapSpider. 大致就是簡單修改了Respone的處理規則, Request的過濾。相較于不同的Spider設計, 大家或許會對FormRequest感興趣, 用于提交表單比較方便。

后續跟進

  1. 核心模塊, Scheduler, Engine, Downloader, Item Pipeline, MiddleWare

關于每個核心模塊都會記錄閱讀經驗/筆記, 并交流一些重載方案

  1. 處理javascript, 處理ajax

處理javascript可以用scrapy_splash的方案;雖然說splash看起來很雜糅,但尼瑪效率高,并發渲染js。作為對比, phantomjs webserver支持10個并發, 也足夠強。但就是不想去造輪子。

  1. “智能處理”Response

用流行的詞兒來說就是 data-driven programming。之前提到 抽象解析方案 就是一個例子。不過對這方面的概念還比較模糊

  1. 分布式爬蟲

scrapy官方也維護了分布式爬蟲的版本--frontera--集成了scheduler, strategy, backend api, message bus, 可以與任意下載器匹配使用,也可以使用frontera中的scrapy wrapper,只需更改配置文件就可以穩定運行。

scrapy.spider.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


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):
        for url in self.start_urls:
            yield self.make_requests_from_url(url)

    def make_requests_from_url(self, url):
        return Request(url, dont_filter=True)

    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__
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容