爬虫入门_8:scrapy框架

scrapy简介

  • 框架:是一个集成了很多功能并且具有很强通用性的一个项目模板

  • 如何学习框架?

    • 专门学习框架封装的各种功能的详细用法
  • scrapy:爬虫中封装好的 一个明星框架。功能:高性能的持久化存储,异步的数据下载,高性能的数据解析,分布式

scrapy框架的基本使用

  • 环境的安装:

    • mac or linux : pip install scrapy

    • windows:

      • 安装wheel:pip install wheel

      • 下载twisted

      下载地址:https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

      • 安装twisted: pip install Twisted-20.3.0-cp37-cp37m-win_amd64.whl

      • 安装pywin32:pip install pywin32

      • 安装scrapy:pip install scrapy
        测试:在终端里输入import scrapy,没有报错即表示安装成功!

  • scrapy使用流程

    • 创建一个工程:在终端中定位到指定文件夹里,然后输入 scrapy startproject xxxPro,即可创建一个工程
    • 进入到工程目录中:cd xxxPro
    • 在spiders子目录中创建一个爬虫文件
      • scrapy genspider spiderName www.xxx.com
    • 执行工程:
      • scrapy crawl spiderName
        –nolog:只打印需要打印的信息,不打印日志;缺点是如果报错,不能显示错误信息

scrapy数据解析

需求:爬取6jianshi中作者的名称和段子内容

  • 创建工程:scrapy startproject sixjianshiPro

  • 进入到工程目录中:cd sixjianshiPro

  • 在spiders子目录中创建一个爬虫文件:scrapy genspider sixjianshi www.xxx.com

    • 在sixjianshi.py中编写代码

      import scrapy
      
      class SixjianshiSpider(scrapy.Spider):
          name = 'sixjianshi'
          # allowed_domains = ['www.xxx.com']
          start_urls = ['https://www.6jianshi.com/']
      
          def parse(self, response):
              # 解析:作者的名称+段子内容
              # 注意:xpath中不能出现body
              div_list = response.xpath('/html//div[3]/div/div/div[1]/div[2]/div[2]/div[@class="art-list"]')
              for div in div_list:
                  # xpath返回的是列表,但是列表元素一定是Selector类型的对象
                  # extract():可以将Selector对象中的data参数存储的字符串提取出来
                  author = div.xpath('./div[@class="art-list-user"]/div[2]/a[1]/text()').extract_first()
                  if author == None:
                      continue
                  # 列表调用了extract(),则表示将列表中每一个Selector对象中data对应的字符串提取了出来
                  content = div.xpath('./div[@class="art-list-content"]/a//text()').extract()
                  content = ''.join(content)
                  print(author,":", content)
      
    • 在settings.py中修改以下内容:

      # UA伪装
      USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'
      
      # Obey robots.txt rules
      # True:遵从robots协议
      ROBOTSTXT_OBEY = False
      
      # 显示指定的类型的日志信息
      LOG_LEVEL = 'ERROR'
      
      # 设置编码格式
      FEED_EXPORT_ENCODING = 'utf-8-sig'
      
    • 输入:scrapy crawl sixjianshi,即可

      运行结果如下:

    爬虫入门_8:scrapy框架_第1张图片

