爬虫之scrapy-redis
redis分布式部署
scrapy框架是否可以自己实现分布式?
不可以原因有两点
其一:因为多台机器上部署的scrapy会各自拥有各自的调度器,这样就使得多台机器无法分配start_urls列表中的url。(多台机器无法共享同一个调度器)
其二:多台机器爬取到的数据无法通过同一个管道对数据进行统一的数据持久出存储。(多台机器无法共享同一个管道)
基于scrapy-redis组件的分布式爬虫
scrapy-redis组件中为我们封装好了可以被多台机器共享的调度器和管道,我们可以直接使用并实现分布式数据爬取
实现方式:
1.基于该组件的RedisSpider类 ==》基于该组件的RedisCrawlSpider类
分布式实现流程:
一.修改爬虫文件 1.导入scrapy-redis模块:from scrapy_redis.spiders import RedisSpider 2.将当前爬虫类的父类修改成RedisSpider 3.将allowed_domains和start_urls进行删除 4.添加一个新的属性redis_key = 'xxx',该属性值表示的就是可以被共享的调度器队列的名称 二.进行配置文件的配置 1.保证爬虫文件发起的请求都会被提交到可以被共享的调度器的队列中 SCHEDULER = "scrapy_redis.scheduler.Scheduler"
2.确保所有爬虫共享相同的去重指纹
DUPEFILTER = "scrapy_redis.dupefilter.RFPDuperFilter"
3.保证爬虫文件提交的item会被存储到可以被共享的管道中 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 }
4. 在redis中保持scrapy-redis用到的队列,不会清理redis中的队列,从而实现暂停和恢复功能
SCHEDULER_PERSTST = True
5.配置最终数据存储的redis数据库 REDIS_HOST = 'redis服务的ip地址' REDIS_PORT = 6379 REDIS_ENCODING = ‘utf-8’ REDIS_PARAMS = {‘password’:’123456’} 6.redis数据库的配置文件进行配置:关闭保护模式和 改bind 0.0.0.0 7.开启redis服务和客户端 8.执行爬虫文件:scrapy runspider xxx.py 9.向调度器队列中仍入一个起始的url:
代码如下
1.创建项目和应用
scrapy start project redisDemo
cd redisDemo
scrapy genspider testDemo
# -*- coding: utf-8 -*- import scrapy from scrapy_redis.spiders import RedisSpider from redisDemo.items import RedisdemoItem ''' 一.修改爬虫文件 1.导入scrapy-redis模块:from scrapy_redis.spiders import RedisSpider 2.将当前爬虫类的父类修改成RedisSpider 3.将allowed_domains和start_urls进行删除 4.添加一个新的属性redis_key = 'xxx',该属性值表示的就是可以被共享的调度器队列的名称 二.进行配置文件的配置 1.保证爬虫文件发起的请求都会被提交到可以被共享的调度器的队列中 SCHEDULER = "scrapy_redis.scheduler.Scheduler" 2.保证爬虫文件提交的item会被存储到可以被共享的管道中 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 } 3.配置最终数据存储的redis数据库 REDIS_HOST = 'redis服务的ip地址' REDIS_PORT = 6379 REDIS_ENCODING = ‘utf-8’ REDIS_PARAMS = {‘password’:’123456’} 4.redis数据库的配置文件进行配置:关闭保护模式和 改bind 0.0.0.0 5.开启redis服务和客户端 6.执行爬虫文件:scrapy runspider xxx.py 7.向调度器队列中仍入一个起始的url: ''' class TestdemoSpider(RedisSpider): name = 'testDemo' # allowed_domains = ['www.x.com'] # 不注释可能会出问题 # start_urls = ['http://www.x.com/'] # 起始url需手动输入 redis_key = 'cmdb' # 表示的就是可以被共享的调度器队列的名称 url = 'http://db.pharmcube.com/database/cfda/detail/cfda_cn_instrument/%d' # 所有的url page = 1 # 生成所有url的公共变量 def parse(self, response): item = RedisdemoItem() # 实例化item item['num'] = response.xpath('/html/body/div/table/tbody/tr[1]/td[2]/text()').extract_first() item['company_name'] = response.xpath('//html/body/div/table/tbody/tr[2]/td[2]/text()').extract_first() item['company_address'] = response.xpath('/html/body/div/table/tbody/tr[3]/td[2]/text()').extract_first() yield item urls = [] if self.page < 130000: self.page += 1 new_url = format(self.url % self.page) yield scrapy.Request(url=new_url, callback=self.parse)
import scrapy class RedisdemoItem(scrapy.Item): # define the fields for your item here like: num = scrapy.Field() company_name = scrapy.Field() company_address = scrapy.Field()
BOT_NAME = 'redisDemo' SPIDER_MODULES = ['redisDemo.spiders'] NEWSPIDER_MODULE = 'redisDemo.spiders' # Crawl responsibly by identifying yourself (and your website) on the user-agent # USER_AGENT = 'redisDemo (+http://www.yourdomain.com)' # Obey robots.txt rules ROBOTSTXT_OBEY = False USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36' # 使用的是可以被共享的调度器 # 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter" # 使用scrapy-redis组件自己的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler" # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据 SCHEDULER_PERSIST = True # 使用scrapy-redis中封装好的可以被共享的管道 ITEM_PIPELINES = { 'scrapy_redis.pipelines.RedisPipeline': 400 } # 配置redis REDIS_HOST = '192.168.12.57' # ip REDIS_PORT = 6379 # 端口 REDIS_ENCODING = 'utf-8' # 编码 # REDIS_PARAMS = {‘password’:’123456’} # 密码 没有就不写了
redis配置
开启redis服务和客户端
net start redis
在爬虫应用目录下执行爬虫文件:(多台机器启动)
scrapy runspider xxx.py
需要向调度器队列中仍入一个起始的url:(不要在开启新机器了)
在redis-cli中输入
lpush cmdb http://db.pharmcube.com/database/cfda/detail/cfda_cn_instrument/1
查看内容: lrange testDemo:items 0 -1
ok,结束
增量爬取
无非判断发送请求的url或者在数据存入时候的关键字判断
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from incrementDemo.items import IncrementdemoItem from redis import Redis import hashlib class QiubaiSpider(CrawlSpider): name = 'qiubai' # allowed_domains = ['www.x.com'] start_urls = ['https://www.qiushibaike.com/text/'] rules = ( Rule(LinkExtractor(allow=r'/text/page/\d+/'), callback='parse_item', follow=True), # 所有页面 Rule(LinkExtractor(allow=r'/text/$'), callback='parse_item', follow=True), # 第一页面 ) # 创建redis链接对象 conn = Redis(host='127.0.0.1', port=6379) def parse_item(self, response): div_list = response.xpath('//div[@id="content-left"]/div') # 获取内容标签列表 for div in div_list: item = IncrementdemoItem() item['author'] = div.xpath('./div[1]/a[2]/h2/text() | ./div[1]/span[2]/h2/text()').extract_first() # 作者 item['content'] = div.xpath('.//div[@class="content"]/span/text()').extract_first() # 内容 # 将解析到的数据值生成一个唯一的标识进行redis存储 source = item['author'] + item['content'] source_id = hashlib.sha256(source.encode()).hexdigest() # 将解析内容的唯一表示存储到redis的data_id中 ex = self.conn.sadd('data_id', source_id) if ex == 1: print('该条数据没有爬取过,可以爬取......') yield item else: print('该条数据已经爬取过了,不需要再次爬取了!!!')
items和管道
import scrapy class IncrementdemoItem(scrapy.Item): # define the fields for your item here like: author = scrapy.Field() content = scrapy.Field()
from redis import Redis import json class IncrementdemoPipeline(object): conn = None def open_spider(self, spider): self.conn = Redis(host='127.0.0.1', port=6379) def process_item(self, item, spider): dic = { 'author': item['author'], 'content': item['content'] } # print(dic) self.conn.lpush('qiubaiData', json.dumps(dic)) return item
ROBOTSTXT_OBEY = False USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36' ITEM_PIPELINES = { 'incrementDemo.pipelines.IncrementdemoPipeline': 300, }