7. Scrapy的高级用法

Scrapy的高级用法

一、Scrapy的五大核心组件

image-20210625200632450

1. Scrapy Engine(引擎)

  • 用来处理整个系统的数据流,触发事务(框架核心)

2. Schedule(调度器)

  • 用来接收引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回,可以想像成一个url(抓取网页的网址或者说是链接)的优先队列,由他来决定下一个要抓取的网址是什么,同时去除重复的网址。

3. Download(下载器)

  • 用于下载网页内容,并将网页内容返回给蜘蛛(Spider下载器是建立在twisted这个高效的一部框架上的)

4. Spiders(爬虫文件)

  • 爬虫主要是干活的,用于从特定的网页中提取自己需要的信息。即所谓的实体(Item),用户也可以从中提取出链接,让Spider继续抓取下一个页面

5. Pipeline(管道)

  • 负责处理爬虫从网页中抽取的实体,主要的功能时持久化存储实体,验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。

二、请求传参实现网站深度爬取

1. 什么是深度爬取?

  • 爬取的数据没有在同一个页面中(首页数据+详情页数据)

  • 如果scrapy中没有请求传参,我们是无法实现深度爬取功能

2. 实现方式

  • scrapy.Request(url,callback,meta={})
    • meta是一个字典,可以通过meta将参数传给callback回调函数
  • callback取出meta的值
    • response.meta

3. 实际案例

  • 创建爬虫项目
1. scrapy startproject moviePro
2. cd moviePro
3. scrapy genspider movie www.xxx.com
  • settings.py

    BOT_NAME = 'moviePro'
    
    SPIDER_MODULES = ['moviePro.spiders']
    NEWSPIDER_MODULE = 'moviePro.spiders'
    
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36'
    
    ROBOTSTXT_OBEY = False
    LOG_LEVEL = 'ERROR'
    
    ITEM_PIPELINES = {
       'moviePro.pipelines.MovieproPipeline': 300,
    }
    ######################  总结  #######################
    这里需要按照配置UA、robots协议、日志等级、开启管道
    
  • movie.py

    import scrapy
    from moviePro.items import MovieproItem
    
    class MovieSpider(scrapy.Spider):
        name = 'movie'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['https://www.4567kan.com/index.php/vod/show/id/5.html']
        # url模板
        url = 'https://www.4567kan.com/index.php/vod/show/id/5/page/%d.html'
        page_num = 2
        def parse(self, response):
            li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li')
            for li in li_list:
                title = li.xpath('./div/a/@title').extract_first()
                detail_url = 'https://www.4567kan.com' + li.xpath('./div/a/@href').extract_first()
    
                item = MovieproItem()
                item['title'] = title
    
                # 对详情页发起请求
                # meta作用:可以将meta字典传递给callback
                yield scrapy.Request(url=detail_url,callback=self.parse_detail,meta={'meta':item})
            if self.page_num < 6:
                new_url = format(self.url%self.page_num)
                self.page_num += 1
                yield scrapy.Request(url=new_url,callback=self.parse)
        # 被用作于解析详情页的数据
        def parse_detail(self,response):
            # 接收传递过来的meta
            item = response.meta['meta']
            desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first()
            item['desc'] = desc
            yield item
    ######################  总结  #######################
    1. 请求传参的重要性就体现在将item对象传给下一个解析函数
    2. 掌握好怎么传递参数和获取参数
    
  • items.py

    import scrapy
    
    class MovieproItem(scrapy.Item):
        # define the fields for your item here like:
        title = scrapy.Field()
        desc = scrapy.Field()
    ######################  总结  #######################
    1. 爬虫文件中解析出几个字段,这里就写几个字段
    2. 这里的item本质就是一个字典
    
  • pipelines.py

    from itemadapter import ItemAdapter
    
    class MovieproPipeline:
        def process_item(self, item, spider):
            print(item)
            return item
    ######################  总结  #######################
    这里只是简单的打印了item中的数据,不做任何持久化存储
    