scrapy的数据持久化存储

  • 基于终端指令:

    • 指令:scrapy crawl xxx -o filePath

    • 要求:只可以将parse方法的返回值存储到本地的文本文件中

    • 注意:持久化存储对应的文本文件类型只可以为:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'

    • 好处:简洁高效便捷

    • 缺点:局限性比较强(数据只可以存储到指定后缀的文本文件中)

    • 案例

      • 修改sixjianshi.py中的parse()方法,将解析数据存储并返回
      def parse(self, response):
        # 解析:作者的名称+段子内容
        div_list = response.xpath('/html//div[3]/div/div/div[1]/div[2]/div[2]/div[@class="art-list"]')
        all_data = []  # 存储所有解析到的数据
        for div in div_list:
            # xpath返回的是列表,但是列表元素一定是Selector类型的对象
            # extract():可以将Selector对象中的data参数存储的字符串提取出来
            author = div.xpath('./div[@class="art-list-user"]/div[2]/a[1]/text()').extract_first()
            if author == None:
                continue
            # 列表调用了extract(),则表示将列表中每一个Selector对象中data对应的字符串提取了出来
            content = div.xpath('./div[@class="art-list-content"]/a//text()').extract()
            content = ''.join(content)
            # print(author,":", content)
            dic = {
                'author': author,
                'content': content
            }
            all_data.append(dic)
      
        return all_data
      
    • 然后在终端进入到工程目录中:cd sixjianshiPro

      • 输入:scrapy crawl sixjianshi -o ./sixjianshi.csv,即可对数据进行持久化存储,存储为csv,也可以存储为其他类型文件

        运行结果:
        爬虫入门_8:scrapy框架_第2张图片

  • 基于管道:

    • 编码流程:

      • 数据解析
      • 在item类中定义相关的属性
      • 将解析的数据封装存储到item类型的对象
      • 将item类型的对象提交给管道进行持久化存储的操作
      • 在管道类的process_item中要将其接收的item对象中存储的数据进行持久化存储操作
      • 在配置文件中开启管道
    • 好处:通用性强

    • 案例

      • 在items.py中创建两个属性

        import scrapy
        
        
        class SixjianshiproItem(scrapy.Item):
            # define the fields for your item here like:
            author = scrapy.Field()
            content = scrapy.Field()
        
      • 修改sixjianshi.py中的parse()方法,将解析数据存储并返回

        import scrapy
        from sixjianshiPro.items import SixjianshiproItem
        
        def parse(self, response):
        	# 解析:作者的名称+段子内容
        	div_list = response.xpath('/html//div[3]/div/div/div[1]/div[2]/div[2]/div[@class="art-list"]')
        
        	for div in div_list:
        		# xpath返回的是列表,但是列表元素一定是Selector类型的对象
        		# extract():可以将Selector对象中的data参数存储的字符串提取出来
        		author = div.xpath('./div[@class="art-list-user"]/div[2]/a[1]/text()').extract_first()
        		if author == None:
        			continue
        		# 列表调用了extract(),则表示将列表中每一个Selector对象中data对应的字符串提取了出来
        		content = div.xpath('./div[@class="art-list-content"]/a//text()').extract()
        		content = ''.join(content)
        		# print(author,":", content)
        
        		# 实例化item类型的对象,并将解析的数据封装到了item中
        		item = SixjianshiproItem()
        	item['author'] = author
        		item['content'] = content
        
        		# 将item提交给了管道
        		yield item
        
      • 在pipelines.py中修改SixjianshiproPipeline类

        class SixjianshiproPipeline:
            fp = None
        
            def open_spider(self, spider):
                """重写父类方法:该方法旨在开始爬虫的时候被调用一次"""
                print("开始爬虫......")
                self.fp = open('./sixjianshi.txt', 'w', encoding='utf-8')
        
            # 专门用来处理item类型对象
            # 该方法可以接收爬虫文件提交过来的item对象
            # 该方法每接收一个item就会被调用一次
            def process_item(self, item, spider):
                author = item['author']
                content = item['content']
        
                # 持久化存储
                self.fp.write(author + ':' + content + '\n')
                return item  # 就会传递给下一个即将被执行的管道类
        
          def close_spider(self, spider):
                """重写父类方法:该方法旨在结束爬虫的时候被调用一次"""
              print("结束爬虫!!!")
                self.fp.close()
        
        
      • 在settings.py中开启管道

        # 开启管道
        ITEM_PIPELINES = {
            # 300表示的是优先级,数据越小,优先级越高
           'sixjianshiPro.pipelines.SixjianshiproPipeline': 300,
        }
        
      • 在终端中输入:scrapy crawl sixjianshi,即可

        运行结果:

      爬虫入门_8:scrapy框架_第3张图片

  • 面试题:将爬取到的数据一份存储到本地一份存储到数据库,如何实现?

    • 管道文件中的一个管道类对应的是将数据存储到一种平台

    • 爬虫文件的item只会给管道文件中第一个被执行的管道类接收

    • process_item 中的return item 表示将item传递给下一个即将被执行的管道类

    • 代码实现

      1. 在win10 中开启mysql,我的用户名是root,没有设置密码

        • 打开cmd命令行进入数据库的bin文件夹下
        • 输入mysql -hlocalhost -uroot -p,出现输入密码 直接按回车即可
          爬虫入门_8:scrapy框架_第4张图片
      2. 在mysql中创建sixjianshi数据库和sixjianshi

      -- 新建数据库语句
      CREATE DATABASE sixjianshi;
      
      -- 使用数据库
      use sixjianshi;
      
      -- 创建数据表
      CREATE TABLE `sixjianshi` (
        `author` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT ''  COMMENT '作者',
        `content` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '内容'
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='sixjianshi';c
      
      -- 查看表数据
      select * from sixjianshi;
      
      1. 在pipelines.py中增加mysqlPipeline类
      import pymysql
      # 管道文件中一个管道类对应将一组数据存储到一个平台或者载体中
      class mysqlPipeline:
        conn = None
          cursor = None
      
          def open_spider(self, spider):
              self.conn = pymysql.Connect(host='127.0.0.1',
                                          port=3306,
                                          user='root',
                                          password='123456',
                                          dt='sixjianshi',
                                          charset='utf8')
      
        def process_item(self, item, spider):
              # 持久化存储到数据库
              self.cursor = self.conn.cursor()
              try:
                  self.cursor.execute('insert into sixjianshi values("%s","%s")' % (item['author'], item['content']))
                  self.conn.commit()  # 数据提交
              except Exception as e:
                  print(e)
                  self.conn.rollback()  # 数据回滚
      
              return item
      
          def close_spider(self, spider):
              self.cursor.close()
              self.conn.close()
      
      1. 在settings.py中开启管道
      # 开启管道
      ITEM_PIPELINES = {
          # 300表示的是优先级,数据越小,优先级越高
         'sixjianshiPro.pipelines.SixjianshiproPipeline': 300,
         'sixjianshiPro.pipelines.mysqlPipeline': 301
      }
      
      1. 在终端中输入:scrapy crawl sixjianshi,即可

      2. 在mysql中查看结果,发现表中已经有数据了

      爬虫入门_8:scrapy框架_第5张图片

实战:基于spider类的全站数据爬取

  • 大部分的网站展示的数据都进行了分页操作,那么将所有页码对应的页面数据进行爬取就是爬虫中的全站数据爬取。

  • 基于scrapy进行全站数据爬取的实现方式:

    • 将所有页面的url添加到start_urls列表(不推荐)
    • 使用Request方法手动发起请求(推荐)
      • yield scrapy.Request(url,callback):callback专门用作于数据解析
  • 需求:爬取站长素材网中的风景板块中的照片名称

  • 代码实现

    • 创建工程:scrapy startproject sc_chinazPro

    • 进入到工程目录中:cd sc_chinazPro

    • 在spiders子目录中创建一个爬虫文件:scrapy genspider sc_chinaz www.xxx.com

    • 在sc_chinaz.py中编写代码

      import scrapy
      
      class ScChinazSpider(scrapy.Spider):
          name = 'sc_chinaz'
          # allowed_domains = ['www.xxx.com']
          start_urls = ['https://sc.chinaz.com/tupian/fengjing.html']
      
          # 生成一个通用的url模板(不可变)
          url = 'https://sc.chinaz.com/tupian/fengjing_%d.html'
          page_num = 2
      
          def parse(self, response):
              div_list = response.xpath('//*[@id="container"]/div')
              for div in div_list:
                  img_name = div.xpath('./p/a[1]/text()').extract_first()
                  print(img_name)
      
            if self.page_num <= 4:  # 爬取前4页数据
                  new_url = format(self.url%self.page_num)
                  print(new_url)
                  self.page_num += 1
                  # 手动请求发送:callback回调函数是专门用作于数据解析
                  yield scrapy.Request(url=new_url,callback=self.parse)
      
    • 在终端中输入:scrapy crawl sc_chinaz,即可

      运行结果如下:

      爬虫入门_8:scrapy框架_第6张图片

五大核心组件

scrapy的五大核心组件的工作流程如下:

爬虫入门_8:scrapy框架_第7张图片

  • 引擎(scrapy)
    • 用来处理整个系统的数据流处理,触发事务(框架核心)
  • 调度器(Scheduler)
    • 用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回。可以想象成一个URL(抓取网页的网址或者说是链接)的优先队列,由它来决定下一个要抓取的网址是什么,同时去除重复网址
  • 下载器(Downloader)
    • 用于下载网页内容,并将网页内容返回给spiders(Scrapy下载器是建立在twisted这个高效的异步模型上的)
  • 爬虫(Spiders)
    • 爬虫是主要干活的,用于从特定的网页中提取自己需要的信息,即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
  • 项目管道(Pipeline)
    • 负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据

请求传参

  • 使用场景:如果爬取解析的数据不在同一张页面中。(深度爬取)

  • 需求:爬取好猎头网站的岗位名称,岗位描述

  • 代码实现

    • 创建工程:scrapy startproject haolietouPro

    • 进入到工程目录中:cd haolietouPro

    • 在spiders子目录中创建一个爬虫文件:scrapy genspider haolietou www.xxx.com

    • 在items.py中创建两个属性

      import scrapy
      
      
      class HaolietouproItem(scrapy.Item):
          # define the fields for your item here like:
          # name = scrapy.Field()
          job_name = scrapy.Field()
          job_desc = scrapy.Field()
      
    • 在settings.py中修改以下内容:

      # UA伪装
      USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'
      
      # Obey robots.txt rules
      # True:遵从robots协议
      ROBOTSTXT_OBEY = False
      
      # 显示指定的类型的日志信息
      LOG_LEVEL = 'ERROR'
      
      # 开启管道
      ITEM_PIPELINES = {
         'haolietouPro.pipelines.HaolietouproPipeline': 300,
      }
      
    • 在haolietou.py中编写代码

      import scrapy
      from haolietouPro.items import HaolietouproItem
      
      
      class HaolietouSpider(scrapy.Spider):
          name = 'haolietou'
          # allowed_domains = ['www.xxx.com']
          start_urls = ['http://www.haolietou.com/jobslist?key=建筑师']
      
          # 通用的url
          url = 'http://www.haolietou.com/jobs/jobs-list.php?key=建筑师&page=%d'
          page_num = 2
      
          # 回调函数接收items
          def parse_detail(self, response):
              item = response.meta['item']
              job_desc = response.xpath('/html/body/div[2]/div[2]/div[1]/div/div[3]/div[2]/p//text()').extract()
              job_desc = ''.join(job_desc)
              item['job_desc'] = job_desc
              # print(job_desc)
      
              yield item
      
          def parse(self, response):
              li_list = response.xpath('//*[@id="form1"]/div/div[3]/div[2]/ul/li')
              print("li_list length is : ", len(li_list))
      
              for li in li_list:
                  item = HaolietouproItem()
                  job_name = li.xpath('./div[1]/p[1]/a/text()').extract_first()
                  item['job_name'] = job_name
                  # print(job_name)
                  detail_url = li.xpath('./div[1]/p[1]/a/@href').extract_first()
                  # print(detail_url)
                  # 对详情页发请求获取详情页的页面源码数据
                  # 手动请求的发送
                  # 请求传参:meta={},可以将meta字典传递给请求对应的回调函数
                  yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})
      
              # 分页操作
              if self.page_num <= 2:
                  print("****************{}************".format(self.page_num))
                  new_url = format(self.url%self.page_num)
                  print(new_url)
                  self.page_num += 1
                  yield scrapy.Request(url=new_url, callback=self.parse)
      
    • 在pipelines.py中编写代码

      class HaolietouproPipeline:
          def process_item(self, item, spider):
              print(item)
              return item
      
    • 在终端中输入:scrapy crawl haolietou,即可

