scrapy-redis是scrapy框架基于redis数据库的组件,用于scrapy项目的分布式开发和部署。
有如下特征:
增量式爬虫
可以继续执行程序,会发现程序在前一次的基础之上继续往后执行,是一个基于url地址的增量式的爬虫
分布式爬取
您可以启动多个spider工程,相互之间共享单个redis的requests队列。最适合广泛的多个域名网站的内容爬取。
分布式数据处理
爬取到的scrapy的item数据可以推入到redis队列中,这意味着你可以根据需求启动尽可能多的处理程序来共享item的队列,进行item数据持久化处理
Scrapy即插即用组件
Scheduler调度器 + Duplication复制 过滤器,Item Pipeline,基本spider
1、scrapy_redis的流程
- 在scrapy_redis中,所有的带抓取的对象和去重的指纹都存在所有的服务器公用的redis中
- 所有的服务器公用一个redis中的request对象
- 所有的request对象存入redis前,都会在同一个redis中进行判断,之前是否已经存入过
- 在默认的情况下,所有数据会存放在redis中
2、scrapy_redis的原理分析
我们从settings.py中的三个配置来进行分析 在settings.py中多了一下几行,这几行表示scrapy_redis
中重新实现的了去重的类,以及调度器,并且使用的RedisPipeline
:
- RedisPipeline
- RFPDupeFilter
- Schedule
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERSIST = True
ITEM_PIPELINES = {
'example.pipelines.ExamplePipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400,
}
2.1、Scrapy_redis之RedisPipeline
RedisPipeline中观察process_item,进行数据的保存,存入了redis中
2.2 Scrapy_redis之RFPDupeFilter
RFPDupeFilter 实现了对request对象的加密
2.3 Scrapy_redis之Scheduler
scrapy_redis调度器的实现了决定什么时候把request对象加入带抓取的队列,同时把请求过的request对象过滤掉
由此可以总结出request对象入队的条件
- request之前没有见过
- request的dont_filter为True,即不过滤
- start_urls中的url地址会入队,因为他们默认是不过滤
1、增量式爬虫
在domz爬虫文件中,实现方式crawlspider
类型的爬虫
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
class DmozSpider(CrawlSpider):
"""Follow categories and extract links."""
name = 'dmoz'
allowed_domains = ['dmoztools.net']
start_urls = ['http://dmoztools.net/']
# 定义数据提取规则,使用了css选择器
rules = [
Rule(LinkExtractor(
restrict_css=('.top-cat', '.sub-cat', '.cat-item')
), callback='parse_directory', follow=True),
]
def parse_directory(self, response):
for div in response.css('.title-and-desc'):
yield {
'name': div.css('.site-title::text').extract_first(),
'description': div.css('.site-descr::text').extract_first().strip(),
'link': div.css('a::attr(href)').extract_first(),
}
但是在settings.py中多了一下几行,这几行表示scrapy_redis
中重新实现的了去重的类,以及调度器,并且使用的RedisPipeline
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERSIST = True
ITEM_PIPELINES = {
'example.pipelines.ExamplePipeline': 300,
'scrapy_redis.pipelines.RedisPipeline': 400,
}
运行dmoz爬虫,观察现象
1、首先我们需要添加redis的地址,程序才能够使用redis
REDIS_URL = "redis://127.0.0.1:6379"
#或者使用下面的方式
# REDIS_HOST = "127.0.0.1"
# REDIS_PORT = 6379
2、我们执行domz的爬虫,会发现redis中多了一下三个键:
3、继续执行程序
继续执行程序,会发现程序在前一次的基础之上继续往后执行,所以domz爬虫是一个基于url地址的增量式的爬虫
2、分布式爬虫
分析demo中代码
通过观察代码:
- 继承自父类为RedisSpider
- 增加了一个redis_key的键,没有start_urls,因为分布式中,如果每台电脑都请求一次start_url就会重复
- 多了
__init__
方法,该方法不是必须的,可以手动指定allow_domains - 和scrapy中的crawlspider的区别在于,继承自的父类不相同,redis_key需要添加
实现当当网分布式爬虫
# -*- coding: utf-8 -*-
from copy import deepcopy
import scrapy
from scrapy_redis.spiders import RedisSpider
class DangdangSpider(RedisSpider):
name = 'dangdang'
allowed_domains = ['dangdang.com']
# start_urls = ['http://dangdang.com/']
redis_key = 'dangdang'
def parse(self, response):
# 获取大分类列表
div_list = response.xpath("//div[@class='con flq_body']/div")[2:-1]
for div in div_list:
item = {}
# 获取大分类名称
item['b_cate'] = div.xpath(".//dl[contains(@class,'primary_dl')]/dt//text()").extract()
# 获取中间分类列表
dl_list = div.xpath(".//dl[@class='inner_dl']")
for dl in dl_list:
# 获取中间分类名称
item['m_cate'] = dl.xpath(".//dt//text()").extract()
# 获取小分类列表
a_list = dl.xpath("./dd/a")
for a in a_list:
# 获取小分类信息
item["s_cate"] = a.xpath("./text()").extract_first()
item["s_href"] = a.xpath("./@href").extract_first()
yield scrapy.Request(
item["s_href"],
callback=self.parse_book_list,
meta={
"item": deepcopy(item)
}
)
def parse_book_list(self, response):
item = response.meta['item']
# 获取图书列表
li_list = response.xpath("//ul[@class='bigimg']/li")
for li in li_list:
item['book_name'] = li.xpath("./a/@title").extract_first()
item['book_href'] = li.xpath("./a/@href").extract_first()
item['book_author'] = li.xpath(".//p[@class='search_book_author']/span[1]/a/text()").extract()
item['book_press'] = li.xpath(".//p[@class='search_book_author']/span[3]/a/text()").extract_first()
item['book_desc'] = li.xpath(".//p[@class='detail']/text()").extract_first()
item['book_price'] = li.xpath(".//span[@class='search_now_price']/text()").extract_first()
item["book_store_name"] = li.xpath(".//p[@class='search_shangjia']/a/text()").extract_first()
item['book_store_name'] = "当当自营" if item["book_store_name"] is None else item["book_store_name"]
yield item
next_url = response.xpath("//li[@class='next']/a/@href").extract_first()
if next_url is not None:
# 构造翻页请求
yield response.follow(
next_url,
callback=self.parse_book_list,
meta={"item": item}
)
在settings中进行配置
# 指定了去重的类
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 制定了调度器的类
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 调度器的内容是否持久化
SCHEDULER_PERSIST = True
# redis的url地址
REDIS_URL = "redis://127.0.0.1:6379"
配置文件扩展
# 更改默认的配置
DEFAULT_LOG_FILENAME = '日志文件.log' # 默认日志文件名称
SPIDERS = [
"spiders.dangdang.Dangdang"
]
PIPELINES = [
"pipelines.Pipeline",
"pipelines.MysqlPipeline"
]
SPIDER_MIDS = [
]
DOWNLOADER_MIDS = [
]
# 控制最大并发数
MAX_ASYNC_NUMBER = 1
# 异步模式 thread, coroutine
ASYNC_TYPE = 'thread'
'''分布式配置'''
# 执行角色
# None 代表非分布式,发起初始请求(_start_requests), 处理请求(_execute_request_response_item)
# master代表主,只负责发起初始请求(_start_requests),并维护请求队列
# slave代表从,只负责处理请求(_execute_request_response_item)
# ROLE = 'master'
# ROLE = 'slave'
ROLE = None
# 最大重试次数
MAX_RETRY_TIMES = 3
# redis 队列的配置
REDIS_QUEUE_NAME = 'request_queue'
REDIS_QUEUE_HOST = 'localhost'
REDIS_QUEUE_PORT = 6379
REDIS_QUEUE_DB = 10
# reids 集合配置
REDIS_SET_NAME = 'filter_container'
REDIS_SET_HOST = 'localhost'
REDIS_SET_PORT = 6379
REDIS_SET_DB = 10
# 利用redis进行请求备份 的配置
REDIS_BACKUP_NAME = 'request_backup'
REDIS_BACKUP_HOST = 'localhost'
REDIS_BACKUP_PORT = 6379
REDIS_BACKUP_DB = 10