三、详细讲解中间件(middleware)

图片看上文

1. 基本情况

  • 作用:批量拦截请求和响应
  • 分类
    • 爬虫中间件(了解)
    • 下载中间件
  • 作用体现
    • 拦截响应
      • 篡改请求url
      • 伪装请求头信息(UA、Cookie)
      • 设置请求代理【重点】
    • 拦截请求
      • 篡改响应数据

2. 中间件中的具体方法【下载中间件】

  • process_request方法:拦截请求

    class MiddleproDownloaderMiddleware:
        # 拦截所有(正常&异常)的请求
        # 参数:request就是拦截到的请求,spider就是爬虫类实例化的对象
        def process_request(self, request, spider):
            print('process_request()')
            request.headers['User-Agent'] = 'xxx'
            request.headers['Cookie'] = 'xxxxx'
            return None # or request
        .....
    
  • process_response方法:拦截响应

    class MiddleproDownloaderMiddleware:
    	.....
        # 拦截所有的响应对象
        # 参数:response拦截到的响应对象,request响应对象对应的请求参数
        def process_response(self, request, response, spider):
            print('process_response()')
            return response
        ......
    
  • process_exception方法:拦截异常的请求

    class MiddleproDownloaderMiddleware:
    	......
        # 拦截异常的请求
        # 参数:request就是拦截到的发生异常的请求
        # 作用:想要将异常的请求进行修正,将其变成正常的请求,然后对其进行重新发送
        def process_exception(self, request, exception, spider):
            # 请求的ip被禁掉,该请求就会变成一个异常请求
            request.meta['proxy'] = 'http://ip:port' # 设置代理
            print('process_exception()')
            return request # 将异常的请求修正后再将其进行重新发送,如果异常没有修正,该方法就会一直执行,陷入死循环
        ......
    

3. 在配置文件中开启下载中间件

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

四、大文件下载

大文件【图片、视频、大文本】数据实在管道中请求得到并进行持久化存储

1.步骤

  • 在爬虫文件中将所需的字段信息解析出来

  • 在items.py中定义字段

  • 将爬虫文件中解析出的字段信息封装到items对象中

  • 在pipelines.py中获取到爬虫文件传过来的item对象中的属性

  • 定义一个大文件存储类,进行持久化存储

2. 大文件下载类

  • ImagePipeline类:下载图片

    import scrapy
    from scrapy.pipelines.images import ImagesPipeline
    
    class ImgPipeline(ImagesPipeline):
    
        # 根据图片地址发起请求
        def get_media_requests(self, item, info):
            pass
    
        # 获取图片名称即可
        def file_path(self, request, response=None, info=None, *, item=None):
            pass
    
        # 将item传递给下一个即将被执行的管道类
        def item_completed(self, results, item, info):
            pass
    
  • MediaPipeline类:下载媒体文件

    import scrapy
    from scrapy.pipelines.media import MediaPipeline 
    
    class mediaPipeline(MediaPipeline):
        
        def get_media_requests(self, item, info):
            pass
    
        def media_downloaded(self, response, request, info, *, item=None):
            pass
    
        def item_completed(self, results, item, info):
            pass
    
  • FilePipeline类:下载文本

    import scrapy
    from scrapy.pipelines.files import FilesPipeline 
    
    class filePipeline(FilesPipeline):
        def get_media_requests(self, item, info):
            pass
        def file_path(self, request, response=None, info=None, *, item=None):
            pass
        def item_completed(self, results, item, info):
            pass
    