图片数据爬取之ImagesPipeline

  • 基于scrapy爬取字符串类型的数据和爬取图片类型的数据区别?
    • 字符串:只需要基于xpath进行解析且提交管道进行持久化存储
    • 图片:xpath解析出图片src的属性值。单独的对图片地址发起请求获取图片二进制类型的数据
  • ImagesPipeline:
    • 只需要将img的src的属性值进行解析,提交到管道,管道就会对图片的src进行请求发送获取图片的二进制的数据,且还会帮我们进行持久化存储

案例

需求:爬取站长素材中的高清图片

  • 使用流程:

    • 数据解析(图片地址)
    • 将存储图片地址的item提交到指定的管道类
    • 在管道文件中自定义一个基于ImagesPipeLine的一个管道类
      • get_media_requests():请求发送
      • file_path():指定图片名称
      • item_completed():将item传递给下一个即将执行的管道类
    • 在配置文件中:
      • 指定图片存储的目录:IMAGES_STORE = ‘./imgs’
      • 指定开启的管道:自定义的管道类
  • 代码实现

    • 创建工程:scrapy startproject imgsPro

    • 进入到工程目录中:cd imgsPro

    • 在spiders子目录中创建一个爬虫文件:scrapy genspider img www.xxx.com

    • 在img.py中编写代码

      import scrapy
      from imgsPro.items import ImgsproItem
      
      class ImgSpider(scrapy.Spider):
          name = 'img'
          # allowed_domains = ['www.xxx.vom']
          start_urls = ['https://sc.chinaz.com/tupian/']
      
          def parse(self, response):
              div_list = response.xpath('//*[@id="container"]/div')
              for div in div_list:
                  # 注意:使用伪属性
                  src = 'https:'+div.xpath('./div/a/img/@src2').extract_first()
                  # print(src)
      
                  item = ImgsproItem()
                  item['src'] = src
      
                  yield item
      
    • 在items.py中创建两个属性

      import scrapy
      
      
      class ImgsproItem(scrapy.Item):
          # define the fields for your item here like:
          src = scrapy.Field()
      
    • 在pipelines.py中编写代码

      # ImagesPipeline专门用于文件下载的管道类,下载过程支持异步和多线程
      from scrapy.pipelines.images import ImagesPipeline
      import scrapy
      
      
      # 新建类
      class imgsPipeline(ImagesPipeline):
          # 重新父类的三个方法
      
          # 可以根据图片地址,进行图片数据的请求
          def get_media_requests(self, item, info):
              yield scrapy.Request(item['src'])
      
          # 指定图片存储的路径
          def file_path(self, request, response=None, info=None, *, item=None):
              imgName = request.url.split('/')[-1]  # 获取图片名称
              return imgName
      
          def item_completed(self, results, item, info):
              return item   # 返回给下一个即将执行的管道类
      
    • 在settings.py中修改以下内容:

      # UA伪装
      USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'
      
      # Obey robots.txt rules
      # True:遵从robots协议
      ROBOTSTXT_OBEY = False
      
      # 显示指定的类型的日志信息
      LOG_LEVEL = 'ERROR'
      
      # 开启管道
      ITEM_PIPELINES = {
         'imgsPro.pipelines.imgsPipeline': 300,
      }
      
      # 指定图片存储的目录,如果文件不存在,会自动创建
      IMAGES_STORE = './imgs__scChinaz'
      
    • 在终端中输入:scrapy crawl img,即可

