荐 scrapy_redis 自定义初始请求
先给结果:重写make_requests_from_url方法!!
如果想知道原因,请继续往下看
scrapy_redis
是做分布式爬虫的时候经常会用到的一个爬虫框架,scrapy_redis
框架是基于scrapy框架,提供了一些以redis
为基础的组件。
相对于scrapy
设置的start_urls
,在scrapy_redis
中只需要设置redis_key
就可以了,爬虫会自动去redis
的相应的key中取到url,然后包装成Request
对象,保存在redis的待爬取队列(request queue)中。
但是我们有时候可能想自定义url请求,比如我们可能想要初始化的请求是个post
而不是get
,或者由于某些原因导致我们从redis
中取出来的数据并不是可以直接请求的url。这些都需要我们对redis
中的url做进一步的处理,这里主要通过对scrapy_redis
中的RedisSpider
类进行分析,看看怎样修改才能达到目的。
我们自己实现的spider类基本上通过继承RedisSpider来完成任务调度的。首先我们看看RedisSpider类的源码:
class RedisSpider(RedisMixin, Spider):
@classmethod
def from_crawler(self, crawler, *args, **kwargs):
obj = super(RedisSpider, self).from_crawler(crawler, *args, **kwargs)
obj.setup_redis(crawler)
return obj
不难看出RedisSpider
类继承自scrapy_redis.spiders.RedisMixin
类和scrapy.spiders.Spider
类,这里使用了多继承,并用RedisMixin
调度功能覆盖Spider
原生的调度功能。
RedisMixin
类主要包括六个方法:
- start_requests
- setup_redis
- next_requests
- make_request_from_data
- schedule_next_requests
- spider_idle
那这些方法都有什么作用呢?我们注意到,RedisSpider
类在实例化之后,调用了setup_redis
方法,该方法源码如下:
def setup_redis(self, crawler=None):
"""Setup redis connection and idle signal.
This should be called after the spider has set its crawler object.
"""
if self.server is not None:
return
if crawler is None:
# We allow optional crawler argument to keep backwards
# compatibility.
# XXX: Raise a deprecation warning.
crawler = getattr(self, 'crawler', None)
if crawler is None:
raise ValueError("crawler is required")
settings = crawler.settings
if self.redis_key is None:
self.redis_key = settings.get(
'REDIS_START_URLS_KEY', defaults.START_URLS_KEY,
)
self.redis_key = self.redis_key % {'name': self.name}
if not self.redis_key.strip():
raise ValueError("redis_key must not be empty")
if self.redis_batch_size is None:
# TODO: Deprecate this setting (REDIS_START_URLS_BATCH_SIZE).
self.redis_batch_size = settings.getint(
'REDIS_START_URLS_BATCH_SIZE',
settings.getint('CONCURRENT_REQUESTS'),
)
try:
self.redis_batch_size = int(self.redis_batch_size)
except (TypeError, ValueError):
raise ValueError("redis_batch_size must be an integer")
if self.redis_encoding is None:
self.redis_encoding = settings.get('REDIS_ENCODING', defaults.REDIS_ENCODING)
self.logger.info("Reading start URLs from redis key '%(redis_key)s' "
"(batch size: %(redis_batch_size)s, encoding: %(redis_encoding)s",
self.__dict__)
self.server = connection.from_settings(crawler.settings)
# The idle signal is called when the spider has no requests left,
# that's when we will schedule new requests from redis queue
crawler.signals.connect(self.spider_idle, signal=signals.spider_idle)
查看源码总结出这个方法做了以下几个事情:
- 查看server是否是none,如果非none,则直接退出
- 初始化redis_key,如果是none的话,为其赋值
%(name)s:start_urls
,并用我们自定义的Spider中的name属性进行替换 - 初始化redis_batch_size(并行爬取数量),如果是none的话,用我们在setting文件中的
CONCURRENT_REQUESTS
或者REDIS_START_URLS_BATCH_SIZE
字段的值,对redis_batch_size进行赋值 - 初始化redis_encoding,如果是none的话,使用setting文件中的
REDIS_ENCODING
字段进行赋值,默认为utf-8 - 建立与redis的连接,并赋值给server变量
- 监听
spider_idle
信号,当收到spider_idle
信号时,将交给spider_idle
方法来处理。
schedule_next_requests & spider_idle
def schedule_next_requests(self):
"""Schedules a request if available"""
# TODO: While there is capacity, schedule a batch of redis requests.
for req in self.next_requests():
self.crawler.engine.crawl(req, spider=self)
def spider_idle(self):
"""Schedules a request if available, otherwise waits."""
# XXX: Handle a sentinel to close the spider.
self.schedule_next_requests()
raise DontCloseSpider
spider_idle
方法主要是处理spider_idle信号,调用 schedule_next_requests
方法
schedule_next_requests
方法从next_requests
方法获取Request
对象,并交给爬虫引擎去执行爬虫操作。
但我们如果想自定义初始请求应该怎么做呢?
我们知道启动一个继承自RedisSpider
的分布式爬虫,在实例化完成之后,会调用scrapy_redis.spiders.RedisMixin.start_requests
方法,这个方法返回了一个next_requests
方法的执行结果。
def start_requests(self):
"""Returns a batch of start requests from redis."""
return self.next_requests()
那么next_requests
方法实现了一个什么样的功能呢?先看看代码是怎么写的
def next_requests(self):
"""Returns a request to be scheduled or none."""
use_set = self.settings.getbool('REDIS_START_URLS_AS_SET', defaults.START_URLS_AS_SET)
fetch_one = self.server.spop if use_set else self.server.lpop
# XXX: Do we need to use a timeout here?
found = 0
# TODO: Use redis pipeline execution.
while found < self.redis_batch_size:
data = fetch_one(self.redis_key)
if not data:
# Queue empty.
break
req = self.make_request_from_data(data)
if req:
yield req
found += 1
else:
self.logger.debug("Request not made from data: %r", data)
if found:
self.logger.debug("Read %s requests from '%s'", found, self.redis_key)
可以得到next_requests
方法主要实现了以下几个功能:
- 初始化use-set,bool类型,如果setting中没有
REDIS_START_URLS_AS_SET
字段,则赋值为false,否则用该字段赋值 - 定义fetch_one(从redis中获取url的方式,spop和lpop)
- 如果redis_key对应的set中,包含比
redis_batch_size
多的数据的话,遍历返回redis_batch_size
个Request对象,如果少的话,就全部返回。这里调用了make_request_from_data
方法,来生成Request对象。
def make_request_from_data(self, data):
"""Returns a Request instance from data coming from Redis.
By default, ``data`` is an encoded URL. You can override this method to
provide your own message decoding.
Parameters
----------
data : bytes
Message from redis.
"""
url = bytes_to_str(data, self.redis_encoding)
return self.make_requests_from_url(url)
查看make_request_from_data
方法的代码发现在将 data 参数转为str类型之后,调用了scrapy.spiders.Spider.make_requests_from_url
方法来生成的Request对象,
def make_requests_from_url(self, url):
""" This method is deprecated. """
warnings.warn(
"Spider.make_requests_from_url method is deprecated: "
"it will be removed and not be called by the default "
"Spider.start_requests method in future Scrapy releases. "
"Please override Spider.start_requests method instead."
)
return Request(url, dont_filter=True)
查看make_requests_from_url
方法的代码,发现这个方法只是将url包装成Request对象而已,所以我只需要重写make_requests_from_url
方法,在其中自定义我们想要的操作,然后返回一个Request对象,就可以完成我们的自定义初始化请求的操作。
本文地址:https://blog.csdn.net/Pual_wang/article/details/107567562
上一篇: python之SMTP发送邮件信息
下一篇: 如何设置双路由器 双路由器的设置方法