技术标签: 爬虫 python python爬虫 分布式 redis
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
类主要包括六个方法:
那这些方法都有什么作用呢?我们注意到,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)
查看源码总结出这个方法做了以下几个事情:
%(name)s:start_urls
,并用我们自定义的Spider中的name属性进行替换CONCURRENT_REQUESTS
或者REDIS_START_URLS_BATCH_SIZE
字段的值,对redis_batch_size进行赋值REDIS_ENCODING
字段进行赋值,默认为utf-8spider_idle
信号,当收到spider_idle
信号时,将交给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
方法主要实现了以下几个功能:
REDIS_START_URLS_AS_SET
字段,则赋值为false,否则用该字段赋值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对象,就可以完成我们的自定义初始化请求的操作。
本文介绍 SAPGUI 里 ABAP 调试器的用法。我们编写的 ABAP 程序,如果执行遇到问题,可以使用 ABAP 调试器,采用单步排错的方式进行调试。我们使用下面这个简单的 ABAP 程序作为例子来讲解。REPORT z.DATA: lv_count TYPE int4 VALUE 0.DO 10 TIMES. lv_count = lv_count + sy-index.ENDDO.WRITE:/ lv_count.这个 ABAP 程序的逻辑很简单,计算整数1 + 2 + 3 _abap调试
文章目录创建用户解锁用户创建表空间自增序列触发器创建表和触发器自定义函数带参数的自定义函数,两种执行方式方式一方式二创建用户$ sqlplus / as sysdbaSQL> startupORACLE instance started.SQL> connect system@prodEnter password:Connected.SQL> create user gree identified by ok;User created.SQL> gran_oracle 自定义函数练习
一、原文地址:https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html二、原文翻译:1、嵌套类 java编程语言允许在另一个类内部定义一个类。这样的类被叫做嵌套类,这里举例说明:/** 外部类 **/public class OuterClass { /** 嵌套类 **/ ...
Shell脚本一键安装samba服务一、shell要求二、实验一、shell要求1、写一个shell脚本,能够实现一键安装并配置samba服务,执行该脚本时需要带一个路径(格式$0 $1) /opt/samba.sh /opt/samba2、目录若存在,则自动创建,任何人都可以访问,并且不需要密码,并且是只读的二、实验创建编写一个samba.sh脚本vi /opt/samba.sh#!/bin/bashif [ "$#" -ne 1 ]then echo "运行脚本格式为:
我最近使用BeautifulSoup和JSON将html笔记本转换为ipynb。诀窍是查看笔记本的JSON模式并模拟它。代码只选择输入代码单元格和标记单元格这是我的密码from bs4 import BeautifulSoupimport jsonimport urllib.requesturl = 'http://nbviewer.jupyter.org/url/jakevdp.github.c..._html转ipynb
最近鼠标用的太久感觉手快要废了打算换一个人体工学鼠标,于是打算尝试罗技的人体工学鼠标,目前Logitech有两款垂直鼠标,分别是MX Vertical 和 MX Lift。我选择两个一起买然后试试水,不行再退货其实与此同时还购买了LogitechG502 和LogitechG903,但是将分开简单测评。既然买都买了,就利用这个千载难逢的机会写点测评,有纠结的小伙伴可以参考一下。目前是在英国购买的,这两款鼠标均通过官网购买,有学生优惠,但是亚马逊时机好的时候还会打折。..._罗技lift和vertical
俄罗斯方块是一个简单的小游戏,完全可以采用单片机,裸机完成。但在嵌入式linux环境下实现,可以充分感受linux应用开发,同样是写一个程序,但功力可能大不相同。由单片机转嵌入式linux应用,框架感很重要,好的框架方便扩展,修改,可移植性强。_基于linux开发板的俄罗斯方块
springBoot中使用缓存1. 在boot启动类上加@EnableCaching注解启动缓存/** * 一. 快速体验注解 * 步骤: * 1. 开启基于注解的缓存@EnableCacheing * 2. 标注缓存注解即可 * @Cacheable 方法执行后结果放进缓存 * @...
android:state_enabled 设置触摸或点击事件是否可用状态,一般只在false时设置该属性,表示不可用状态 android:state_pressed 设置是否按压状态,一般在true时设置该属性,表示已按压状态,默认为false android:state_selected 设置是否选中状..._按钮选中状态
常见用法:F1显示当前程序或者windows的帮助内容。F2当你选中一个文件的话,这意味着“重命名”F3当你在桌面上的时候是打开“查找:所有文件”对话框F10或ALT激活当前程序的菜单栏windows键或CTRL ESC打开开始菜单CTRL ALT DELETE在win9x中打开关闭程序对话框DELETE删除被选择的选择项目,如果是文件,将被放入回收站SHIFT DELETE删除被选择的选择项目,_电脑基本操作的快捷键
文章目录前言一、poi是什么?二、使用步骤1.引入依赖2.导出excel3.导入excel总结前言excel的上传下载是常见的需求,这里记录一下使用poi实现Excel的上传和下载一、poi是什么?Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。二、使用步骤1.引入依赖代码如下(示例):<!--poi依赖--><dependency> <grou._java poi 下载excel
选中Reformat Code选项,然后右键,先移除,然后添加自己喜欢的快捷键。一次进入File—>Setting—>Keymap。1、格式化代码快捷键被占用失效的原因。然后在搜索框内输入:reformat。快捷键被占用 或者被修改。_pycharm格式化代码快捷键没反应