scrapy中间件

下载中间件:

  • 位置:引擎和下载器之间
  • 作用:批量拦截到整个工程中所有的请求和响应
  • 拦截请求:
    • UA伪装:process_request
    • 代理IP: process_exception:return request
  • 拦截响应:
    • 篡改响应数据,响应对象

    • 需求:爬取网易新闻中的新闻数据(标题和内容)

      1. 通过网易新闻的首页解析出五大板块对应的详情页的url(没有动态加载)
      2. 每一个板块对应的新闻标题都是动态加载出来的(没有动态加载)
      3. 通过解析出每一条新闻详情页的url获取详情页的页面源码,解析出新闻内容
    • 代码实现

      • 创建工程:scrapy startproject wangyiPro

      • 进入到工程目录中:cd wangyiPro

      • 在spiders子目录中创建一个爬虫文件:scrapy genspider wangyi www.xxx.com

      • 在items.py中创建两个属性

        import scrapy
        
        
        class WangyiproItem(scrapy.Item):
            # define the fields for your item here like:
            title = scrapy.Field()
            content = scrapy.Field()
        
      • 在pipelines.py中编写代码

        class WangyiproPipeline:
            def process_item(self, item, spider):
                print(item)
                return item
        
      • 在middlewares.py中编写代码

        from scrapy.http import HtmlResponse
        from time import sleep
        
        class WangyiproDownloaderMiddleware:
            # Not all methods need to be defined. If a method is not defined,
            # scrapy acts as if the downloader middleware does not modify the
            # passed objects.
        
        
            def process_request(self, request, spider):
                # Called for each request that goes through the downloader
                # middleware.
        
                # Must either:
                # - return None: continue processing this request
                # - or return a Response object
                # - or return a Request object
                # - or raise IgnoreRequest: process_exception() methods of
                #   installed downloader middleware will be called
                return None
        
            # 该方法拦截四大板块对应的响应对象,进行篡改
            def process_response(self, request, response, spider):  # spider:爬虫对象
                bro = spider.bro  # 获取了在爬虫类中定义的浏览器对象
        
                # 挑选出指定的响应对象进行篡改
                # 通过url指定request
                # 通过request指定response
                if request.url in spider.models_urls:
                    bro.get(request.url)  # 获取四大板块对应的url进行请求
                    sleep(3)
                    page_text = bro.page_source   # 包含了动态加载的新闻数据
        
                    # response  # 四大板块对应的响应对象
                    # 针对定位的这些response进行篡改
                    # 实例化一个新的响应对象(符合需求:包含动态加载出的新闻数据),替代原来旧的响应对象
                    # 如何获取动态加载出的新闻数据
                          #  基于selenium便捷的获取动态加载数据
                    new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
        
                    return new_response
                else:
                    # response :其他请求对应的响应对象
                    return response
        
        
            def process_exception(self, request, exception, spider):
                # Called when a download handler or a process_request()
                # (from other downloader middleware) raises an exception.
        
                # Must either:
                # - return None: continue processing this exception
                # - return a Response object: stops process_exception() chain
                # - return a Request object: stops process_exception() chain
                pass
        
      • 在settings.py中修改以下内容:

        # UA伪装
        USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'
        
        # Obey robots.txt rules
        # True:遵从robots协议
        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 = ['www.xxx.com']
            start_urls = ['https://news.163.com/']
            models_urls = []  # 存储四个板块对应的详情页的url
        
            def __init__(self):
                self.bro = webdriver.Chrome('D:\PythonCode\待整理\爬虫相关\第8章:scrapy框架\chromedriver.exe')
        
            # 解析四大板块对应详情页的url
            def parse(self, response):
                li_list = response.xpath('//*[@id="index2016_wrap"]/div[3]/div[2]/div[2]/div[2]/div/ul/li')
                print("li list length is ",len(li_list))
                alist = [2,3,5,6]
                for index in alist:
                    model_url = li_list[index].xpath('./a/@href').extract_first()
                    self.models_urls.append(model_url)
        
                # 依次对每一个板块对应的页面进行请求
                for url in self.models_urls:  # 对每一个板块的url进行请求发送
                    yield scrapy.Request(url, callback=self.parse_model)
        
            # 每个板块对应的新闻标题相关的内容都是动态加载
            def parse_model(self, response):
                """解析每一个板块中对应新闻的标题和新闻详情页的url"""
                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()
        
                    item = WangyiproItem()
                    item['title'] = title
                    # 对新闻详情页的url发起请求
                    yield scrapy.Request(url=new_detail_url,callback=self.parse_detail,meta={'item':item})
        
            def parse_detail(self,response):  # 解析新闻内容
                content = response.xpath('//*[@id="content"]//text()').extract()
                content = ''.join(content)
                item = response.meta['item']
                item['content'] = content
        
                yield item
        
            def closed(self,spider):
                self.bro.quit()
        
      • 在终端中输入:scrapy crawl wangyi,即可