3. 具体案例【爬取必应壁纸】

  • 创建爬虫项目

    1. scrapy startproject imgPro
    2. cd imgPro
    3, scrapy genspider img
    
  • settings.py

    BOT_NAME = 'imgPro'
    
    SPIDER_MODULES = ['imgPro.spiders']
    NEWSPIDER_MODULE = 'imgPro.spiders'
    
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36'
    
    ROBOTSTXT_OBEY = False
    LOG_LEVEL = 'ERROR'
    
    ITEM_PIPELINES = {
       'imgPro.pipelines.ImgproPipeline': 300,
       'imgPro.pipelines.ImgPipeline': 301,
    }
    IMAGES_STORE = 'sceneryLib'
    ######################  总结  #######################
    因为使用的管道下载大文件,所以在此处要配置好大文件在本地的存储位置
    
  • img.py

    import scrapy
    from imgPro.items import ImgproItem
    
    class ImgSpider(scrapy.Spider):
        name = 'img'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['https://bing.ioliu.cn/']
    
        def parse(self, response):
            div_list = response.xpath('/html/body/div[3]/div')
            for div in div_list:
                img_src = div.xpath('.//img/@src').extract_first()
                title = div.xpath('.//h3/text()').extract_first().split('(')[0] + '.png'
                # print(title,img_src)
                item = ImgproItem()
                item['title'] = title
                item['img_src'] = img_src
    
                yield item
    ######################  总结  #######################
    1. 正常操作,只需将图片的地址和图片的名字传给item对象
    
  • items.py

    import scrapy
    
    class ImgproItem(scrapy.Item):
        # define the fields for your item here like:
        title = scrapy.Field()
        img_src = scrapy.Field()
    ######################  总结  #######################
    正常操作,爬虫文件中解析出多少字段就创建多个字段
    
  • pipelines.py

    from itemadapter import ItemAdapter
    
    # 该管道默认无法帮助我们请求图片数据,因此我们就不用
    class ImgproPipeline:
        def process_item(self, item, spider):
            return item
    
    # 管道需要接受item中的图片地址和名称,然后在管道中请求到图片的数据对其进行持久化存储
    import scrapy
    from scrapy.pipelines.images import ImagesPipeline # 提供了数据下载功能
    
    class ImgPipeline(ImagesPipeline):
    
        # 根据图片地址发起请求
        def get_media_requests(self, item, info):
            yield scrapy.Request(url=item['img_src'],meta={'item':item})
    
        # 获取图片名称即可
        def file_path(self, request, response=None, info=None, *, item=None):
            # 通过request获取meta中的item
            item = request.meta['item']
            filePath = item['title']
            return filePath # 只需要返回图片名称
    
        # 将item传递给下一个即将被执行的管道类
        def item_completed(self, results, item, info):
            return item
    ######################  总结  #######################
    1. get_media_requests()方法向图片地址发送请求
    2. file_path()方法将图片名字返回
    3. item_completed()方法将item对象传递给下一个管道类
    

4. 配置文件中的常见配置

  • 增加并发

    • 默认scrapy开启的并发线程为16个,可以适当增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100。值为100,并发设置成100。
  • 降低日志级别

    • 在运行scrapy时,会有大量的日志信息输出,为了减少CPU的使用率,可以设置log输出信息为INFO或ERROR,在配置文件中:LOG_LEVEL = "ERROR".
  • 禁止cookie

    • 如果不是整的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中:COOKIES_ENABLED = False
  • 禁止重试

    • 对请求失败的http请求进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。RETRY_ENABLED = False
  • 减少下载超时

    • 如果对一个非常慢的链接进行爬取,减少下载超时可以让卡住的链接快速放弃,从而提升效率。在配置文件中:DOWNLOAS_TIMEOUT = 10 超时时间为10秒

五、基于CrawlSpider实现全站数据爬取

1. 基本概念

  • CrawlSpider是Spider的一个子类。不过他的概念多于Spider父类
  • 作用:被作用于专业实现全站数据爬取,就是将一个页面下所有页码对应数据进行爬取。

2. 基本使用

  • 创建一个爬虫工程

    scrapy startproject crawlPro
    
  • 进入到爬虫工程

    cd crawlPro
    
  • 创建一个基于CrawlSpider的爬虫文件

    scrapy genspider -t crawl crawl www.xxx.com
    
  • 执行工程

    # 第一个crawl是执行爬虫文件的命令# 第二个crawl是爬虫文件的名字scrapy crawl crawl
    

