scrapy有内置的信号,比如spider_opened,spider_closed,item_dropped等等。我们可以利用这些信号来做一些初始化的工作和数据收集统计。比如spider_opened信号就方便了我们在爬虫启动的时候,创建到数据库的连接等,item_dropped的信号就方便我们去收集在爬虫工作的期间,收集被丢弃的item的数量等等。这篇文章不是告诉大家如何使用scrapy信号的,而是告诉大家scrapy信号的工作原理,以及如何自定义我们自己的信号。
一.pydispatch模块
scrapy的信号处理使用的是dispatch模块,这个模块非常小,也非常容易看懂,使用方法如下:
from pydispatch import dispatcher
hello = object()
def func():
print('hello, world')
dispatcher.connect(func, signal=hello, sender=dispatcher.Anonymous)
dispatcher.send(signal=hello)
上面几行代码就创建了我们自己的信号hello,并将这个信号绑定到一个接收器上面即func()行数。然后我们利用send()方法发送了这个信号,打开终端运行上面的代码,就可以看到’hello, world’。上面的例子使用非常简单,其实我们还可以为func()函数参数,就像scrapy内置中的下面这个信号一样:
scrapy.signals.spider_error(failure, response, spider)
当发送上面这个信号的时候,会附带一些关键词参数传递到信号的接收器,然后我们的函数就可以利用这个参数做一些具体的分析,比如日志记录等。
具体可以参考pydispatch这个模块。
二.scrapy的signalmanager
scrapy的信号管理就是对dispatch的一个包装。底层的实现就是dispatch。那么我们如何使用scrapy的signalmanager来自定义信号呢?
from scrapy.signalmanager import SignalManager
def func():
print('hello, world')
hello = object()
mysignal = SignalManager()
mysignal.connect(func, signal=hello)
mysignal.send_catch_log(signal=hello)
上面几行代码就创建了我们自己的信号,运行之后也是输出’hello, world’。其实在scrapy中我们根本不需要自己手动的创建SignalManager的实例,我们只要使用from_crawler这个类方法就好了。
具体实现如下:
login_failure = object()
@classmethod
def from_crawler(cls, crawler):
instance = cls(*args, **kwargs)
crawler.signals.connect(instance.login_in, signal=login_failure)
return instance
上面已经定义好了一个信号接收器,我们只要在只当的地方发送这个login_failure信号即可。那我们该如何使用呢?
比如我们现在在抓取登录页面,里面的内容需要登录才能够抓取的到,而我们在解析这个网页的时候最好就要判断一下当前是否处于登录状态,如果处于未登录状态,我们就发送这个login_failure的信号,然后跳转到登录的函数重新登录,伪代码实现如下:
login_failure = object()
class MySpider(Spider):
name = 'myspider'
allowed_domains = [xxx]
login_url = 'http://www.example.com/login_in'
start_urls = [xxx]
def start_requests(self):
pass
@classmethod
def from_crawler(cls, crawler):
instance = super(MySpider, cls).from_crawler(crawler)
crawler.signals.connect(instance.login_in, signal=login_failure)
instance.signals = crawler.signals
return instance
def login_in(self, response):
#这里用于登录
#登陆成功之后就继续调用parse_response来解析网页
def parse_response(self, response):
#如果下面if语句成立,表明未登录
if xxx:
self.signals.send_catch_log(signal=login_failure, response=response)
上面的代码就简单的定义了一个信号,如果我们在解析需要登录的页面时,发现未登录,就发送login_failure信号,然后信号接收函数就会接收这个信号,然后进行登录。其中我们在send_catch_log中传递了response参数,这是关键词参数直接传递给信号接收函数的。
实现的原理非常的简单,理解了之后,我们就可以在必要的自定义非常多的信号,以供自己使用。