scrapy分布式爬虫scrapy_redis二篇

时间:2023-03-09 16:53:48
scrapy分布式爬虫scrapy_redis二篇
 ===============================================================
     Scrapy-Redis分布式爬虫框架
     ===============================================================

     1.Scrapy-Rdis-project: example  (Scrapy-Redis分布式爬虫框架----范例说明(Dmoz网站)  )
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
        |
        |  1.创建项目--- scrapy startproject example
        |          example/
        |          ├── scrapy.cfg
        |          └── example
        |              ├── __init__.py
        |              ├── items.py
        |              ├── middlewares.py
        |              ├── pipelines.py
        |              ├── settings.py
        |              └── spiders
        |                  ├── __init__.py
        |                  └── my_crawlspider.py/my_redisspider.py/my_rediscrawlspider.py  # 3中类型的Scrap-Redis爬虫
        |
        |  2.明确目标--- vim items.py
        |      vim items.py
        |          import scrapy
        |
        |          class ExampleItem(scrapy.Item):
        |              name = scrapy.Field()
        |              description = scrapy.Field()
        |              link = scrapy.Field()
        |              crawled = scrapy.Field()
        |              spider = scrapy.Field()
        |              url = scrapy.Field()
        |
        |  3.编写自定义pipeline--- vim pipelines.py
        |      vim pipelins.py
        |          from datetime import datetime
        |          class ExampPipeline(object):
        |              def process_item(self,item,spider):
        |                  item['crawled'] = datetime.utcnow()  # 调用datetime.utcnow()方法获取爬虫执行时的UTC时间
        |                  item['spider'] = spider.name         # 调用spider.name属性获取当前爬虫名(因为可能同时有多个爬虫在爬取,这样可以看到谁爬了哪些网页)
        |                  return item
        |
        |  4.注册自定义pipeline及Scrapy-Redis分布式爬虫相关设置--- vim settings.py
        |      vim settings.py
        |          #-----------Scrapy-Redis分布式爬虫相关设置如下-------------
        |          DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"   # 使用Scrapy-Redis的去重组件,不再使用scrapy的去重组件
        |          SCHEDULER = "scrapy_redis.scheduler.Scheduler"               # 使用Scrapy-Redis的调度器,不再使用scrapy的调度器
        |          SCHEDULER_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"   # 使用Scrapy-Redis的从请求集合中取出请求的方式,三种方式择其一即可:
        |          #SCHEDULER_CLASS = "scrapy_redis.queue.SpiderQueue"          # 分别按(1)请求的优先级/(2)队列FIFO/(3)栈FILO 取出请求
        |          #SCHEDULER_CLASS = "scrapy_redis.queue.SpiderStack"
        |          SCHEDULER_PERSIST = True          # 允许暂停,redis请求记录不会丢失(重启爬虫不会重头爬取已爬过的页面)
        |
        |          REDIS_HOST = "200.200.200.200"    # 这两项是Redis连接设置,如果注释或不写会默认将数据存放到本机的Redis中
        |          REDIS_PORT = 6379                 # 注意:master端的Redis需要允许远程连接--配置中注释掉bind 127.0.0.1
        |
        |          #----------注册RedisPipeline/自定义pipeline------------------
        |          ITEM_PIPELINES = {
        |                  "example.pipelines.ExampPipeline":300,        # 自定义pipeline视情况选择性注册(可选)
        |                  "scrapy_redis.pipelines.RedisPipeline":400    # 将RedisPipeline注册到pipeline组件中(这样才能将数据存入Redis)
        |              }   # 注意:自定义pipeline的优先级需高于Redispipeline,因为RedisPipeline不会返回item,所以如果RedisPipeline优先级高于自定义pipeline,那么自定义pipeline无法获取到item
        |
        |  5.制作爬虫(三种Scrapy-Redis爬虫--- CrawlSpider/RedisSpider/RedisCrawlSpider)
        |
        |
        |      类型一:基于CrawlSpider类的Scrapy-Redis分布式爬虫 (无需任何修改)
        |      =================================================================================
        |          (1)生成爬虫--- scrapy genspider -t crawl my_crawlspider "dmoz.org"
        |          (2)设置爬虫--- vim my_crawlspider.py
        |          vim my_crawlspider.py
        |              from scrapy.linkextractor import LinkExtractor
        |              from scrapy.Spiders import CrawlSpider,Rule
        |
        |              class My_crawlspider(CrawlSpider):
        |                  name = "my_crawlspider"
        |                  allowed_domains = ["dmoz.org"]
        |                  start_urls = ["http://www.dmoz.org/"]
        |                  links = LinkExtractor(restrict_css('.top-cat','.sub-cat','.cat-item'))
        |                  rules = [
        |                      Rule(links,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(),    # 直接将name/description/link存入Redis数据库
        |                                  'link':div.css('.a::attr(href)').extract(),
        |                              }
        |          (3)执行爬虫方法--- scrapy crawl my_crawlspider (与正常scrapy一样,无需redis_key,比较鸡肋并不是真正的多机器爬虫)
        |
        |
        |
        |      类型二:基于RedisSpider类的Scrapy-Redis分布式爬虫 (需要部分删改)
        |      =================================================================================
        |          (1)生成爬虫--- scrapy genspider my_redisspider "dmoz.org"
        |          (2)设置爬虫--- vim my_redisspider.py
        |          vim my_redisspider.py
        |              from scrapy_redis.Spiders import RedisSpider                        # 变化1:从scrapy_redis.Spiders中引入RedisSpider
        |
        |              class My_redisspiderSpider(RedisSpider):                            # 变化2:爬虫类所继承的父类变为RedisSpider类
        |                  name = 'my_redisspider'
        |                  redis_key = "my_redisspider:start_urls"                         # 变化3:多了一个对所有爬虫发号施令的redis_key,少了allowed_domain和start_urls
        |
        |                  def __init__(self,*args,**kwargs):                              # 变化4:重写__init__方法:动态获取限制域
        |                      domain = kwargs.pop('domain','')
        |                      self.allowed_domain = filter(None,domain,split(','))
        |                      super(My_redisspiderSpider,self).__init__(*args,**kwargs)   # 注意super()里面的参数因爬虫类名不同而不同
        |
        |                  def parse(self,response):
        |                      return {    'name':response.css('.site-title::text').extract_first(),
        |                                  'url':respons.url,                              # 直接将name/url存入Redis数据库
        |                          }
        |          (3)执行爬虫方法                                                         # 变化5:爬虫执行方法变化
        |              <1>将项目代码复制到给台slave上(可更改爬虫名)并启动爬虫--- scrapy runspider my_redisspider.py
        |              <2>在master端redis上发号施令--- lpush my_redisspider:start_urls http://www.dmoz.org/
        |
        |
        |
        |      类型三:基于RedisCrawlSpider类飞Scrapy-Redis分布式爬虫 (需要部分删改)
        |      =================================================================================
        |          (1)生成爬虫--- scrapy genspider -t crawl my_rediscrawlspider "dmoz.org"
        |          (2)设置爬虫--- vim my_rediscrawlspider.py
        |          vim my_rediscrawlspider.py
        |              from scrapy.linkextractor import LinkExtractor
        |              from scrapy.Spiders import CrawlSpider,Rule
        |              from scrapy_redis.Spiders import RedisCrawlSpider                             # 变化1:从scrapy_redis.Spiders中引入RedisCrawlSpider
        |
        |              class My_rediscrawlspiderSpider(RedisCrawlSpider):                            # 变化2:爬虫类所继承的父类变为RedisCrawlSpider类
        |                  name = 'my_rediscrawlspider'
        |                  redis_key = 'my_rediscrawlspider:start_urls'                              # 变化3:多了一个对所有爬虫发号施令的redis_key,少了allowed_domain和start_urls
        |
        |                  rules =[    Rule(LinkExtractor(),callback='parse_page',follow=True),    ]
        |
        |                  def __init__(self,*args,**kwargs):                                        # 变化4:重写__init__方法:动态获取限制域
        |                      domain = kwargs.pop('domain','')
        |                      self.allowed_domain = filter(None,domain,split(','))
        |                      super(My_rediscrawlspiderSpider,self).__init__(*args,**kwargs)        # 注意super()里面的参数因爬虫类名不同而不同
        |
        |                  def parse_page(self,response):
        |                      return {    'name':response.css('.site-title::text').extract_first(),
        |                                  'url':respons.url,                                        # 直接将name/url存入Redis数据库
        |                          }
        |          (3)执行爬虫方法                                                                   # 变化5:爬虫执行方法变化
        |              <1>将项目代码复制到给台slave上(可更改爬虫名)并启动爬虫--- scrapy runspider my_rediscrawlspider.py
        |              <2>在master端redis上发号施令--- lpush my_rediscrawlspider:start_urls http://www.dmoz.org/
        |
        |  小结:
        |      如果只想使用Redis的去重和保存功能 ---- 使用类型一
        |      如果写分布式 ---- 根据情况选择类型二/类型三
        |      如果写聚焦爬虫 ---- 选择类型三
        |
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------

     2.Scrapy-Rdis-project: youyuan  (Scrapy-Redis分布式爬虫框架进阶1----有缘网:非分布式基于CrawlSpider的scrapy项目  )
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
        |
        |  1.创建项目--- scrapy startproject youyuan
        |          youyuan/
        |          ├── scrapy.cfg
        |          └── youyuan
        |              ├── __init__.py
        |              ├── items.py
        |              ├── middlewares.py
        |              ├── pipelines.py
        |              ├── settings.py
        |              └── spiders
        |                  ├── __init__.py
        |                  └── yy.py
        |
        |  2.明确目标--- vim items.py
        |      vim items .py
        |          improt scrapy
        |
        |          class YouyuanItem(scrapy.Item):
        |              username = scrapy.Field()       # 用户名
        |              age = scrapy.Field()            # 年龄
        |              header_url = scrapy.Field()     # 头像地址
        |              image_url = scrapy.Field()      # 相册个图片地址
        |              content = scrapy.Field()        # 内心独白
        |              place_from = scrapy.Field()     # 籍贯
        |              education = scrapy.Field()      # 教育
        |              hobby = scrapy.Field()          # 爱好
        |              source_url = scrapy.Field()     # 个人主页
        |              source = scrapy.Field()         # 数据来源网站
        |
        |  3.制作爬虫
        |      (1)生成爬虫--- scrapy genspider -t crawl yy "youyuan.com"
        |      (2)设置爬虫--- vim yy.py
        |      vim  yy.py
        |          import scrapy
        |          from scrapy.linkextractor import LinkExtractor
        |          from scrapy.Spiders import CrawlSpider,Rule
        |          from youyuan.items import YouyuanItem
        |          import re
        |
        |          class YySpider(CrawlSpider):
        |              name = "yy"
        |              allowed_domains = ['youyuan.com']
        |              start_urls = ["http://www.youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p1/"]
        |
        |              page_links = LinkExtractor(allow=(r'youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p\d+/')) # 获取每个显示页的连接
        |              person_links = LinkExtractor(allow =(r'youyuan.com/\d+-profile/'))                                  # 获取个人主页
        |              rules = (
        |                      Rule(page_links),                                 # 没有callback/没有follow,默认follow=True继续跟进
        |                      Rule(person_links,callback='parse_item'),         # 有callback/没有follow,默认follow=False不继续跟进
        |                  )
        |              def parse_item(self,response):
        |                  item = YouyuanItem()
        |                  item['username'] = self.get_username()
        |                  item['age'] = self.get_age()
        |                  item['header_url'] = self.get_header_url()
        |                  item['image_url'] = self.get_image_url()
        |                  item['content'] = self.get_content()
        |                  item['place_from'] = self.get_place_from()
        |                  item['education'] = self.get_education()
        |                  item['hobby'] = self.get_hobby()
        |                  item['source_url'] = self.get_source_url()
        |                  item['source'] = self.get_source()
        |
        |              #-----所谓不再使用土枪土炮,看起来高大上的方法----------
        |              def get_username(self.response):
        |                  username = response.xpath('//dl[@class="person_cen"]//div[@class="main"]/strong/text()').extract()
        |                  if len(username):
        |                      username = username[0]
        |                  else:
        |                      username = "Null"
        |                  return username.strip()
        |              def get_age(self,response):
        |                  age = response.xpath('//dl/[@class="person_cen"]//dd/p/text()').extract()
        |                  if len(age):
        |                      age = re.find_all(u"\d+岁",age[0])[0]
        |                  else:
        |                      age = "Null"
        |                  return age.strip()
        |              def get_header_url(self,response):
        |                  header_url = response.xpath('//dl[@class="person_cen"]//dt/img/@src').extract()
        |                  if len (header_url):
        |                      header_url = header_url[0]
        |                  else:
        |                      header_url = "Null"
        |                  return header_url.strip()
        |              def get_image_url(self,response):
        |                  image_url = response.xpath('//div[@class="ph_show"]/ul/li/a/img/@src').extract()
        |                  if len(image_url):
        |                      image_url = "|".join(image_url)  # 这样生成这种形式:xxxx|xxxx|xxxx|
        |                  else:
        |                      image_url = "Null"
        |                  return image_url
        |              def get_content(self,response):
        |                  content = response.xpath('//div[@class="pre_data"]/ul/li[2]//ol[1]/span/text()').extract()
        |                  if len(content):
        |                      content = content[0]
        |                  else:
        |                      content = "Null"
        |                  return content.strip()
        |              def get_place_from(self,response):
        |                  place_from = response.xpath('//div[@class="pre_data"]/ul/li[2]//ol[2]/li[1]/span/text()').extract()
        |                  if len(place_from):
        |                      place_from = place_from[0]
        |                  else:
        |                      place_from = "Null"
        |                  return place_from.strip()
        |              def get_education(self,response):
        |                  education = response.xpath('//div[@class="pre_data"]/ul/li[3]//ol[2]//li[2]/span/text()').extract()
        |                  if len(education):
        |                      education = education[0]
        |                  else:
        |                      education = "Null"
        |                  return education.strip()
        |              def get_hobby(self,response):
        |                  hobby = response.xpath('//dl[@class="person_cen"]//ol/li/text()').extract()
        |                  if len(hobby):
        |                      hobby = ",".join(hobby).replace(" ","")
        |                  else:
        |                      hobby = "Null"
        |                  return hobby.strip()
        |
        |  4.编写item pipeline--- vim pipelines.py
        |      vim pipelines.py
        |          import json
        |
        |          class YouyuanJsonPipeline(obeject):
        |              def __init__(self):
        |                  self.f = open("youyuan.json","w")
        |              def process_item(self,item,spider):
        |                  text = json.dumps(dict(item),ensure_ascii=False) + ",\n"
        |                  self.f.write(item)
        |              def close_spider(self,spider):
        |                  self.f.close()
        |
        |  5.启动上述pipeline---  vim settings.py
        |      vim settings.py
        |          ITEM_PIPELINES = {"youyuan.pipelines.YouyuanJsonPipeline":300}
        |
        |  6.执行爬虫--- scrapy crawl yy
        |
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------

     3.Scrapy-Rdis-project: youyuan  (Scrapy-Redis分布式爬虫框架进阶2----有缘网:非分布式基于CrawlSpider的scrapy项目的数据存入本机Redis  )
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
        |
        |      说明:仅仅实在上述scrapy项目的基础上进行settings.py文件的点滴修改,增加一个RedisPipeline而已 <----并不属于Scrapy-Redis分布式
        |          youyaun/
        |          ├── scrapy.cfg
        |          └── youyaun
        |              ├── __init__.py
        |              ├── items.py
        |              ├── middlewares.py
        |              ├── pipelines.py
        |              ├── settings.py     <----仅对settings.py文件做部分添加修改
        |              └── spiders
        |                  ├── __init__.py
        |                  └── yy.py
        |
        |  5.settings.py添加部分信息--- 启用Scrapy-Redis的去重组件/调度器/取出请求方式,以及注册RedisPipeline组件(让数据存入Redis)
        |      vim settings.py
        |          DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"  # 使用Scrapy-Redis的去重组件,不再使用scrapy的去重组件
        |          SCHEDULER = "scrapy_redis.scheduler.Scheduler"              # 使用Scrapy-Redis的调度器,不再使用scrapy的调度器
        |          SCHEDULER_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"  # 使用Scrapy-Redis的从请求集合中取出请求的方式,三种方式择其一即可:
        |          #SCHEDULER_CLASS = "scrapy_redis.queue.SpiderQueue"         # 分别按(1)请求的优先级/(2)队列FIFO/(3)栈FILO 取出请求
        |          #SCHEDULER_CLASS = "scrapy_redis.queue.SpiderStack"
        |          SCHEDULER_PERSIST = True                                    # 允许暂停,redis请求记录不会丢失(重启爬虫不会重头爬取已爬过的页面)
        |
        |          #REDIS_HOST = "200.200.200.200"                             # 这两项是Redis连接设置,注释或不写默认将数据存放到本机的Redis中
        |          #REDIS_PORT = 6379
        |
        |          #----------注册RedisPipeline/自定义pipeline------------------
        |          ITEM_PIPELINES = {
        |                  "youyuan.pipelines.YouyuanJsonPipeline":300,        # 自定义pipeline视情况选择性注册(可选)
        |                  "scrapy_redis.pipelines.RedisPipeline":400          # 将RedisPipeline注册到pipeline组件中(这样才能将数据存入Redis)
        |              }    # 注意:自定义pipeline的优先级需高于Redispipeline,因为RedisPipeline不会返回item,所以如果RedisPipeline优先级高于自定义pipeline,那么自定义pipeline无法获取到item
        |
        |  6.执行爬虫--- scrapy crawl yy
        |
        |      # 注意: 在原始scrapy项目的基础上,在settings.py文件中添加上述几行设置,就可以将scrapy爬取的数据存放到本机redis中
        |      # 注意: 上述要想成功保存到本机Redis,有两个前提:本机必须(pip install scrapy-redis) 和本机redis必须启动(redis-server /etc/redis.conf)
        |
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------

     4.Scrapy-Rdis-project: youyuan  (Scrapy-Redis分布式爬虫框架进阶3----有缘网:非分布式基于CrawlSpider的scrapy项目 ----> 改写为:RedisCrawlSpider类的Scrapy-Redis分布式爬虫项目 )
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
        |
        |      说明:仅仅实在原始scrapy项目的基础上对settings.py/yy.py文件进行的点滴修改即可
        |          youyaun/
        |          ├── scrapy.cfg
        |          └── youyaun
        |              ├── __init__.py
        |              ├── items.py
        |              ├── middlewares.py
        |              ├── pipelines.py
        |              ├── settings.py     <----对settings.py文件做部分添加修改(使用Scrapy-Redis的去重组件/调度器/取出请求策略/允许暂停/指明远程Redis主机,并注册RedisPipeline组件)
        |              └── spiders
        |                  ├── __init__.py
        |                  └── yy.py       <----对爬虫文件进行部分修改(引入RedisCrawlSpider爬虫类/去掉allowed_domain/去掉start_urls/增加redis_key/改写init方法动态获取限制域)
        |
        |  1.修改设置文件--- vim settings.py
        |      vim settings.py
        |          DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"  # 使用Scrapy-Redis的去重组件,不再使用scrapy的去重组件
        |          SCHEDULER = "scrapy_redis.scheduler.Scheduler"              # 使用Scrapy-Redis的调度器,不再使用scrapy的调度器
        |          SCHEDULER_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"  # 使用Scrapy-Redis的从请求集合中取出请求的方式,三种方式择其一即可:
        |          #SCHEDULER_CLASS = "scrapy_redis.queue.SpiderQueue"         # 分别按(1)请求的优先级/(2)队列FIFO/(3)栈FILO 取出请求
        |          #SCHEDULER_CLASS = "scrapy_redis.queue.SpiderStack"
        |          SCHEDULER_PERSIST = True                                    # 允许暂停,redis请求记录不会丢失(重启爬虫不会重头爬取已爬过的页面)
        |
        |          REDIS_HOST = "200.200.200.200"                              # 这两项是Redis连接设置,如果注释或不写会默认将数据存放到本机的Redis中
        |          REDIS_PORT = 6379                                           # 注意:master端的Redis需要允许远程连接--配置中注释掉bind 127.0.0.1
        |
        |          #----------注册RedisPipeline/自定义pipeline------------------
        |          ITEM_PIPELINES = {
        |                  "youyuan.pipelines.YouyuanJsonPipeline":300,        # 自定义pipeline视情况选择性注册(可选)
        |                  "scrapy_redis.pipelines.RedisPipeline":400          # 将RedisPipeline注册到pipeline组件中(这样才能将数据存入Redis)
        |              }    # 注意:自定义pipeline的优先级需高于Redispipeline,因为RedisPipeline不会返回item,所以如果RedisPipeline优先级高于自定义pipeline,那么自定义pipeline无法获取到item
        |
        |  2.修改爬虫文件--- vim yy.py
        |      vim yy.py
        |          import scrapy
        |          from scrapy linkextractor import LinkExtractor
        |          from scrapy.Spiders import CrawlSpider,Rule
        |          from youyuan.items import YouyuanItem
        |          import re
        |          from scrapy_redis.Spiders import RedisCrawlSpider           # 变化1:从scrapy_redis.Spiders中引入RedisCrawlSpider
        |
        |          class YySpider(RedisCrawlSpider):                           # 变化2:爬虫类所继承的父类变为RedisCrawlSpider类
        |              name = "yy"
        |              redis_key = "yyspider:start_urls"                       # 变化3:多了一个对所有爬虫发号施令的redis_key,少了allowed_domain和start_urls
        |
        |              def __init__(self,*args,**kwargs):                      # 变化4:重写__init__方法:动态获取限制域
        |                  domain = kwargs.pop('domain','')
        |                  self.allowed_domain = filter(None,domain,split(','))
        |                  super(YySpider,self).__init__(*args,**kwargs)       # 注意super()里面的参数因爬虫类名不同而不同
        |
        |              page_links = LinkExtractor(allow=(r'youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p\d+/'))
        |              person_links = LinkExtractor(allow =(r'youyuan.com/\d+-profile/'))
        |              .......
        |              .......      # 后面的代码都相同
        |
        |  3.爬虫的执行方式改变
        |          <1>将项目代码复制到给台slave上(可更改爬虫名)并启动爬虫--- scrapy runspider yy.py
        |          <2>在master端redis上发号施令--- lpush yyspider:start_urls http://www.youyuan.com/find/beijing/mm18-25/advance-0-0-0-0-0-0-0/p1/
        |
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------

     5.Scrapy-Rdis-project: sina2  (Scrapy-Redis分布式爬虫框架----新浪分类资讯:非分布式基于scrapy.Spider的scrapy项目 ----> 改写为:RedisSpider类的Scrapy-Redis分布式爬虫项目 )
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
        |      改写该项目注意:由于改写后数据存放到Redis,所以需要去掉"目录存储路径"相关的代码
        |
        |  1.创建项目--- scrapy startproject sina2
        |          sina2/
        |          ├── scrapy.cfg
        |          └── sina2
        |              ├── __init__.py
        |              ├── items.py
        |              ├── middlewares.py
        |              ├── pipelines.py
        |              ├── settings.py     <----对settings.py文件做部分添加修改(使用Scrapy-Redis的去重组件/调度器/取出请求策略/允许暂停/指明远程Redis主机,并注册RedisPipeline组件)
        |              └── spiders
        |                  ├── __init__.py
        |                  └── xinlang.py  <----对爬虫文件进行部分修改(引入RedisCrawlSpider爬虫类/去掉allowed_domain/去掉start_urls/增加redis_key/改写init方法动态获取限制域)
        |
        |  2.明确目标--- vim items.py
        |      vim items.py
        |          import scrapy
        |
        |          class SinaItem(scrapy.Item):
        |              parent_title = scrap.Field()       # 大类标题
        |              parent_url = scrap.Field()         # 大类URL
        |              sub_title = scrap.Field()          # 小类标题
        |              sub_url = scrap.Field()            # 小类URL
        |              #sub_filename = scrapy.Field()     # 小类目录存储路径(数据存放到Redis,不在需要这块代码)
        |              son_url = scrap.Field()            # 小类的子链接
        |              article_title = scrap.Field()      # 文章标题
        |              article_content = scrap.Field()    # 文章内容
        |
        |  3.制作爬虫
        |      (1)生成爬虫--- scrapy genspider xinlang "sina.com.cn"
        |      (2)设置爬虫--- vim xinlang.py
        |      vim xinlang.py
        |          import scrapy
        |          import os
        |          from sina.items import SinaItem
        |          import sys
        |          from scrapy_redis.Spiders import RedisSpider                 ###<-----变化1:从scrapy_redis.Spiders中引入RedisSpider
        |
        |          reload(sys)
        |          sys.setdefaultencoding('utf-8')
        |
        |          class XinlangSpider(RedisSpider):                            ###<-----变化2:爬虫类所继承的父类变为RedisSpider类
        |              name = 'xinlang'
        |              redis_key = "xinlangspider:start_urls"                   ###<-----变化3:多了一个对所有爬虫发号施令的redis_key,少了allowed_domain和start_urls
        |
        |              def __init__(self,*args,**kwargs):                       ###<-----变化4:重写__init__方法:动态获取限制域
        |                  domain = kwargs.pop('domain','')
        |                  self.allowed_domain = filter(None,domain,split(','))
        |                  super(XinlangSpider,self).__init__(*args,**kwargs)   # 注意super()里面的参数因爬虫类名不同而不同
        |
        |              def parse(self,response):
        |                  items = []
        |                  parent_title_list = response.xpath('//div[@id=\"tab01\"]/div/h3/a/text()').extract()
        |                  parent_url_list = response.xpath('//div[@id=\"tab01\"]/div/h3/a/@href').extract()
        |                  sub_title_list = response.xpath('//div[@id=\"tab01\"]/div/ul/li/a/text()').extract()
        |                  sub_url_list = response.xpath('//div[@id=\"tab01\"]/div/ul/li/a/@href').extract()
        |
        |                  for i in range(0,len(parent_title_list)):
        |                      #parent_filename = "./Data/" + parent_title_list[i]   # 创建大类的存放目录(若存在则不创建,若不存在则重新创建)
        |                      #if(not os.path.exists(parent_filename)):             # (数据存放到Redis,不在需要这块代码)
        |                      #   os.makedirs(parent_filename)
        |
        |                      for j in range(0,len(sub_url_list)):                          # 实例化SinaItem()并保存大类的URL和标题
        |                          item = SinaItem()
        |                          item['parent_title'] = parent_title_list[i]
        |                          item['parent_url'] = parent_url_list[i]
        |                          if_belong = sub_url_list[i].startwith(item['parent_url']) # 判断小类URL是否以大类URL开头(即判断小类是否属于大类)
        |                          if (if_belong):
        |                              #sub_filename = parent_filename + "/" + sub_title_list[j]   # 如果属于该大类,则判断小类存放目录是否存在,不存在则新建该目录
        |                              #if (not os.path.exists(sub_filename)):                     # (数据存放到Redis,不在需要这块代码)
        |                              #   os.makedirs(sub_filename)
        |                              item['sub_title'] = sub_title_list[j]       # 保存小类的标题/URL/存放目录,并将目前所获取item信息追加到items列表中保存
        |                              item['sub_url'] = sub_url_list[j]
        |                              item['sub_filename'] = sub_filename
        |                              items.append(item)
        |                  for item in items:              # 逐一取出子类的url,并附带上meta信息(即item),将其加入请求队列,使用second_parse()函数处理其返回的响应
        |                      yield scrapy.Request(url=item['sub_url'],meta={'meta_1':item},callback=self.second_parse)
        |
        |              def second_parse(self,response):
        |                  meta_1 = response.meta['meta_1']                      # 将meta对应的item信息赋值给meta_1(即,meta_1 = item)
        |                  son_url_list = response.xpath('//a/@href').extract()  # 匹配获取返回的孙类的URL列表
        |                  items = []
        |                  for i in range(0,len(son_url_list)):    # 循环取出孙类URL判断其是否属于某个大类(以大类的URL开头)和是否是文章(以.shml结尾),如果属于则将该孙类URL保存起来
        |                      if_belong = son_url_list[i].endwith('.shtml') and sub_url_list[i].startwith(meta_1['parent_url'])
        |                      if (if_belong):
        |                          item = SinaItem()
        |                          item['parent_title'] = meta_1['parent_title']
        |                          item['parent_url'] = meta_1['parent_url']
        |                          item['sub_title'] = meta_1['sub_title']
        |                          item['sub_url'] = meta_1['sub_url']
        |                          #item['sub_filename'] = meta_1['sub_filename']  # (数据存放到Redis,不在需要这块代码)
        |                          item['son_url'] = son_url_list[i]
        |                          items.append(item)
        |                  for item in items:              # 逐一取出孙类的url,并附带上meta信息(即第二次的item),将其加入请求队列,使用third_parse()函数处理其返回的响应
        |                      yield scrapy.Request(url=item['son_url'],meta={'meta_2':item},callback=self.third_parse)
        |
        |              def third_parse(self,response):
        |                  item = response.meta['meta_2']  # 将meta对应的(第二次获取更新的item信息)赋值给这里的item(即,item = item)
        |                  article_content = ""            # 从孙类URL返回响应中匹配出文章标题和文章内容并保存进item
        |                  article_title_list = response.xpath('//hi[@id=\"main_title\"]/text()').extract()
        |                  article_content_list = response.xpath('//div[@id=\"artibody\"]/p/text()').extract()
        |                  for content_part in article_content_list:
        |                      article_content += content_part               # 通过循环拼接成完整的文章内容
        |                  item['article_title'] = article_title_list[0]
        |                  item['article_content'] = article_content
        |                  yield item                                        # 将数据收集完整的item传递给pipeline处理
        |
        |  4.编写item pipelines--- vim pipelines.py (忽略)  # (数据存放到Redis,不再需要这块代码,不需要自定义pipeline保存数据到本地)
        |
        |  5.启用上述pipeline组件--- vim settings.py
        |      vim settings.py
        |          DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"  # 使用Scrapy-Redis的去重组件,不再使用scrapy的去重组件
        |          SCHEDULER = "scrapy_redis.scheduler.Scheduler"              # 使用Scrapy-Redis的调度器,不再使用scrapy的调度器
        |          SCHEDULER_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"  # 使用Scrapy-Redis的从请求集合中取出请求的方式,三种方式择其一即可:
        |          #SCHEDULER_CLASS = "scrapy_redis.queue.SpiderQueue"         # 分别按(1)请求的优先级/(2)队列FIFO/(3)栈FILO 取出请求
        |          #SCHEDULER_CLASS = "scrapy_redis.queue.SpiderStack"
        |          SCHEDULER_PERSIST = True                                    # 允许暂停,redis请求记录不会丢失(重启爬虫不会重头爬取已爬过的页面)
        |
        |          REDIS_HOST = "200.200.200.200"                              # 这两项是Redis连接设置,如果注释或不写会默认将数据存放到本机的Redis中
        |          REDIS_PORT = 6379                                           # 注意:master端的Redis需要允许远程连接--配置中注释掉bind 127.0.0.1
        |
        |          ITEM_PIPELINES = {  #"sina.pipelines.SinaSavePipeline":300,     # (数据存放到Redis,不再需要这块代码,不需要自定义pipeline保存数据到本地)
        |                              "scrapy_redis.pipelines.RedisPipeline":400  # 将RedisPipeline注册到pipeline组件中(这样才能将数据存入Redis)
        |                          }
        |
        |  6.爬虫的执行方式改变
        |          <1>将项目代码复制到给台slave上(可更改爬虫名)并启动爬虫--- scrapy runspider xinlang.py
        |          <2>在master端redis上发号施令--- lpush yyspider:start_urls http://news.sina.com.cn/guide/
        |
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------

     6.Scrapy-Rdis-project: youyuan  (Scrapy-Redis分布式爬虫框架----将Redis中数据持久化存储到MongoDB/MySQL中----> 将有缘网分布式爬取到Redis中的数据转存到MongoDB/MySQL中)
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------
        |
        |  要将Scrapy-Redis项目爬取到Redis中的数据转存到Mongodb/MySQL中,只需要在项目一级目录下创建两个转存的脚本文件即可
        |
        |  有缘网Scrapy-Redis项目树形图
        |          youyuan/
        |          ├── scrapy.cfg
        |          ├── process_item_for_mongodb.py     <----项目一级目录下创建将Redis数据转存到mongodb的python脚本
        |          ├── process_item_for_mysql.py       <----项目一级目录下创建将Redis数据转存到mysql的python脚本
        |          └── youyuan
        |              ├── __init__.py
        |              ├── items.py
        |              ├── middlewares.py
        |              ├── pipelines.py
        |              ├── settings.py
        |              └── spiders
        |                  ├── __init__.py
        |                  └── yy.py
        |
        |
        |  1.将Redis数据转存到mongodb中 ----- vim process_item_for_mongodb.py
        |      vim process_item_for_mongodb.py
        |          #!/usr/bin/env python
        |          #coding:utf-8
        |
        |          import redis
        |          import pymongo
        |          import json
        |          def process_item():
        |              rediscli = redis.Redis(host='200.200.200.200',port=6379,db=0)       # 创建Redis连接对象
        |              mongocli = pymongo.MongoClient(host='200.200.200.202',port=27017)   # 创建MongoDB连接对象
        |              db_name = mongocli['youyuan']                                       # 利用MongoDB连接对象在MongoDB中创建名为youyuan的数据库对象
        |              sheet_name = db_name['beijing18-24']                                # 利用该数据库对象在youyuan数据库中创建名为beijing18-24的表对象
        |              count = 0
        |              while True:
        |                  source,data = rediscli.blpop("yy:items")                        # 使用循环通过redis连接对象的blpop()方法,不断取出redis中的数据(blpop即FIFO,rlpop即FILO)
        |                  data = json.loads(data)                                         # 将取出的json字符串类型的数据转化为python类型的对象
        |                  sheet_name.insert(data)                                         # 利用mongodb的表对象的insert()方法,向表中插入(刚才转化的python对象)
        |                  count += 1
        |                  print "已经成功从redis转移" + str(count) + "条数据到mongodb"
        |
        |          if __name__ == "__main__":
        |              process_item()
        |
        |  注意:MongoDB中可以自动创建键,所以直接执行该脚本就可转移数据
        |
        |
        |  2.将Redis数据转存到mysql中 ----- vim process_item_for_mysql.py
        |      vim process_item_for_mysql.py
        |          import redis
        |          import MySQLdb
        |          import json
        |          def process_item():
        |              rediscli = redis.Redis(host='200.200.200.200',port=6379,db=0)                                               # 创建Redis连接对象
        |              mysqlcli = MySQLdb.connect(host=') # 创建MySQL连接对象
        |              count = 0
        |              while True:
        |                  source,data = rediscli.blpop('yy:items')            # 使用循环通过redis连接对象的blpop()方法,不断取出redis中的数据(blpop即FIFO,rlpop即FILO)
        |                  data = json.loads(data)                             # 将取出的json字符串类型的数据转化为python类型的对象
        |                  try:
        |                      cursor = mysqlcli.cursor()                      # 利用mysql连接对象创建cursor操作游标,并使用该操作游标向Mysql表中插入数据,数据通过python对象获取其值
        |                      cursor.execute("insert into beijing18-24 (username,age,spider,crawled) values(%s,%s,%s,%s)",[data['username'],data['age'],data['spider'],data['crawled']])
        |                      mysqlcli.commit()                               # 插入完成后需提交事务
        |                      cursor.close()                                  # 关闭操作游标
        |                      count += 1
        |                      print "已经成功从redis转移" + str(count) + "条数据到mongodb"
        |                  except:
        |                      pass
        |
        |          if __name__ == "__main__":
        |              process_item()
        |
        |  注意:MySQL中不能自动创建字段,所以在执行该脚本前,需要自行在数据库中创建好响应的数据库/表/字段,然后才能执行该脚本,转移数据到MySQL中
        |
         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------