3. 案例:爬取糗事百科

  • 创建爬虫工程

    scrapy startproject crawlPro
    cd scawlPro
    scrapy genspider -t crawl first www.xxx.com
    
  • settings.py

    BOT_NAME = 'crawlPro'
    
    SPIDER_MODULES = ['crawlPro.spiders']
    NEWSPIDER_MODULE = 'crawlPro.spiders'
    
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36'
    
    ROBOTSTXT_OBEY = False
    LOG_LEVEL = 'ERROR'
    
  • first.py

    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    
    
    class FirstSpider(CrawlSpider):
        name = 'first'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['https://www.qiushibaike.com/text/']
    
        # 实例化一个LinkExtractor对象
        # 链接提取器:根据指定规则(allow参数)在页面中进行链接(url)的提取
        # allow='正则表达式':提取链接的规则
    
        link = LinkExtractor(allow=r'/text/page/\d+/')
        rules = (
            # 实例化一个Rule对象
            # 规则提取器:接收链接提取器提取到的链接,对其发起请求(get),然后根据指定规则(callback)解析数据
            Rule(link, callback='parse_item', follow=True),
        )
        # follow=True:
        # 将链接提取器 继续作用到 链接提取器提取到的页码 所对应的 页面中
        def parse_item(self, response):
            print(response)
            # 基于response实现数据解析
    

4. CrawlSpider和Spider的区别

  • Rule规则匹配器

    Rule(LinkExtractor(allow=r'/text/page/\d+/'), callback='parse_item', follow=True)
    
    • LinkExtractor:链接提取器
    • callback:回调函数,用于数据解析
    • follow:是否将本页提取到的链接作为起始链接
  • LinkExtractor链接提取器

    LinkExtractor(allow=r'/text/page/\d+/')
    
    • allow:待匹配链接的正则表达式
  • CrawlSpider需要和请求传参一起使用,不然在item持久化时会出现问题

六、分布式爬虫

1. 基本概念

  • 什么是分布式
    • 分布式就是一个机群,让机群中的每台电脑执行同一组程序,且对同一组资源进行联合分布数据爬取
  • 实现方式
    • scrapy + redis(scrapy结合scrapy-redis组件)
  • 为什么原生scrapy不能实现分布式?
    • 调度器无法被分布式机群共享
    • 管道无法被分布式机群共享
  • scrapy-redis组件的作用
    • pip install scrapy-redis
    • 给原生的scrapy框架提供共享的管道和调度器

2. 实现流程

  • 修改爬虫文件

    • 导包:from scrapy_redis.spiders import RedisCrawlSpider
    • 修改当前爬虫类的父类替换成RedisCrawlSpider
    • 将start_urls 替换成redis_key的属性,属性值为任意字符串
      • redis_key = 'xxx':表示的是可以被共享的调度器队列的名称,最终是需要将其实的url手动放置到redis_key表示的队列中
    • 将数据解析的代码补充完整
  • 对settings.py配置文件进行配置

    • 指定调度器

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

      ITEM_PIPELINES = {
          'scrapy_redis.pipelines.RedisPipeline': 400
      }
      # 特点:该种管道只可以将item写入redis
      
    • 指定redis

      REDIS_HOST = '127.0.0.1'
      REDIS_PORT = 6379
      # 下面两个配置可选
      REDIS_ENCODING = 'utf-8'
      REDIS_PARAMS = {'username':'root','password':'12345'}
      
  • 配置本地redis的配置文件(redis.window.conf)

    • 解除默认绑定(解除后,说明此redis可以被远端连接)
      • 56行:bind 127.0.0.1 改成 # bind 127.0.0.1
    • 关闭保护模式(关闭后,说明远端连接可以对此redis进行读写)
      • 75行:protected-mode yes 改成 protected-mode no
  • 启动redis服务(客户端+服务端)

  • 执行scrapy工程(不要在爬虫配置文件中添加:LOG_LEVEL)

    • 程序会停留在listening位置:等待起始的url的加入
  • 向redis_key表示的队列中添加起始指令

    • 需要在redis的客户端执行如下指令(调度器队列是存在于redis中)

      lpush `redis_key` 起始url
      