CrawlSpider:类,Spider的一个子类

  • 全站数据爬取的方式
    • 基于Spider:手动请求
    • 基于CrawlSpider
  • CrawlSpider的使用:
    • 创建一个工程
    • cd xxx
    • 创建爬虫文件(CrawlSpider):
      • scrapy genspider -t crawl xxx www.xxx.com
      • 链接提取器:
        • 作用:根据指定的规则(allow)进行指定链接的提取
      • 规则解析器:
        • 作用:将链接提取器提取到的链接进行指定规则(callback)的解析

案例

需求:爬取福州便民热线网站中的编号,新闻标题,及详情页的新闻内容和编号

  • 分析:爬取的数据没有在同一张页面中

    • 可以使用链接提取器提取所有页码链接
  • 让链接提取器提取所有的新闻详情页的链接

  • 代码实现

    • 创建工程:scrapy startproject fuzhouPro

    • 进入到工程目录中:cd fuzhouPro

    • 创建一个爬虫文件:scrapy genspider -t crawl fuzhou www.xxx.com

    • 在fuzhou.py中编写代码

      import scrapy
      from scrapy.linkextractors import LinkExtractor
      from scrapy.spiders import CrawlSpider, Rule
      from fuzhouPro.items import FuzhouproItem, DetailItem
      
      
      class FuzhouSpider(CrawlSpider):
          name = 'fuzhou'
          # allowed_domains = ['www.xxx.com']
          start_urls = ['http://fz12345.fuzhou.gov.cn/webEntAppealList.jsp?listType=1&pageSize=10&cp=1']
      
          # 链接提取器:根据指定规则(allow='正则')进行指定链接的提取
          link = LinkExtractor(allow=r'pageSize=10&cp=\d+')
          link_detail = LinkExtractor(allow=r'callId=\S\S\d+&from=webIndex')
          rules = (
              # 规则提取器:将链接提取器提取到链接进行指定规则(callback)的解析操作
              Rule(link, callback='parse_item', follow=True),
              # follow=True:可以将链接提取器 继续作用到 链接提取器提取到的链接 所对应的页面中
              Rule(link_detail, callback='parse_detail')
          )
      
          # 解析新闻编号和新闻的标题
          # 如下两个解析方法中是不可以实现请求传参!!!
          # 无法将两个解析方法解析的数据存储到同一个item中,可以依次存储到两个item
          def parse_item(self, response):
              li_list = response.xpath('//*[@id="frame_container"]/div/div[2]/div/div[2]/div[1]/ul/li')
              for li in li_list:
                  new_num = li.xpath('./div/span[1]//text()').extract()
                  new_num = ''.join(new_num)
                  new_num = new_num.split(':')[-1].strip()  
                  new_title = li.xpath('./a/b/text()').extract_first()
                  # print(new_num, new_title)
                  item = FuzhouproItem()
                  item['title'] = new_title
                  item['new_num'] = new_num
      
                  yield item
      
          # 解析新闻内容和新闻编号
          def parse_detail(self, response):
              # 注意:xpath表达式中不可以出现tbody标签,直接删除‘tbody’即可
              new_id = response.xpath('//*[@id="appeal-nature-0"]//tr[1]/td[2]/p/text()').extract_first()
              new_content = response.xpath('//*[@id="appeal-nature-0"]//tr[3]/td[2]/p//text()').extract()
              new_content = ''.join(new_content)
              # print(new_id, new_content)
              item = DetailItem()
              item['new_id'] = new_id
              item['content'] = new_content
      
              yield item
      
    • 在items.py中创建属性

      import scrapy
      
      
      class FuzhouproItem(scrapy.Item):
          # define the fields for your item here like:
          title = scrapy.Field()
          new_num = scrapy.Field()
      
      class DetailItem(scrapy.Item):
          new_id = scrapy.Field()
          content = scrapy.Field()
      
    • 在pipelines.py中编写代码

      class FuzhouproPipeline:
          def process_item(self, item, spider):
              # 如何判断item的类型
              # 将数据写入数据库时,如何保证数据的一致性:通过new_id和new_num进行关联
              if item.__class__.__name__ == 'DetailItem':
                  print(item['new_id'], item['content'])
              else:
                  print(item['new_num'], item['title'])
              return item
      
    • 在settings.py中修改以下内容:

      USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'
      
      # Obey robots.txt rules
      # True:遵从robots协议
      ROBOTSTXT_OBEY = False
      
      # 显示指定的类型的日志信息
      LOG_LEVEL = 'ERROR'
      
      # 开启管道
      ITEM_PIPELINES = {
         'fuzhouPro.pipelines.FuzhouproPipeline': 300,
      }
      
    • 在终端中输入:scrapy crawl fuzhou,即可

