• 爬虫07 /scrapy图片爬取、中间件、selenium在scrapy中的应用、CrawlSpider、分布式、增量式


    爬虫07 /scrapy图片爬取、中间件、selenium在scrapy中的应用、CrawlSpider、分布式、增量式

    1. scrapy图片的爬取/基于管道类实现

    • 爬取流程:

      1. 爬虫类中将解析到的图片地址存储到item,将item提交给指定的管道

      2. 在管道文件中导包:from scrapy.pipelines.images import ImagesPipeline

      3. 基于ImagesPipeline父类,自定义一个管道类

      4. 重写管道类中的如下三个方法:

        指定文件路径

        def file_path(self,request,response=None,info=None):
            # 可以接收get_media_requests传递过来的meta,获取图片名称
            request.meta
            
        # 在配置文件设置
        # IMAGES_STORE=‘./imgs’
        

        对图片地址发送请求

        def get_media_requests(self,item,info):
        	yield scrapy.Request(url,meta)
        
        # 将item中存储的图片地址进行get请求发送
        # meta就会传递给file_path方法
        

        返回item,交由下一个管道类处理

        def item_completed(self,request,item,info):
            return item
        
    • 代码示例

      items.py

      import scrapy
      
      class ImgproItem(scrapy.Item):
          img_src = scrapy.Field()
          img_name = scrapy.Field()
      

      imgDemo.py

      # -*- coding: utf-8 -*-
      import scrapy
      from imgPro.items import ImgproItem
      
      class ImgdemoSpider(scrapy.Spider):
          name = 'imgDemo'
          start_urls = ['http://www.521609.com/daxuemeinv/']
      
          def parse(self, response):
              li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li')
              for li in li_list:
                  img_src = 'http://www.521609.com'+li.xpath('./a[1]/img/@src').extract_first()
                  img_name = li.xpath('./a[2]/b/text() | ./a[2]/text()').extract_first()+'.jpg'
                  print(img_name)
                  item = ImgproItem()
                  item['img_src'] = img_src
                  item['img_name'] = img_name
                  yield item
      

      pipelines.py

      # -*- coding: utf-8 -*-
      import scrapy
      from scrapy.pipelines.images import ImagesPipeline
      
      class ImgproPipeline(ImagesPipeline):
          # 指定文件存储的目录(文件名)
          def file_path(self,request,response=None,info=None):
              # 接收meta
              item = request.meta['item']
              return item['img_name']
          
          # 对指定资源进行请求发送
          def get_media_requests(self,item,info):
              # meta可以传递给file_path
              yield scrapy.Request(item['img_src'],meta={'item':item})
      
          # 用于返回item,将item传递给下一个即将被执行的管道类
          def item_completed(self,request,item,info):
              return item
      

      settings.py

      ITEM_PIPELINES = {
         'imgPro.pipelines.ImgproPipeline': 300,
      }
      
      # 指定文件存储的目录
      IMAGES_STORE = './imgs'
      

    2. 中间件的使用

    • scrapy中间件:/在爬虫中一般使用下载中间件

      下载中间件/DownloaderMiddleware:位于引擎和下载器之间;

      爬虫中间件/SpiderMiddleware:位于引擎和Spider之间

    • 作用:

      拦截所有的请求和响应

    • 拦截请求:

      process_request拦截正常的请求

      process_exception拦截异常的请求,必须有return request返回值,对异常请求重新发送

      篡改请求的头信息:

      process_request
      request.headers['User-Agent'] = 'chorme'
      

      设置代理:

      process_exception:
      request.meta['proxy'] = 'http://ip:port'  # 是字符串不是字典
      

      注意:process_exception,return request的作用,将修正后的请求重新发送

    • 拦截响应:

      篡改响应数据:

      1. 不满足需求的响应数据对应的一定是不满足需求的响应对象,动态加载的数据就是不满足需求的对象
      2. 直接更换响应对象
    • 代码示例:

      配置文件settings.py:将中间件配置打开

      DOWNLOADER_MIDDLEWARES = {
         'middlePro.middlewares.MiddleproDownloaderMiddleware': 543,
      }
      

      爬虫文件middle.py:

      # -*- coding: utf-8 -*-
      import scrapy
      
      class MiddleSpider(scrapy.Spider):
          name = 'middle'
          start_urls = ['http://www.521609.com/daxuemeinv/']
      
          def parse(self, response):
              li_list = response.xpath('//*[@id="content"]/div[2]/div[2]/ul/li')
              for li in li_list:
                  img_src = 'http://www.123'+li.xpath('./a[1]/img/@src').extract_first()
                  yield scrapy.Request(img_src)
      

      中间件middlewares.py

      # -*- coding: utf-8 -*-
      from scrapy import signals
      
      import random
      class MiddleproDownloaderMiddleware(object):
          # 拦截正常请求
          # 参数request:拦截到的请求
          user_agent_list = ['UA池']
          def process_request(self, request, spider):
              print('proces_request!!!')
              # UA伪装
              request.headers['User-Agent'] = random.choice(self.user_agent_list)
              return None
          # 拦截所有的响应
          def process_response(self, request, response, spider):
      
              return response
          # 拦截发生异常的请求,目的就是为了将异常的请求进行修正,然后将修正之后的正常的请求进行重新发送
          def process_exception(self, request, exception, spider):
              # 代理操作
              request.meta['proxy'] = 'http://ip:port'
              print('i am exception!!!')
              return request
      

    3. selenium在scrapy中的应用

    • selenium在scrapy中的编码流程:

      1. 在爬虫类中实例化浏览器对象:将实例化出来的浏览器作为爬虫类的一个属性;属性可以交互给中间件类

      2. 编写自动化操作:写在中间件的process_response中

      3. 关闭浏览器: 写在爬虫类的closed(self)方法中,只会在关闭的时候执行一次

    • 需求:爬取网易新闻中国内,国际,军事,航工,无人机这五个板块下所有的新闻标题和内容

      分析:

      1. 通过中间件更换不满足需求的响应对象
      2. 在scrapy中应用selenum
      3. 每一个板块中显示的新闻标题是动态加载的

      代码实现:

      settings.py

      # 打开中间件配置
      DOWNLOADER_MIDDLEWARES = {
         'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,
      }
      

      wangyi.py:爬虫文件

      # -*- coding: utf-8 -*-
      import scrapy
      from selenium import webdriver
      from wangyiPro.items import WangyiproItem
      
      class WangyiSpider(scrapy.Spider):
          name = 'wangyi'
          start_urls = ['https://news.163.com/']
          
          # 整个项目中涉及的响应对象个数:1+5+n
          # 解析:解析五个新闻板块对应的url
          five_model_urls = []
          bro = webdriver.Chrome(executable_path=r'chromedriver.exe')
          
          # 方法只会被调用一次
          def closed(self,spider):
              self.bro.quit()
      
          def parse(self, response):
              li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
              model_indexs = [3,4,6,7,8]
              for index in model_indexs:
                  li_tag = li_list[index]
                  # 解析出了每一个板块对应的url
                  model_url = li_tag.xpath('./a/@href').extract_first()
                  self.five_model_urls.append(model_url)
                  # 对每一个板块的url进行手动的请求发送
                  yield scrapy.Request(model_url,callback=self.parse_model)
                  
          # 解析:每一个板块中的新闻标题和新闻详情页的url(两个值都是动态加载出来的)
          def parse_model(self,response):
              # 遇到了不满足需求的响应对象就是当前方法中的response参数
              div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div/div/ul/li/div/div')
              for div in div_list:
                  title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
                  detail_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()
                  item = WangyiproItem()
                  item['title'] = title
                  if detail_url:
                      yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item})
          
          # 爬取新闻详情
          def parse_detail(self,response):
              item = response.meta['item']
              content = response.xpath('//*[@id="endText"]//text()').extract()
              content = ''.join(content)
              item['content'] = content
      
              yield item
      

      middlewares.py

      # -*- coding: utf-8 -*-
      from scrapy import signals
      from scrapy.http import HtmlResponse
      from time import sleep
      
      
      class WangyiproDownloaderMiddleware(object):
      
          def process_request(self, request, spider):
              return None
          
          # 拦截所有的响应(1+5+n),只有5个响应不满足需求,动态加载数据只能爬取少量数据   
          def process_response(self, request, response, spider):   
              # request.url:每一个响应对应的url
              # spider.five_model_urls:5个板块对应的url
              if request.url in spider.five_model_urls:
                  # 满足if条件的response就是5个板块对应的response
                  spider.bro.get(request.url)   # 对每一个板块对应的url进行get请求发送
                  sleep(3)
                  spider.bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
                  sleep(2)
                  page_text = spider.bro.page_source
                  new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
                  return new_response
              else:
                  return response
      
          def process_exception(self, request, exception, spider):
              pass
      
      # 流程总结:
      # 1.将拦截到所有的响应中的指定5个不满足需求的响应对象找出
      # 2.将这5个响应对象删除,实例化5个新的响应对象
      # 3.保证5个新的响应对象中包含动态加载出来的新闻标题数据
      # 4.将满足需求的5个新的响应对象返回
      

    4. CrawlSpider

    • 作用:

      是Spider的一个子类,可以实现全站数据爬取

    • 实现流程:

      1. 创建工程:scrapy startproject proName
      2. 切换到工程目录:cd proName
      3. 创建爬虫文件:scrapy genspider -t crawl spiderName www.xxx.com
    • 关键点:

      LinkExtracor链接提取器:可以根据指定的规则(allow=正则)进行链接的提取

      Rule规则解析器:将链接提取器提取到的链接进行请求发送,然后根据指定的规则(callback)进行数据解析

      参数follow=True:将链接提取器 继续作用 到链接提取器提取到的链接 所对应的页面源码中

    • 代码示例:CrawlSpider实现深度爬取

      items.py :

      import scrapy
      
      class SunproItem(scrapy.Item):
          title = scrapy.Field()
          status = scrapy.Field()
      
      class SunproItem_content(scrapy.Item):
          content = scrapy.Field()
      

      sun.py/爬虫文件:

      # -*- coding: utf-8 -*-
      import scrapy
      from scrapy.linkextractors import LinkExtractor
      from scrapy.spiders import CrawlSpider, Rule
      from sunPro.items import SunproItem_content,SunproItem
      
      class SunSpider(CrawlSpider):
          name = 'sun'
          start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=']
          # 实例化了一个链接提取器对象
          # 作用:可以根据指定的规则(allow=(正则))进行链接的提取,筛选链接
          link = LinkExtractor(allow=r'type=4&page=d+')  # 提取页码链接
          link_detail = LinkExtractor(allow=r'question/d+/d+.shtml')
          rules = (
              # 规则解析器
              # 作用:规则解析器可以将链接提取器提取到的链接进行请求发送且进行指定规则(callback)的数据解析
              Rule(link, callback='parse_item', follow=False),
              Rule(link_detail,callback='parse_detail')
          )
          # 该方法调用的次数请求的个数
          def parse_item(self, response):
              tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
              for tr in tr_list:
                  title = tr.xpath('./td[2]/a[2]/@title').extract_first()
                  status = tr.xpath('./td[3]/span/text()').extract_first()
                  item = SunproItem()
                  item['title'] = title
                  item['status'] = status
                  yield item
      
          def parse_detail(self,response):
              content = response.xpath('/html/body/div[9]/table[2]//tr[1]').extract()
              content = ''.join(content)
              item = SunproItem_content()
              item['content'] = content
      
              yield item
              
      # link:实例化了一个链接提取器对象
      # 作用:可以根据指定的规则(allow=(正则))进行链接的提取
      
      # Rule:规则解析器
      # 作用:规则解析器可以将链接提取器提取到的链接进行请求发送且进行指定规则(callback)的数据解析
      

      pipelines.py/管道:

      class SunproPipeline(object):
          def process_item(self, item, spider):
              if item.__class__.__name__ == 'SunproItem_content':
                  print(item['content'])
              else:
                  print(item['title'],item['status'])
              return item
          
      # 获取对象从属的父类的名称:.__class__.__name__
      

    5. 分布式

    • 概念:

      可以使用多台电脑组件一个分布式机群,让其执行同一组程序,对同一组网络资源进行联合爬取。

    • scrapy实现分布式:

      原生的scrapy是无法实现分布式:调度器无法被共享,管道无法被共享

      基于scrapy+redis(scrapy&scrapy-redis组件)实现分布式

    • scrapy-redis组件:

      作用:提供可被共享的管道和调度器

      环境安装:

      pip install scrapy-redis
      

      编码流程:

      1.创建工程:scrapy startproject proName

      2.切换到工程目录:cd proName

      3.创建crawlspider的爬虫文件:scrapy genspider -t crawl spiderName www.xxx.com

      4.修改一下爬虫类:

      • 导包:from scrapy_redis.spiders import RedisCrawlSpider
      • 修改当前爬虫类的父类:RedisCrawlSpider
      • allowed_domains和start_urls删除
      • 添加一个新属性:redis_key = 'xxxx'可以被共享的调度器队列的名称

      5.修改配置settings.py

      &指定管道

      ITEM_PIPELINES = {  
          	'scrapy_redis.pipelines.RedisPipeline': 400
           }
      

      &指定调度器

      # 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
      DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
      
      # 使用scrapy-redis组件自己的调度器
      SCHEDULER = "scrapy_redis.scheduler.Scheduler"
      
      # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
      SCHEDULER_PERSIST = True
      

      &指定redis数据库

      REDIS_HOST = 'redis服务的ip地址'
      REDIS_PORT = 6379
      

      6.配置redis数据库(redis.windows.conf)

      # 关闭默认绑定
      56Line:#bind 127.0.0.1
      
      # 关闭保护模式
      75line:protected-mode no
      

      7.启动redis服务(携带配置文件)和客户端

      redis-server.exe redis.windows.conf
      redis-cli
      

      8.执行工程

      scrapy runspider spider.py
      
      # 先执行工程,让其产生调度器
      

      9.将起始的url扔入到可以被共享的调度器的队列(sun)中

      在redis-cli中操作:lpush sun www.xxx.com
      

      10.redis

      redis_key:items:存储的就是爬取到的数据
      
    • 代码示例:

      fbs.py:爬虫文件

      # -*- coding: utf-8 -*-
      import scrapy
      from scrapy.linkextractors import LinkExtractor
      from scrapy.spiders import CrawlSpider, Rule
      from scrapy_redis.spiders import RedisCrawlSpider
      from fbsPro.items import FbsproItem
      
      class FbsSpider(RedisCrawlSpider):
          name = 'fbs'
          # allowed_domains = ['www.xxx.com']
          # start_urls = ['http://www.xxx.com/']
          redis_key = 'sun'  # 可以被共享的调度器队列的名称
          link = LinkExtractor(allow=r'type=4&page=d+')
          rules = (
              Rule(link, callback='parse_item', follow=True),
          )
      
          def parse_item(self, response):
              tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr')
              for tr in tr_list:
                  title = tr.xpath('./td[2]/a[2]/@title').extract_first()
                  status = tr.xpath('./td[3]/span/text()').extract_first()
      
                  item = FbsproItem()
                  item['title'] = title
                  item['status'] = status
      
                  yield item
      

      settings.py

      #指定管道
      ITEM_PIPELINES = {
          'scrapy_redis.pipelines.RedisPipeline': 400
      }
      #指定调度器
      # 增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化
      DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
      
      # 使用scrapy-redis组件自己的调度器
      SCHEDULER = "scrapy_redis.scheduler.Scheduler"
      
      # 配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据
      SCHEDULER_PERSIST = True
      
      #指定redis
      REDIS_HOST = '192.168.16.64'
      REDIS_PORT = 6379
      

    5. 增量式

    • 概念

      监测网站数据更新的情况。爬取到最新更新出来的数据。

    • 关键点:

      核心:去重,利用set

      记录表:记录爬取过的信息,需要持久化存储,redis中使用set进行持久化存储

    • 需求:url=https://www.4567tv.tv/frim/index1.html,爬取其电影的名称以及详情页的简介

      分析:

      1. 对于深度爬取:一般采用url作为唯一标识进行存储;

        比如:先在首页爬取链接,再根据这些链接爬取相应的内容,爬取图片

      2. 对于非深度爬取:一般通过数据指纹作为数据的唯一标识

        比如:没有动态加载的数据,所有数据都在本页

      3. 数据指纹:一组数据的唯一标识

    • 代码示例:

      movie.py:爬虫文件

      # -*- coding: utf-8 -*-
      import scrapy
      from scrapy.linkextractors import LinkExtractor
      from scrapy.spiders import CrawlSpider, Rule
      from redis import Redis
      from moviePro.items import MovieproItem
      
      class MovieSpider(CrawlSpider):
          name = 'movie'
          start_urls = ['https://www.4567tv.tv/frim/index1.html']
          conn = Redis(host='127.0.0.1',port=6379)
          link = LinkExtractor(allow=r'frim/index1-d+.html')  #提取页码链接
          rules = (
              Rule(link, callback='parse_item', follow=False),
          )
      
          def parse_item(self, response):
              # 电影名称+详情页的url
              li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
              for li in li_list:
                  name = li.xpath('./div/a/@title').extract_first()
                  item = MovieproItem()
                  item['name'] = name
                  detail_url = 'https://www.4567tv.tv'+li.xpath('./div/a/@href').extract_first()
                  ex = self.conn.sadd('movie_record',detail_url)
                  if ex == 1:  # 这部电影之前没有存在于记录表中
                      print('有最新更新的数据!!!!!!')
                      yield scrapy.Request(url=detail_url,callback=self.parse_detail,meta={'item':item})
                  else:
                      print('暂无新数据的更新......')
                      
          def parse_detail(self,response):
              item = response.meta['item']
              desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
              item['desc'] = desc
              yield item
      

      items.py

      import scrapy
      
      class MovieproItem(scrapy.Item):
          name = scrapy.Field()
          desc = scrapy.Field()
      

      pipelines.py

      class MovieproPipeline(object):
          def process_item(self, item, spider):
              conn = spider.conn
              conn.lpush('movieData',item)
              return item
      
    • 增量式总结:

      对于深度爬取,可以在爬取之前对比记录表中是否已经有该url;

      对于非深度爬取,只能是在爬下来之后,加过加密校验后,判断校验值是否相同,进而确定是否保存到数据库/记录表

  • 相关阅读:
    leetcode-26-删除排序数组中的重复项
    用设计模式优化if else
    Builder设计模式
    退役划水(6)
    linux下安装nginx
    .NET 云原生架构师训练营(模块二 基础巩固 引入)--学习笔记
    SpringBoot项目的jar包瘦身计划
    如何解决高并发秒杀的超卖问题
    idea 最新jar包
    ArcGIS10.2企业数据库安装与配置
  • 原文地址:https://www.cnblogs.com/liubing8/p/12020237.html
Copyright © 2020-2023  润新知