3. 案例:爬取阳光政务

  • 创建一个爬虫工程

    scrapy startproject fbsPro
    cd fbsPro
    scrapy genspider -t crawl fbs www.xxx.com
    
  • settings.py

    BOT_NAME = 'fbsPro'
    
    SPIDER_MODULES = ['fbsPro.spiders']
    NEWSPIDER_MODULE = 'fbsPro.spiders'
    
    
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36'
    
    ROBOTSTXT_OBEY = False
    
    
    # 增加了一个去重容器类的配置,作用使用Redis的集合来存储请求的指纹数据,从而实现请求去重
    DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
    # 使用scrapy-redis组件自己的调度器
    SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
    # 配置调度器是否要持久化,也就是当爬虫结束了,要不要清空Redis中请求队列和去重指纹的set。如果是True,就表示要持久化存储,不清空数据,否则清空
    SCHEDULER_PERSIST =True
    
    # 指定管道
    ITEM_PIPELINES = {
       'scrapy_redis.pipelines.RedisPipeline': 400
    }
    
    # 指定redis
    REDIS_HOST = '127.0.0.1'
    REDIS_PORT = 6379
    # REDIS_ENCODING = 'utf-8'
    # REDIS_PARAMS = {'username':'root','password':'12345'}
    
  • fbs.py

    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 = ['https://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']
    
        redis_key = 'sunQueue' # 可以被共享调度器队列的名称
        # 稍后我们是需要将一个起始的url手动添加到redis_key表示的队列中
    
        rules = (
            Rule(LinkExtractor(allow=r'id=1&page=\d+'), callback='parse_item', follow=False),
        )
    
        def parse_item(self, response):
            # 将全站的标题获取
            li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
            for li in li_list:
                title = li.xpath('./span[3]/a/text()').extract_first()
                item = FbsproItem()
                item['title'] = title
                yield item
    
  • items.py

    import scrapy
    
    
    class FbsproItem(scrapy.Item):
        title = scrapy.Field()
    
  • 效果图

    image-20210625200656738

image-20210625200707123

image-20210625200716012

image-20210625200726687

七、增量式爬虫

1. 基本概念

  • 增量式爬虫就是检测网站数据更新的情况,以便于爬取到最新更新出来的数据
  • 实战的核心:去重
  • 实战中去重的方式(记录表)
    • 记录标需要记录什么?记录的一定是爬取过的相关信息。
      • 爬取过的相关信息:每部电影详情页的url
      • 只需要使用某一组数据,该组数据如果可以作为该部电影的唯一标识,刚好电影详情页的url就可以作为电影的唯一标识。只要可以表示电影唯一标识的数据,我们统称为数据指纹。
  • 去重方式对象的记录表(符合能去重 + 能持久化存储)
    • python中的set集合(不可行)
      • set集合无法持久化存储
    • redis中的sete(可行)
      • 可以持久化存储
  • 数据指纹一般是加过密的
    • 什么情况需要对数据指纹加密
      • 如果数据的唯一标识内容数据量比较大,可以使用hash加那个数据加密成32位的密文。目的是为了节省空间

