scrapy-redis
讲师的博客:https://www.cnblogs.com/wupeiqi/p/6912807.html
scrapy-redis是一个基于redis的scrapy组件,通过它可以快速实现简单分布式爬虫程序,该组件本质上提供了三大功能:
- scheduler : 调度器
- dupefilter : URL去重规则(被调度器使用)
- pipeline : 数据持久化
准备工作
安装模块
pip install scrapy-redis
创建爬虫应用
项目就不重新创建了,直接在之前Scrapy课程的项目里,再创建一个新的应用:
> cd PeppaScrapy
> scrapy genspider [项目名称] [起始url]
通过环境变量指定配置文件
之前的课程上,已经对配置文件做了一些设置了。这里既不想把之前的内容覆盖掉,也不想受到之前配置的影响。
可以通过命令行的-s参数或者是在类里写一个名称为custom_settings的字典,一个一个参数的进行设置。这两个的优先级都很高。但是都不是配置文件的形式,这里可以单独再写一个配置文件,然后通过设置系统环境变量的方式来指定爬虫应用加载的配置文件。
在原来的settings.py的同级目录里创建一个mysettings.py的配置文件,文件可以在任意位置,只要能配python导入。然后设置一个系统环境变量即可:
import os
os.environ.setdefault('SCRAPY_SETTINGS_MODULE', 'PeppaScrapy.mysettings')
连接redis设置
把设置都写在配置文件中即可:
# redis 配置文件
REDIS_HOST = 'localhost' # 主机名
REDIS_PORT = 6379 # 端口
# REDIS_URL = 'redis://user:pass@hostname:9001' # 连接URL,和上面2条一样,也是连接redis的设置,优先使用这个
# REDIS_PARAMS = {} # Redis连接参数,这里有一些默认值,可以去源码里看
# REDIS_PARAMS['redis_cls'] = 'myproject.RedisClient' # 指定连接Redis的Python模块,默认:redis.StrictRedis
# REDIS_ENCODING = "utf-8" # redis编码类型,默认:'utf-8'
连接参数
这里提供两种方式连接,一种是前2个设置,指定HOST和PORT。如果还需要用户名和密码就没办法了。
另一种就是用一个REDIS_URL参数,按照上面的格式把所有的信息填好。另外这里REDIS_URL参数的优先级高,就是说如果设置了REDIS_URL参数,那么上面的2个参数就没有效果了。这个逻辑可以在scrapy_redis.connection.py里的get_redis函数里找到。
其他连接参数
REDIS_PARAMS,是连接redis时使用的其他参数。所以其实用户名,密码也是可以写在这里面的。即使什么都不写,scrapy-redis模块本身也设置了一些默认值,可以在scrapy_redis.defaults.py里找到:
REDIS_PARAMS = {
'socket_timeout': 30,
'socket_connect_timeout': 30,
'retry_on_timeout': True,
'encoding': REDIS_ENCODING, // 这个值默认是'utf-8',也在这个文件里
}
具体还可以设置哪些参数,就是看连接Redis的那个类的构造函数了,源码里就是用这个字典直接**kwargs创建对象了。默认的用来连接Redis的模块是redis.StrictRedis,下面是这个类的构造函数的参数列表:
def __init__(self, host='localhost', port=6379,
db=0, password=None, socket_timeout=None,
socket_connect_timeout=None,
socket_keepalive=None, socket_keepalive_options=None,
connection_pool=None, unix_socket_path=None,
encoding='utf-8', encoding_errors='strict',
charset=None, errors=None,
decode_responses=False, retry_on_timeout=False,
ssl=False, ssl_keyfile=None, ssl_certfile=None,
ssl_cert_reqs='required', ssl_ca_certs=None,
max_connections=None):
REDIS_ENCODING,是指定编码类型,默认utf-8没太多要说的。
用于连接redis的所有参数,除了以下4个是单独写的,其他的都写在REDIS_PARAMS这个字典里。按照上面构造函数里的变量名称写。这4个可以单独写的,其实也就是做了一步映射而已:
# 这个也是 scrapy_redis.connection.py 里的源码
SETTINGS_PARAMS_MAP = {
'REDIS_URL': 'url',
'REDIS_HOST': 'host',
'REDIS_PORT': 'port',
'REDIS_ENCODING': 'encoding',
}
REDIS_PARAMS['redis_cls'],指定连接Redis的Python模块。按上面说的,处理那4个参数,其他的都只能写在REDIS_PARAMS这个字典里。
源码文件
上面这些主要是翻了3个文件里的源码:
- scrapy_redis.defaults.py
- scrapy_redis.connection.py
- redis.client.py
URL去重
模块提供了一个使用redis做去重的规则,只需要按之前在Scrapy课程里学的,设置自定的去重规则即可。具体就是加上一条配置:
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
url去重算法
这里的去重规则,会先把url转换成唯一标识,然后再存到集合里。
源码里会把url通过hashlib进行哈希运算,用的是SHA1·。把原本的字符串转成数字签名。这么做的好处就是,即使url会很长,但是生成的数字签名的长度是固定的。这里可以注意下这个技巧,把url存储在redis里的时候,占用的长度是固定的,关键是不会太长。
另外这个转换过程还有个特点。如果url里是带get参数的,如果参数的值是一样的,但是出现的顺序不同,转换后生成的唯一标识也是一样的。比如像下面这样:
url1 = 'https://www.baidu.com/s?wd=sha1&ie=utf-8'
url2 = 'https://www.baidu.com/s?ie=utf-8&wd=sha1'
像这种,只是参数的先后顺序不同,两个请求应该还是同一个请求,应该要去重。这里在这种情况下生成的唯一标识是一样的,所以可以做到去重。
这个去重规则实现了去重的算法,是要被调度器使用的。在去重的这个类里,通过request_seen这个方法来判断是否有重复。下面的调度器里就会调用这个方法来判断避免重复爬取。
调度器
在配置文件里,加上下面的配置来指定调度器,这个是scrapy读取的配置:
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
接下来出一些在scrapy-redis模块里要读取的配置。其中部分在scrapy_redis.defaults.py里有默认设置,还有的在代码里也有默认的逻辑,所以配置文件里可以什么都不写,需要改变设置的话也可以自己在配置文件里指定。
使用的队列
SCHEDULER_QUEUE_CLASS = 'scrapy_redis.queue.PriorityQueue'
指定调度器存取请求使用的队列类型,有3个可选的:
- PriorityQueue,默认。通过redis的有序集合来实现
- FifoQueue,队列。通过redis的List实现的,使用lpush和rpop进行存取
- LifoQueue,栈。也是通过redis的List实现的,使用lpush和lpop进行存取
请求存放在redis中的key
SCHEDULER_QUEUE_KEY = '%(spider)s:requests'
这样,不同的爬虫应用在redis中可以使用不同的key存取请求。
对保存到redis中的数据进行序列化
SCHEDULER_SERIALIZER = "scrapy_redis.picklecompat"
这个设置在defaults里没有,如果没有设置,就用 scrapy_redis.picklecompat 这个。而底层用的还是pickle模块。这样可以把对象序列化之后在redis中保存起来。
清空调度器和去重记录
下面的2个参数都是布尔型的,如果不设置就是False,如果需要开启,就设置为True:
SCHEDULER_PERSIST = True # 是否在关闭时候保留,调度器和去重记录
SCHEDULER_FLUSH_ON_START = True # 是否在开始之前清空,调度器和去重记录
上面两个参数的效果都是判断后决定是否要执行调度器的一个flush方法。而这个flush方法里执行的则是清空调度器和去重记录。
注意这两个参数的效果。默认都是False,就是在开始之前不清空调度器和去重记录,在结束的时候也不保留调度器和去重记录。测试的话一般不需要保留。如果是上线使用,一般需要保留调度器和去重记录,那么就按下面来设置:
# 保留调度器和去重记录的设置
SCHEDULER_PERSIST = True
SCHEDULER_FLUSH_ON_START = False
获取请求时等待的时间
调度器获取请求时,如果数据为空,进行等待的时间,默认为0,单位秒:
SCHEDULER_IDLE_BEFORE_CLOSE = 0
这个实际就是redis的blpop和brpop操作时的timeout参数,默认为0。如果为0,就使用lpop和rpop这2个不阻塞的pop方法。如果大于0,就使用阻塞的pop方法。这里不会用到timeout为0的阻塞pop,所以不会一直阻塞,总会返回的。无论阻塞还是不阻塞,取不到值就返回None。
另外,调度器默认用的是有序集合,redis的有序集合取值没有阻塞也没有timeout,所以这个值是无效的。
去重规则的参数
下面2个是去重规则使用的参数,其中一个在去重的时候讲过了。另一个就是在redis里使用的key:
SCHEDULER_DUPEFILTER_KEY = '%(spider)s:dupefilter' # 去重规则在redis中保存时对应的key
SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter' # 去重规则对应处理的类
数据持久化
模块还提供了数据持久化,在scrapy的配置里指定好数据持久化使用的类:
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300,
}
# 下面是两个持久化时可以定制的配置
# PIPELINE_KEY = '%(spider)s:items'
# REDIS_ITEMS_SERIALIZER = "scrapy.utils.serialize.ScrapyJSONEncoder"
先去看看这个类里具体做了些什么:
from twisted.internet.threads import deferToThread
class RedisPipeline(object):
def process_item(self, item, spider):
return deferToThread(self._process_item, item, spider)
def _process_item(self, item, spider):
key = self.item_key(item, spider)
data = self.serialize(item)
self.server.rpush(key, data)
return item
先看process_item方法,返回一个deferToThread,在twisted里的相当于拿一个线程来做一个事。具体twisted干了啥就忽略把,主要是处理_process_item这个方法。所以真正做的处理是在_process_item方法里。
这里先把item的数据通过self.serialize函数做序列化,然后就是下面的rpush存到redis里去了。这里有2个可以定制的参数,一个是序列化的方法,一个是存储的redis里使用的Key。
默认使用下面的Key,当然可以在配置文件里指定:
PIPELINE_KEY = '%(spider)s:items'
默认使用的序列化的方法是一个做了一些定制的json方法,在下面这里:
from scrapy.utils.serialize import ScrapyJSONEncoder
也是可以通过参数来指定自己的序列化方法的:
REDIS_ITEMS_SERIALIZER = "scrapy.utils.serialize.ScrapyJSONEncoder"
爬虫和起始url
之前在spiders文件夹下写爬虫的时候,都是继承scrapy.Spider然后写自己的类。
模块也提供了一个它自己的类,可以直接继承模块提供的这个类来写爬虫:
from scrapy_redis.spiders import RedisSpider
class TestSpider(RedisSpider):
name = 'jandan'
allowed_domains = ['jandan.net']
用了模块的爬虫后,起始url也可以直接从redis里获取了,相关的配置有下面2个:
START_URLS_KEY = '%(name)s:start_urls'
START_URLS_AS_SET = False
第一个设置,是在redis里用来存储起始url的key,只有通过这个key才能从redis里获取到起始的url。
第二个设置,是指定在redis里使用是否使用集合来存储起始url,默认不用集合,用的就是列表。
使用模块提供的这个爬虫,会一直运行,永远不会停止。没有任务的话应该就是一直等着直到获取到新的任务。可以往redis对应的起始url的key里添加数据,就会自动的开始爬虫。适合在线上使用。
而scrapy模块提供的scrapy.Spider这个爬虫适合平时自己用,爬完就结束了。