數(shù)據(jù)流向
關(guān)于Spider
在我看來珊楼,Spider主要負(fù)責(zé)Request的生成,和Response的處理(解析)度液。不過除了這兩個功能外厕宗,如果想在多場景下合理定制Spider画舌,必須對每一個屬性/方法都有所了解(最好閱讀源代碼)。一下是我的一些總結(jié)已慢。(必須是指就流程而言)
屬性方法 | 功能 | 必須 | 描述 |
---|---|---|---|
name | id | 是 | scrapy識別spider的id |
start_urls | 種子 | 否 | scrapy推薦的爬蟲入口 |
allowd_doamins | 正則 | 否 | 過濾非匹配url |
custom_settings | 參數(shù) | 否 | 對于每個spider獨(dú)有的配置參數(shù) |
crawler | 交互 | 是 | 綁定spider曲聂,與engine交互 |
settings | 配置 | 是 | 導(dǎo)入?yún)?shù) |
logger | 日志 | 是 | 直接來自python標(biāo)準(zhǔn)庫 |
from_crawler | 實(shí)例化入口 | 是 | scrapy風(fēng)格的實(shí)例化入口 |
start_requests | 初始化 | 是 | scrapy推薦的初始Request生成器 |
make_requests_from_url | 否 | 構(gòu)造Request, dont_filter | |
parse | 解析 | 是 | Request的callback,返回Item或者Request |
log | 日志 | 是 | 封裝logger |
close | 信號處理 | 可定制佑惠,處理spider_closed信號 |
分析
- parse(response)
作為Request的回掉函數(shù)朋腋,功能簡單,邏輯清晰膜楷,不過大概是 開發(fā) 最費(fèi)時的一步旭咽,也非常容易把代碼寫丑。歸根結(jié)底是 解析字段 和 數(shù)據(jù)清洗 的復(fù)雜赌厅。
scrapy推薦的做法是將解析/清洗方案抽象出來穷绵,利用Itemloader導(dǎo)入,代碼可以異常簡潔特愿。我正在計(jì)劃仲墨,將字段定義/解析規(guī)則/清晰規(guī)則序列化到數(shù)據(jù)庫,每次針對url的特征(例如domain洽议,path),進(jìn)行選擇宗收。
- crawler
了解scrapy如何運(yùn)作,躲不開河玩意兒亚兄。scrapy具有模塊化的特征混稽,但在很多模塊/插件初始化的時候都通過crawler導(dǎo)入settings;你想在后臺調(diào)用spider审胚,也需要對crawler有所了解匈勋。在我看來,crawler就是用來管理Spider膳叨,封裝了spider初始化洽洁,啟動,終止的api菲嘴。如果足夠好奇饿自,仔細(xì)看看scrapy.crawler.CrawlerProcess。俺3個月前第一次嘗試將scrapy掛到flask上時差點(diǎn)被搞死(關(guān)于twisted的一堆報(bào)錯)龄坪。
提示:單獨(dú)開進(jìn)程執(zhí)行昭雌。
- from_crawler(crawler, *args, **kwargs)
scrapy 推薦的代碼風(fēng)格,用于實(shí)例化某個對象(中間件健田,模塊)
"This is the class method used by Scrapy to create your spiders". crawler 常常出現(xiàn)在對象的初始化烛卧,負(fù)責(zé)提供crawler.settings
- close
從scrapy的風(fēng)格來看,是一個 異步信號處理器 妓局。更常見的方案是在from_settings 中利用信號(Signal)和指定方法關(guān)聯(lián)总放。使用它可以做一些有趣的事兒呈宇,比如在爬蟲終止時給自己發(fā)一封郵件;定時統(tǒng)計(jì)爬蟲的進(jìn)展局雄。
@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還維護(hù)了 CrawlSpider, XMLFeedSpider, SitemapSpider. 大致就是簡單修改了Respone的處理規(guī)則, Request的過濾甥啄。相較于不同的Spider設(shè)計(jì), 大家或許會對FormRequest感興趣, 用于提交表單比較方便。
后續(xù)跟進(jìn)
- 核心模塊, Scheduler, Engine, Downloader, Item Pipeline, MiddleWare
關(guān)于每個核心模塊都會記錄閱讀經(jīng)驗(yàn)/筆記, 并交流一些重載方案
- 處理javascript, 處理ajax
處理javascript可以用scrapy_splash的方案哎榴;雖然說splash看起來很雜糅型豁,但尼瑪效率高,并發(fā)渲染js尚蝌。作為對比, phantomjs webserver支持10個并發(fā), 也足夠強(qiáng)迎变。但就是不想去造輪子。
- “智能處理”Response
用流行的詞兒來說就是 data-driven programming飘言。之前提到 抽象解析方案 就是一個例子衣形。不過對這方面的概念還比較模糊
- 分布式爬蟲
scrapy官方也維護(hù)了分布式爬蟲的版本--frontera--集成了scheduler, strategy, backend api, message bus, 可以與任意下載器匹配使用,也可以使用frontera中的scrapy wrapper姿鸿,只需更改配置文件就可以穩(wěn)定運(yùn)行谆吴。
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__