2. 案例:爬取K8经典电影

  • 创建爬虫工程

    scrapy startproject zlsPro
    cd zlsPro
    scrapy genspider -t crawl zls www.xxx.com
    
  • settings.py

    BOT_NAME = 'zlsPro'
    
    SPIDER_MODULES = ['zlsPro.spiders']
    NEWSPIDER_MODULE = 'zlsPro.spiders'
    
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36'
    
    ROBOTSTXT_OBEY = False
    
    LOG_LEVEL = 'ERROR'
    
    ITEM_PIPELINES = {
       'zlsPro.pipelines.ZlsproPipeline': 300,
    }
    
  • zls.py

    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    from ..items import ZlsproItem
    from redis import Redis
    
    class ZlsSpider(CrawlSpider):
        name = 'zls'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['https://www.k8jds.com/index.php/vod/show/class/%E5%8A%A8%E4%BD%9C/id/5.html']
    
        conn = Redis(host='127.0.0.1',port=6379)
    
        rules = (
            Rule(LinkExtractor(allow=r'page/\d+\.html'), callback='parse_item', follow=False),
        )
    
        def parse_item(self, response):
            # 解析电影的名称 + 电影详情页的url
            li_list = response.xpath('/html/body/div[2]/div/div[2]/div/div[2]/ul/li')
            for li in li_list:
                title = li.xpath('./div/a/@title').extract_first()
                detail_url = "https://www.k8jds.com" + li.xpath('./div/a/@href').extract_first()
    
                ex = self.conn.sadd('movie_url',detail_url)
    
                if ex == 1: # 表示该电影的url并没有爬取过
                    item = ZlsproItem()
                    item['title'] = title.strip()
                    print("正在更新数据.......")
                    yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})
                else:       # 表示该电影的url已经爬取过
                    print('暂无新数据更新!')
    
    
    
        def parse_detail(self,response):
            desc = response.xpath('/html/body/div[2]/div/div[1]/div[4]/div/div[2]/div/span[2]/text()').extract_first()
            item = response.meta['item']
            item['desc'] = desc.strip()
            yield item
    
  • items.py

    import scrapy
    
    class ZlsproItem(scrapy.Item):
        title = scrapy.Field()
        desc = scrapy.Field()
    
    
  • pipelines.py

    from itemadapter import ItemAdapter
    
    
    class ZlsproPipeline:
        def process_item(self, item, spider):
            # print(item)
            conn = spider.conn
            conn.lpush('movie_data',item)
            return item
    

八、Scrapy + Selenium实现爬虫

1. 基本概念

Selenium的具体操作看爬虫相关第5篇文章

2. 案例:爬取网易新闻

1. 目标
	- https://news.163.com/
    - 爬取网易新闻中的国内,国际,军事,航空,无人机这五个板块下面所有的新闻数据(标题+内容)
    
2. 分析
    - 首页没有动态加载的数据
        - 爬取五个板块对应的url
    - 每个板块对应的页面中的新闻数据是动态加载的
        - 爬取新闻标题 + 详情页的url
    - 每一条新闻详情页中的数据不是动态加载的
        - 爬取新闻的内容
        