分布式爬虫

  • 概念:需要搭建一个分布式的集群,让其对一组资源进行分布联合爬取

  • 作用:提升爬取数据的效率

  • 如何实现分布式?

    • 安装一个scrapy-Redis的组件:pip install scrapy-redis

    • 原生的scrapy是不可以实现分布式爬虫,必须要让scrapy结合着scrapy-redis组件一起实现分布式爬虫

      • 为什么原生的scrapy不可以实现分布式爬虫?

        • 调度器不可以被分布式集群共享
        • 管道不可以被分布式集群共享
      • scrapy-redis组件作用

        • 可以给原生的scrapy框架提供可以被共享的管道和调度器
    • 实现流程

      • 创建一个工程

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

      • 修改当前的爬虫文件

        • 导包:from scrapy_redis.spiders import RedisCrawlSpider
        • 将start_urls和allowed_domains进行注释
        • 添加一个新属性:redis_key=‘xxx’ 可以被共享的调度器队列的名称
        • 编写数据解析相关的操作
        • 将当前爬虫类的父类修改成RedisCrawlSpider
      • 修改配置文件settings

        • 指定使用可以被共享的管道:

          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
          
      • redis相关操作配置:

        • 配置redis的配置文件:
          • linux或者mac:redis.conf
          • windows:redis.windows.conf
          • 打开配置文件修改:
            • 将bind 127.0.0.1进行删除
            • 关闭保护模式:protected-mode yes改为no
        • 结合着配置文件开启redis服务
          • 打开redis-server 配置文件
        • 启动客户端
          • redis-cli
      • 执行工程:

        • 进入spider文件目录下,输入scrapy runspider xxx.py
      • 向调度器的队列中放入一个起始的url:

        • 调度器的队列在redis的客户端中
          • 在redis客户端中输入lpush xxx www.xxx.com
      • 爬取到的数据存在了redis的proName:items这个数据结构中

