- Scrapy架构分析
- Spider及CrawlSpider源码分析
- Middlewares运作原理及部分源码分析
- Pipelines运作原理及部源码分析
Scrapy架构
Scrapy是用Twisted编写的,Twisted是一个流行的事件驱动的Python网络框架。因此,它使用非阻塞(也称为异步)代码实现并发。Scrapy官方文档中的系统架构图:
Scrapy中的数据流动由执行引擎控制,过程如下:
- Engine从Spider中获取爬取的初始Requests(请求);
- Engine在Scheduler中调度Requests,并请求下一个要爬取的Requests;
- Scheduler返回下一个Requests给Engine;
- Engine通过
Downloader Middlewares
将请求发送给Downloader; - 一旦页面完成下载,Downloader将当前页面生成一个Response(响应),并通过
Downloader Middlewares
将其发送到Engine; - Engine从Downloader接收Response,通过
Spider Middlewares
处理并发送给Spider进行处理; - Spider处理Response,并通过
Spider Middlewares
向Engine返回爬取的Item和新的Requests; - Engine将Spider返回的Item发送到Item Pipelines,然后将Spider返回的Requests发送到Scheduler,并请求可能的下一个Requests进行爬取;
- 重复以上步骤,直到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爬取流程如下:
- 首先生成爬取的初始请求,然后指定一个回调函数;要执行的第一个请求是通过调用start_requests()方法获得的,该方法(默认)为start_urls中指定的URL生成请求,并将parse方法作为请求的回调函数;
- 在回调函数中,解析Response(网页相应),并返回提取数据的dict、item对象、Request对象或这些对象的iterable。Request还包含回调(默认parse,可通过callback指定),交由Scrapy下载,然后由指定的回调处理它们的响应;
- 在回调函数中,解析(css、xpath等)页面内容,并使用解析的数据生成Item;
- 最后,从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