3. selenium使用流程
	- 在爬虫类中实例化一个浏览器对象,将其作为爬虫类的一个属性
	- 在中间间中实现浏览器自动化相关的操作
	- 在爬虫类中重写closed(self,spider)方法,在其内部关闭浏览器对象
  • 创建爬虫工程

    scrapy startproject wangyiPro
    cd wangyiPro
    scrapy genspider wangyi www.xxx.com
    
  • settings.py

    BOT_NAME = 'wangyiPro'
    
    SPIDER_MODULES = ['wangyiPro.spiders']
    NEWSPIDER_MODULE = 'wangyiPro.spiders'
    
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36'
    
    ROBOTSTXT_OBEY = False
    LOG_LEVEL = 'ERROR'
    
    DOWNLOADER_MIDDLEWARES = {
       'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,
    }
    
    ITEM_PIPELINES = {
       'wangyiPro.pipelines.WangyiproPipeline': 300,
    }
    
  • wangyi.py

    import scrapy
    from selenium import webdriver
    from wangyiPro.items import WangyiproItem
    
    class WangyiSpider(scrapy.Spider):
        name = 'wangyi'
        # allowed_domains = ['https://news.163.com/']
        start_urls = ['https://news.163.com/']
    
        model_urls = []  # 每一个模块对应的url
    
        browser = webdriver.Chrome('C:\Program Files (x86)\Google\chromedriver')
    
        # 数据解析:每一个板块对应的url
        def parse(self, response):
            li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li')
            indexs = [2,3,5,6]
            for index in indexs:
                model_li = li_list[index]
                model_url = model_li.xpath('./a/@href').extract_first()
                self.model_urls.append(model_url)
    
            # 对每一个板块的url发起请求
            for url in self.model_urls:
                yield scrapy.Request(url=url,callback=self.parse_model)
    
        # 解析每个板块下面的新闻标题和新闻详情页的url
        def parse_model(self,response):
            """
            直接对response解析新闻标题数据和新闻详情页数据是无法获取该数据的(这是动态加载出来的数据)
            该response是不符合要求的response,需要将其便车给符合邀要求的response
            满足需求的response就是包含动态加载数据的response
            满足要求的response和不满足要求的response区别在哪?
                区别在于响应数据不同。我们可以使用中间将不满足需求的响应对象中的响应数据篡改成包含了动态加载数据的响应数据,将其编程满足要求的响应对象
            """
            div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div[1]/div/ul/li/div/div')
            for div in div_list:
                title = div.xpath('./div/div[1]/h3/a/text()').extract_first()
                new_detail_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()
                if new_detail_url:
                    item = WangyiproItem()
                    item['title'] = title.strip()
                    yield scrapy.Request(url=new_detail_url,callback=self.parse_new_detail,meta={'item':item})
    
        # 解析新闻详情页的内容
        def parse_new_detail(self,response):
            item = response.meta['item']
            content = response.xpath('//*[@id="content"]/div[2]/p/text()').extract()
            item['content'] = content
            yield item
    
        def closed(self,spider):
            self.browser.quit()
    
  • items.py

    import scrapy
    
    
    class WangyiproItem(scrapy.Item):
        
        title = scrapy.Field()
        content = scrapy.Field()
    
  • middlewares.py

    from scrapy import signals
    
    from scrapy.http import HtmlResponse
    from time import sleep
    
    
    class WangyiproDownloaderMiddleware:
    
        def process_request(self, request, spider):
    
            return None
    
    
        # 拦截所有的相应对象
        # 整个工程发起的请求 :1 + 4 + n,相应的也会有1 + 4 + n个响应对象
        # 只有指定的5个响应对象是不满足需求的
        # 只需要讲不满足需求的4个指定的响应对象的响应数据进行篡改即可
        def process_response(self, request, response, spider):
            # 将拦截到的所有响应对象中指定的4个响应对象找出
            if request.url in spider.model_urls:
                browser = spider.browser
                # response表示的就是指定的不满足需求的4个响应对象
                # 篡改响应数据:首先要获取满足需求的响应数据,再将其篡改到响应对象中即可
                # 满足需求的响应数据就可以使用selenium获取
                browser.get(request.url) # 使用selenium对4个板块的url发起请求
                sleep(2)
                browser.execute_script('window.scrollTo(0,document.body.scrollHeight)') # 页面滑倒底部
                sleep(1)
                # 捕获到了板块页面中加载出来的全部数据(包含动态加载的数据)
                page_text = browser.page_source
                # response.text = page_text
                # 返回了一个新的响应对象,新的响应对象替换原来不满足需求的响应对象
                return HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
            else:
                return response
    
        def process_exception(self, request, exception, spider):
    
            pass
    
        def spider_opened(self, spider):
            spider.logger.info('Spider opened: %s' % spider.name)
    
    
  • pipelines.py

    class WangyiproPipeline:
        def process_item(self, item, spider):
            print(item)
            return item
    
posted @ 2021-06-04 12:24  今天捡到一百块钱  阅读(434)  评论(0编辑  收藏  举报