案例

需求:爬取福州便民热线网站中的编号,新闻标题

代码实现

  • 创建一个工程:scrapy startproject fbsPro

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

    scrapy genspider -t crawl fbs www.xxx.com

  • 编写爬虫文件fbs.py

    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 = 'fuzhou'
        rules = (
            Rule(LinkExtractor(allow=r'pageSize=10&cp=\d+'), callback='parse_item', follow=True),
        )
    
        def parse_item(self, response):
            li_list = response.xpath('//*[@id="frame_container"]/div/div[2]/div/div[2]/div[1]/ul/li')
            for li in li_list:
                new_num = li.xpath('./div/span[1]//text()').extract()
                new_num = ''.join(new_num)
                new_num = new_num.split(':')[-1].strip()
                new_title = li.xpath('./a/b/text()').extract_first()
                
                item = FbsproItem()
                item['title'] = new_title
                item['new_num'] = new_num
    
                yield item
    
    
  • 在items.py中创建两个属性

    import scrapy
    
    class FbsproItem(scrapy.Item):
        # define the fields for your item here like:
        title = scrapy.Field()
        new_num = scrapy.Field()
    
  • 修改配置文件settings

    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36'
    
    # Obey robots.txt rules
    # True:遵从robots协议
    ROBOTSTXT_OBEY = False
    
    # 显示指定的类型的日志信息
    LOG_LEVEL = 'ERROR'
    
    # 指定管道
    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 = '127.0.0.1'  # 最好写成redis远程服务器的ip地址
    REDIS_PORT = 6379
    
  • 执行工程:

    进入spider文件目录下,输入scrapy runspider fbs.py

  • 向调度器的队列中放入一个起始的url:

    在redis客户端中输入lpush fuzhou http://fz12345.fuzhou.gov.cn/webEntAppealList.jsp?listType=1&pageSize=10&cp=1

  • 在redis客户端中查看爬取到的数据情况

    爬取的存在了redis的fbs:items这个数据结构中

    redis中相关命令如下:

    • key * : 查看数据存储结果列表
    • lrange fbs:items 0 -1 :查看存储数据
    • llen fbs:items:查看数据大小

    爬虫入门_8:scrapy框架_第8张图片
    如果本文对你有帮助,记得“点赞、收藏”哦~

你可能感兴趣的:(python,爬虫相关,爬虫,python,scapy)