005-Scrapy

简介

什么是框架?

所谓的框架,其实说白了就是一个【项目的半成品】,该项目的半成品需要被集成了各种功能且具有较强的通用性。

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍。所谓的框架就是一个已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)的具有很强通用性的项目模板。对于框架的学习,重点是要学习其框架的特性、各个功能的用法即可。

初期如何学习框架?

只需要学习框架集成好的各种功能的用法即可!前期切勿钻研框架的源码!

安装

Linux/mac系统:
      pip install scrapy(任意目录下)

Windows系统:可以直接pip install scrapy安装     

基本使用

  • 创建项目

    • scrapy startproject firstBlood项目名称

    • 项目的目录结构:

      • firstBlood   # 项目所在文件夹, 建议用pycharm打开该文件夹
            ├── firstBlood  		# 项目跟目录
            │   ├── __init__.py
            │   ├── items.py  		# 封装数据的格式
            │   ├── middlewares.py  # 所有中间件
            │   ├── pipelines.py	# 所有的管道
            │   ├── settings.py		# 爬虫配置信息
            │   └── spiders			# 爬虫文件夹, 稍后里面会写入爬虫代码
            │       └── __init__.py
            └── scrapy.cfg			# scrapy项目配置信息,不要删它,别动它,善待它. 
        
        
  • 创建爬虫爬虫文件:

    • cd project_name(进入项目目录)
    • scrapy genspider 爬虫文件的名称(自定义一个名字即可) 起始url (随便写一个网址即可)
      • (例如:scrapy genspider first www.xxx.com)
    • 创建成功后,会在爬虫文件夹下生成一个py的爬虫文件
  • 编写爬虫文件

    • 理解爬虫文件的不同组成部分

    • import scrapy
      
      class FirstSpider(scrapy.Spider):
          #爬虫名称:爬虫文件唯一标识:可以使用该变量的值来定位到唯一的一个爬虫文件
          name = 'first' #无需改动
          #允许的域名:scrapy只可以发起百度域名下的网络请求
          # allowed_domains = ['www.baidu.com']
          #起始的url列表:列表中存放的url可以被scrapy发起get请求
          start_urls = ['https://www.baidu.com/','https://www.sogou.com']
      
          #专门用作于数据解析
          #参数response:就是请求之后对应的响应对象
          #parse的调用次数,取决于start_urls列表元素的个数
          def parse(self, response):
              print('响应对象为:',response)
      
      
  • 配置文件修改:settings.py

    • 不遵从robots协议:ROBOTSTXT_OBEY = False
    • 指定输出日志的类型:LOG_LEVEL = 'ERROR'
    • 指定UA:USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36'
  • 运行项目

    • scrapy crawl 爬虫名称 :该种执行形式会显示执行的日志信息(推荐)
      

数据解析

  • 注意,如果终端还在第一个项目的文件夹中,则需要在终端中执行cd ../返回到上级目录,在去新建另一个项目。

  • 新建数据解析项目:

    • 创建工程:scrapy startproject 项目名称
    • cd 项目名称
    • 创建爬虫文件:scrapy genspider 爬虫文件名 www.xxx.com
  • 配置文件的修改:settings.py

    • 不遵从robots协议:ROBOTSTXT_OBEY = False
    • 指定输出日志的类型:LOG_LEVEL = 'ERROR'
    • 指定UA:USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36'
  • 编写爬虫文件:spiders/duanzi.py

    • #https://www.xiachufang.com/category/40076/
      
      import scrapy
      
      
      class BloodSpider(scrapy.Spider):
          #爬虫文件的唯一标识
          name = 'blood'
          #允许的域名
          # allowed_domains = ['www.baidu.com']
          #起始的url列表(重要):列表内部的url都会被框架进行异步的请求发送
          start_urls = ['https://www.xiachufang.com/category/40076/']
      
          #数据解析:parse调用的次数取决于start_urls列表元素的个数
          def parse(self, response): #response参数就表示响应对象
              #如何实现数据解析:xpath
              li_list = response.xpath('/html/body/div[4]/div/div/div[1]/div[1]/div/div[2]/div[2]/ul/li')
              for li in li_list:
                  #xpath最终会返回的是Selector对象,我们想要的解析的数据是存储在该对象的data属性中(extract可以实现该功能)
                  # title = li.xpath('./div/div/p[1]/a/text()')[0].extract() #一般不用
      
                  #extract_first可以将xpath返回列表中的第一个Selector对象中的data属性值获取
                  # title = li.xpath('./div/div/p[1]/a/text()').extract_first()
      
                  #extract可以将xpath返回列表中的每一个Selector对象中的data属性值获取
                  title = li.xpath('./div/div/p[1]/a/text()').extract()
      
                  #如果xpath返回的列表元素只有一个则使用extract_first,否则使用extract
                  print(title)
      

Mysql :如何建表,如何进行基本的数据查询,如何进行数据插入 V 8.0+

简介

什么是框架?

所谓的框,其实说白了就是一个【项目的半成品】,该项目的半成品需要被集成了各种功能且具有较强的通用性。

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,非常出名,非常强悍。所谓的框架就是一个已经被集成了各种功能(高性能异步下载,队列,分布式,解析,持久化等)的具有很强通用性的项目模板。对于框架的学习,重点是要学习其框架的特性、各个功能的用法即可。

初期如何学习框架?

只需要学习框架集成好的各种功能的用法即可!前期切勿钻研框架的源码!

安装

Linux/mac系统:
      pip install scrapy(任意目录下)

Windows系统:

      a. pip install wheel(任意目录下)

      b. 下载twisted文件,下载网址如下: http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

      c. 终端进入下载目录,执行 pip install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl
      注意:如果该步骤安装出错,则换一个版本的whl文件即可

      d. pip install pywin32(任意目录下)

      e. pip install scrapy(任意目录下)
      
如果安装好后,在终端中录入scrapy指令按下回车,如果没有提示找不到该指令,则表示安装成功

基本使用

  • 创建项目

    • scrapy startproject 项目名称

    • 项目的目录结构:

      • firstBlood   # 项目所在文件夹, 建议用pycharm打开该文件夹
            ├── firstBlood  		# 项目跟目录
            │   ├── __init__.py
            │   ├── items.py  		# 封装数据的格式
            │   ├── middlewares.py  # 所有中间件
            │   ├── pipelines.py	# 所有的管道
            │   ├── settings.py		# 爬虫配置信息
            │   └── spiders			# 爬虫文件夹, 稍后里面会写入爬虫代码
            │       └── __init__.py
            └── scrapy.cfg			# scrapy项目配置信息,不要删它,别动它,善待它. 
        
        
  • 创建爬虫爬虫文件:

    • cd project_name(进入项目目录)
    • scrapy genspider 爬虫文件的名称(自定义一个名字即可) 起始url
      • (例如:scrapy genspider first www.xxx.com)
    • 创建成功后,会在爬虫文件夹下生成一个py的爬虫文件
  • 编写爬虫文件

    • 理解爬虫文件的不同组成部分

    • import scrapy
      
      
      class BiliSpider(scrapy.Spider):
          #爬虫文件的名称,是当前爬虫文件的唯一标识
          name = 'bili'
          #允许的域名
          # allowed_domains = ['www.baidu.com']
          #起始的url列表:可以将即将被请求的url,存放在当前列表中。默认情况,列表中存储的url都会被scrapy框架进行get请求的发送
          start_urls = ['https://www.baidu.com/','https://www.sogou.com']
          #实现数据解析
          #参数response表示请求对应的响应对象
          #parse方法调用的次数取决于请求的次数
          def parse(self, response):
              print(response)
      
      
  • 配置文件修改:settings.py

    • 不遵从robots协议:ROBOTSTXT_OBEY = False
    • 指定输出日志的类型:LOG_LEVEL = 'ERROR'
    • 指定UA:USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36'
  • 运行项目

    • scrapy crawl 爬虫名称 :该种执行形式会显示执行的日志信息(推荐)
      

数据解析

  • 注意,如果终端还在第一个项目的文件夹中,则需要在终端中执行cd ../返回到上级目录,在去新建另一个项目。

  • 新建数据解析项目:

    • 创建工程:scrapy startproject 项目名称
    • cd 项目名称
    • 创建爬虫文件:scrapy genspider 爬虫文件名 www.xxx.com
  • 配置文件的修改:settings.py

    • 不遵从robots协议:ROBOTSTXT_OBEY = False
    • 指定输出日志的类型:LOG_LEVEL = 'ERROR'
    • 指定UA:USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36'
  • 编写爬虫文件:spiders/bilibili.py

    • import scrapy
      
      #爬取bili对应的视频标题
      class BiliSpider(scrapy.Spider):
          #爬虫文件的名称,是当前爬虫文件的唯一标识
          name = 'bili'
          #允许的域名
          # allowed_domains = ['www.baidu.com']
          #起始的url列表:可以将即将被请求的url,存放在当前列表中。默认情况,列表中存储的url都会被scrapy框架进行get请求的发送
          start_urls = ['https://search.bilibili.com/all?vt=40586385&keyword=%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1&from_source=webtop_search&spm_id_from=333.1007&search_source=5']
          #实现数据解析
          #参数response表示请求对应的响应对象
          #parse方法调用的次数取决于请求的次数
          def parse(self, response):
              #可以在响应对象中直接使用xpath进行数据解析
              div_list = response.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[2]/div/div')
              for div in div_list:
                  #注意:在scrapy中使用xpath进行数据解析,进行标签定位后,提取数据的时候,返回的是Selector对象而并非是提取处出的字符串类型的数据
                  #extract():可以将Selector中存储的字符串数据进行提取
                  # title = div.xpath('./div/div[2]/div/div/a/h3/@title')[0].extract()
                  # up_name = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()')[0].extract()
                  up_name = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract_first()
                  #extract_first() ==> [0].extract()
                  title = div.xpath('./div/div[2]/div/div/a/h3/@title').extract_first()
                  #在xpath后直接调用extract返回的数据会存储在一个列表中
                  # up_name = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract()
                  up_name = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract_first()
      
                  #extract():xpath返回的是多个数据
                  #extract_first():xpath返回的是单个数据
                  print(title,up_name)
      
      

持久化存储

两种方案:

  • 基于终端指令的持久化存储
  • 基于管道的持久化存储(推荐)

基于终端指令的持久化存储

  • 只可以将parse方法的返回值存储到指定后缀的文本文件中。

  • 编码流程:

    • 在爬虫文件中,将爬取到的数据全部封装到parse方法的返回值中

      • import scrapy
        
        
        class BiliSpider(scrapy.Spider):
            name = 'bili'
            # allowed_domains = ['www.xxx.com']
            start_urls = ['https://search.bilibili.com/all?vt=40586385&keyword=%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1&from_source=webtop_search&spm_id_from=333.1007&search_source=5']
            #基于终端指令的持久化存储:只可以将parse方法的返回值存储到固定后缀的文本文件中
            def parse(self, response):
                div_list = response.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[2]/div/div')
                all_data = []
                for div in div_list:
                    up_name = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract_first()
                    title = div.xpath('./div/div[2]/div/div/a/h3/@title').extract_first()
                    dic = {}
                    dic['title'] = title
                    dic['name'] = up_name
                    all_data.append(dic)
                return all_data #all_data里面就存储了爬取到的数据
        
        
    • 将parse方法的返回值存储到指定后缀的文本文件中:

      • scrapy crawl 爬虫文件名称 -o bilibili.csv
  • 总结:

    • 优点:简单,便捷
    • 缺点:局限性强
      • 只可以将数据存储到文本文件无法写入数据库
      • 存储数据文件后缀是指定好的,通常使用.csv
      • 需要将存储的数据封装到parse方法的返回值中

基于管道实现持久化存储

优点:极大程度的提升数据存储的效率

缺点:编码流程较多

编码流程

1.在爬虫文件中进行数据解析

import scrapy
from biliSavePro.items import BilisaveproItem

class BiliSpider(scrapy.Spider):
    name = 'bili'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://search.bilibili.com/all?vt=40586385&keyword=%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1&from_source=webtop_search&spm_id_from=333.1007&search_source=5']

    def parse(self, response):
        div_list = response.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[2]/div/div')
        all_data = []
        for div in div_list:
            up_name = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract_first()
            title = div.xpath('./div/div[2]/div/div/a/h3/@title').extract_first()
           

2.将解析到的数据封装到Item类型的对象中

  • 2.1 在items.py文件中定义相关的字段

    • # Define here the models for your scraped items
      #
      # See documentation in:
      # https://docs.scrapy.org/en/latest/topics/items.html
      
      import scrapy
      
      
      class BilisaveproItem(scrapy.Item):
          # define the fields for your item here like:
          # name = scrapy.Field()
          #进行字段的定义:数据解析出来几个字段,这里就需要定义几个字段
          title = scrapy.Field()
          up_name = scrapy.Field()
      
      
  • 2.2 在爬虫文件中引入Item类,实例化item对象,将解析到的数据存储到item对象中

    • def parse(self, response):
              div_list = response.xpath('//*[@id="i_cecream"]/div/div[2]/div[2]/div/div/div/div[2]/div/div')
              all_data = []
              for div in div_list:
                  up_name = div.xpath('./div/div[2]/div/div/p/a/span[1]/text()').extract_first()
                  title = div.xpath('./div/div[2]/div/div/a/h3/@title').extract_first()
                  #创建一个item类型的对象
                  item = BilisaveproItem()
                  #将解析出来的数据存储到item类型对象中
                  item['title'] = title #将数据解析出来的title数据存储到item对象中的title属性中
                  item['up_name'] = up_name
      
                  #将item对象提交给管道
                  yield item
      

3.将item对象提交给管道

  • #将存储好数据的item对象提交给管道
    yield item
    

4.在管道中接收item类型对象(pipelines.py就是管道文件)

  • 管道只可以接收item类型的对象,不可以接收其他类型对象

  • class SavedataproPipeline:
        #process_item用来接收爬虫文件传递过来的item对象
        #item参数,就是管道接收到的item类型对象
        def process_item(self, item, spider):
            print(item)
            return item
    

5.在管道中对接收到的数据进行任意形式的持久化存储操作

  • 可以存储到文件中也可以存储到数据库中

  • # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
    
    
    # useful for handling different item types with a single interface
    from itemadapter import ItemAdapter
    
    
    class BilisaveproPipeline:
        fp = None
        #重写父类的方法
        def open_spider(self,spider):
            #该方法只会在项目运行时process_item方法调用前被执行一次
            print('i am open_spider()')
            self.fp = open('bili.txt','w')
    
        #该方法是用来接收爬虫文件提交过来的item对象
        #参数item就是爬虫文件提交过来的item对象
        #process_item会被调用多次(调用的次数取决于爬虫文件向管道提交item的次数)
        def process_item(self, item, spider):
            # print(item['title'],item['up_name'])
            self.fp.write(item['up_name']+':'+item['title']+'\n')
            print('数据成功被保存!!!')
            return item
    
        def close_spider(self,spider):
            print('i am close_spider()')
            #该方法只会在process_item方法调用结束后被调用一次
            self.fp.close()
    
    

6.在配置文件中开启管道机制

  • 注意:默认情况下,管道机制是没有被开启的,需要在配置文件中手动开启
  • 在setting.py中把ITEM_PIPELINES解除注释就表示开启了管道机制

管道深入操作

  • 如何将数据存储到数据库

    • 注意:一个管道类负责将数据存储到一个具体的载体中。如果想要将爬取到的数据存储到多个不同的载体/数据库中,则需要定义多个管道类。
  • 思考:

    • 在有多个管道类的前提下,爬虫文件提交的item会同时给没一个管道类还是单独的管道类?
      • 爬虫文件只会将item提交给优先级最高的那一个管道类。优先级最高的管道类的process_item中需要写return item操作,该操作就是表示将item对象传递给下一个管道类,下一个管道类获取了item对象,才可以将数据存储成功!
  • 管道类:

  • import pymysql #pip install pymysql
    #pymysql可以实现使用python程序远程连接mysql数据库
    class BiliprodbPipeline:
        conn = None  # mysql的链接对象
        cursor = None  # 游标对象
        def open_spider(self,spider):
            #创建链接对象
            self.conn = pymysql.Connect(
                host='127.0.0.1',#数据库服务器ip地址
                port=3306, #mysql固定端口号
                user='root',#mysql用户名
                password='boboadmin',#mysql密码
                db='db001',
                charset='utf8'
            )
            #创建游标对象:是用来执行sql语句
            self.cursor = self.conn.cursor()
        #将数据存储到mysql数据库
        def process_item(self, item, spider):
            sql = 'insert into bili values ("%s","%s")'%(item['up_name'],item['title'])
            self.cursor.execute(sql)
            self.conn.commit() #提交事物
            print('数据存储到mysql中......')
            return item #item会返回给下一个即将被执行的管道类
    
        def close_spider(self,spider):
            self.cursor.close()
            self.conn.close()
    
    #将数据持久化存储到redis中
    from redis import Redis
    class BiliprodbPipelineRedis:
        conn = None
        def open_spider(self,spider):
            self.conn = Redis(
                host='127.0.0.1',
                port=6379
            )
        def process_item(self, item, spider):
            #item本质是一个字典
            self.conn.lpush('bili',item)
            print('数据存储到redis中......')
            return item
    
    
    import pymongo
    class MongoPipeline:
        conn = None #链接对象
        db_sanqi = None #数据仓库
        def open_spider(self,spider):
            self.conn = pymongo.MongoClient(
                host='127.0.0.1',
                port=27017
            )
            self.db_sanqi = self.conn['sanqi']
        def process_item(self,item,spider):
            self.db_sanqi['xiaoshuo'].insert_one({'title':item['title']})
            print('插入成功!')
            return item
    
  • 配置文件:

  • ITEM_PIPELINES = {
       #管道类后面的数字表示管道类的优先级,数字越小优先级越高。优先级越高,则表示该管道类会被优先执行
       'biliProDB.pipelines.BiliprodbPipeline': 300,
       'biliProDB.pipelines.BiliprodbPipelineRedis': 301,
       'biliProDB.pipelines.MongoPipeline': 302
    }
    

scrapy爬取多媒体资源数据

  • 使用一个专有的管道类ImagesPipeline

  • pip install PIL / pip install Pillow

  • 具体的编码流程:

    • 1.在爬虫文件中进行图片/视频的链接提取

    • 2.将提取到的链接封装到items对象中,提交给管道

    • 3.在管道文件中自定义一个父类为ImagesPipeline的管道类,且重写三个方法即可:

      • def get_media_requests(self, item, info):接收爬虫文件提交过来的item对象,然后对图片地址发起网路请求,返回图片的二进制数据
        
        def file_path(self, request, response=None, info=None, *, item=None):指定保存图片的名称
        def item_completed(self, results, item, info):返回item对象给下一个管道类
        
    • 爬虫文件代码:

    import scrapy
    
    from imgPro.items import ImgproItem
    class ImgSpider(scrapy.Spider):
        name = 'img'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['http://pic.netbian.com/4kmeinv/']
    
        def parse(self, response):
            #解析图片地址
            li_list = response.xpath('//*[@id="main"]/div[3]/ul/li')
            for li in li_list:
                img_src = 'http://pic.netbian.com'+li.xpath('./a/img/@src').extract_first()
                #图片地址封装到item对象中,且将item提交给管道即可
                item = ImgproItem()
                item['img_src'] = img_src
                
                yield item
    
    • 4.在配置文件中开启指定的管道,且通过IMAGES_STORE = 'girlsLib'操作指定图片存储的文件夹。
    # Define your item pipelines here
    #
    # Don't forget to add your pipeline to the ITEM_PIPELINES setting
    # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
    
    
    # useful for handling different item types with a single interface
    import scrapy
    from itemadapter import ItemAdapter
    
    from scrapy.pipelines.images import ImagesPipeline
    
    #自定义的管道类一定要继承与ImagesPipeline
    class mediaPileline(ImagesPipeline):
        #重写三个父类的方法来完成图片二进制数据的请求和持久化存储
        #可以根据图片地址,对其进行请求,获取图片数据
        #参数item:就是接收到的item对象
        def get_media_requests(self, item, info):
            img_src = item['src']
            yield scrapy.Request(img_src)
        #指定图片的名称(只需要返回图片存储的名称即可)
        def file_path(self, request, response=None, info=None, *, item=None):
            imgName = request.url.split('/')[-1]
            print(imgName,'下载保存成功!')
            return imgName
        #如果没有下一个管道类,该方法可以不写
        def item_completed(self, results, item, info):
            return item #可以将当前的管道类接收到item对象传递给下一个管道类2.
    

scrapy深度爬取

  • 如何爬取多页的数据(全站数据爬取)

    • 手动请求发送:

      • #callback用来指定解析方法
        yield scrapy.Request(url=new_url,callback=self.parse)
        
  • 如何爬取深度存储的数据

    • 什么是深度,说白了就是爬取的数据没有存在于同一张页面中。

    • 必须使用请求传参的机制才可以完整的实现。

      • 请求传参:

        • yield scrapy.Request(meta={},url=detail_url,callback=self.parse_detail)
          
          可以将meta字典传递给callback这个回调函数
          
import scrapy

from deepPro.items import DeepproItem
class DeepSpider(scrapy.Spider):
    name = 'deep'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://wz.sun0769.com/political/index/politicsNewest']

    def parse(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()
            state = li.xpath('./span[2]/text()').extract_first().strip()
            detail_url = 'https://wz.sun0769.com'+li.xpath('./span[3]/a/@href').extract_first().strip()

            #创建一个item类型的对象
            item = DeepproItem()
            item['title'] = title
            item['state'] = state
            #请求传参的技术,将此处的item对象传递给指定的函数:meta作用,可以将一个字典传递给callback指定的回调函数
            #对详情页的url进行请求发送(手动请求发送GET)
            yield scrapy.Request(url=detail_url,callback=self.parse_detail,meta={'item':item})


    def parse_detail(self,response):
        #负责对详情页的页面源码数据进行数据解析
        content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]//text()').extract()
        content = ''.join(content).strip()

        #接收通过请求传参传递过来的字典数据
        dic_meta = response.meta
        item = dic_meta['item']
        item['content'] = content

        yield item


  • 拓展:爬取多页的数据
import scrapy

from deepPro.items import DeepproItem
class DeepSpider(scrapy.Spider):
    name = 'deep'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://wz.sun0769.com/political/index/politicsNewest']

    #通用的url模版
    url_model = 'https://wz.sun0769.com/political/index/politicsNewest?id=1&page=%d'
    #页码
    page_num = 2

    def parse(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()
            state = li.xpath('./span[2]/text()').extract_first().strip()
            detail_url = 'https://wz.sun0769.com'+li.xpath('./span[3]/a/@href').extract_first().strip()

            #创建一个item类型的对象
            item = DeepproItem()
            item['title'] = title
            item['state'] = state
            #请求传参的技术,将此处的item对象传递给指定的函数:meta作用,可以将一个字典传递给callback指定的回调函数
            #对详情页的url进行请求发送(手动请求发送GET)
            yield scrapy.Request(url=detail_url,callback=self.parse_detail,meta={'item':item})

        if self.page_num <= 5:
            new_url = format(self.url_model % self.page_num)
            self.page_num += 1
            yield scrapy.Request(url=new_url,callback=self.parse)


    def parse_detail(self,response):
        #负责对详情页的页面源码数据进行数据解析
        content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]//text()').extract()
        content = ''.join(content).strip()

        #接收通过请求传参传递过来的字典数据
        dic_meta = response.meta
        item = dic_meta['item']
        item['content'] = content

        yield item

如何提高scrapy的爬取效率

增加并发:
    默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。

降低日志级别:
    在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为WORNING或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘ERROR’

post请求发送

  • 问题:在之前代码中,我们从来没有手动的对start_urls列表中存储的起始url进行过请求的发送,但是起始url的确是进行了请求的发送,那这是如何实现的呢?

    • 解答:其实是因为爬虫文件中的爬虫类继承到了Spider父类中的start_requests(self)这个方法,该方法就可以对start_urls列表中的url发起请求:

    •   class PostdemoSpider(scrapy.Spider):
            name = 'postDemo'
            # allowed_domains = ['www.xxx.com']
          
            #让scrapy对start_urls中的列表元素进行post请求的发送
            start_urls = ['http://www.xxx.com/']
        
            #该函数是scrapy已经写好了,运行项目的时候,scrapy是在调用该方法进行相关请求的发送
            def start_requests(self):
                for url in self.start_urls:
                    #FormRequest进行post请求发送
                    yield scrapy.FormRequest(url=url,callback=self.parse,formdata='post请求参数')
            
            def parse(self, response):
                pass
      
    • 【注意】该方法默认的实现,是对起始的url发起get请求,如果想发起post请求,则需要子类重写该方法。

      • yield scrapy.Request():发起get请求
      • yield scrapy.FormRequest():发起post请求
    • import scrapy
      
      class PostdemoSpider(scrapy.Spider):
          name = 'postDemo'
          # allowed_domains = ['www.xxx.com']
      
          #让scrapy对start_urls中的列表元素进行post请求的发送
          start_urls = ['https://fanyi.baidu.com/sug']
      
          #该函数是scrapy已经写好了,运行项目的时候,scrapy是在调用该方法进行相关请求的发送
          def start_requests(self):
              for url in self.start_urls:
                  #FormRequest进行post请求发送
                  yield scrapy.FormRequest(url=url,callback=self.parse,formdata={"kw": "dog"})
      
          def parse(self, response):
              #数据解析
              ret = response.json()
              print(ret)
      

scrapy的核心组件

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

中间件

  • scrapy的中间件有两个:

    • 爬虫中间件
    • 下载中间件
    • 中间件的作用是什么?
      • 观测中间件在五大核心组件的什么位置,根据位置了解中间件的作用
        • 下载中间件位于引擎和下载器之间
        • 引擎会给下载器传递请求对象,下载器会给引擎返回响应对象。
        • 作用:可以拦截到scrapy框架中所有的请求和响应。
          • 拦截请求干什么?
            • 修改请求的ip,修改请求的头信息,设置请求的cookie
          • 拦截响应干什么?
            • 可以修改响应数据
  • 中间件重要方法:

  • # Define here the models for your spider middleware
    #
    # See documentation in:
    # https://docs.scrapy.org/en/latest/topics/spider-middleware.html
    
    from scrapy import signals
    
    # useful for handling different item types with a single interface
    from itemadapter import is_item, ItemAdapter
    from scrapy.http import HtmlResponse
    
    class MiddleproDownloaderMiddleware:
    
        #拦截处理所有的请求对象
        #参数:request就是拦截到的请求对象,spider爬虫文件中爬虫类实例化的对象
        #spider参数的作用可以实现爬虫类和中间类的数据交互
        def process_request(self, request, spider):
            return None
        #拦截处理所有的响应对象
        #参数:response就是拦截到的响应对象,request就是被拦截到响应对象对应的唯一的一个请求对象
        def process_response(self, request, response, spider):
            #篡改了拦截到的响应数据:返回一个自定义的新的响应对象
            response = HtmlResponse(request=request,body='123',url=request.url,encoding='utf-8')
            return response
        #拦截和处理发生异常的请求对象
        #参数:reqeust就是拦截到的发生异常的请求对象
        def process_exception(self, request, exception, spider):
            pass
        #控制日志数据的(忽略)
        def spider_opened(self, spider):
            spider.logger.info('Spider opened: %s' % spider.name)
    
    

开发代理中间件

  • request.meta['proxy'] = proxy

  • # Define here the models for your spider middleware
    #
    # See documentation in:
    # https://docs.scrapy.org/en/latest/topics/spider-middleware.html
    
    from scrapy import signals
    
    # useful for handling different item types with a single interface
    from itemadapter import is_item, ItemAdapter
    from scrapy import Request
    
    class MiddleproDownloaderMiddleware:
       
        #拦截处理所有的请求对象
        #参数:request就是拦截到的请求对象,spider爬虫文件中爬虫类实例化的对象
        #spider参数的作用可以实现爬虫类和中间类的数据交互
        def process_request(self, request, spider):
            #是的所有的请求都是用代理,则代理操作可以写在该方法中
            request.meta['proxy'] = 'http://ip:port'
            #弊端:会使得整体的请求效率变低
            print(request.url+':请求对象拦截成功!')
            return None
        #拦截处理所有的响应对象
        #参数:response就是拦截到的响应对象,request就是被拦截到响应对象对应的唯一的一个请求对象
        def process_response(self, request, response, spider):
            print(request.url+':响应对象拦截成功!')
            return response
        #拦截和处理发生异常的请求对象
        #参数:reqeust就是拦截到的发生异常的请求对象
        #方法存在的意义:将发生异常的请求拦截到,然后对其进行修正
        def process_exception(self, request, exception, spider):
            print(request.url+':发生异常的请求对象被拦截到!')
            #修正操作
            #只有发生了异常的请求才使用代理机制,则可以写在该方法中
            request.meta['proxy'] = 'https://ip:port'
            return request #对请求对象进行重新发送
        #控制日志数据的(忽略)
        def spider_opened(self, spider):
            spider.logger.info('Spider opened: %s' % spider.name)
    
    

开发UA中间件

  • request.headers['User-Agent'] = ua

  •     def process_request(self, request, spider):
            request.headers['User-Agent'] = '从列表中随机选择的一个UA值'
            print(request.url+':请求对象拦截成功!')
            return None
    

开发Cookie中间件

  • request.cookies = cookies

  • def process_request(self, request, spider):
        request.headers['cookie'] = 'xxx'
        #request.cookies = 'xxx'
        print(request.url+':请求对象拦截成功!')
        return None
    

中间件中spider参数的作用:

可以实现爬虫文件和中间件进行的数据交互,构建代理池为例:

spider爬虫文件

import scrapy

class MiddleSpider(scrapy.Spider):
    name = 'middle'
    # allowed_domains = ['www.xxx.com']
    start_urls = ['https://www.baidu.com/','https://www.sogou.com/','https://www.jd.com/']

    #代理池
    proxy_list = [
        'https://ip:port','https://ip:port','https://ip:port','https://ip:port','https://ip:port'
    ]#该代理池是需要再中间件中被应用/使用

    def parse(self, response):
        pass

中间件代码:

    def process_request(self, request, spider):
        print('i am process_reqeust()')
      
        #获取在爬虫文件中定义好的代理池
        proxy_list = spider.proxy_list
        print(proxy_list)
        #设置拦截到请求对象的代理
        import random
        request.meta['proxy'] = random.choice(proxy_list)

CrawlSpider

  • 实现网站的全站数据爬取

    • 就是将网站中所有页码对应的页面数据进行爬取。
  • crawlspider其实就是scrapy封装好的一个爬虫类,通过该类提供的相关的方法和属性就可以实现全新高效形式的全站数据爬取。

  • 使用流程:

    • 新建一个scrapy项目

    • cd 项目

    • 创建爬虫文件(*):

      • scrapy genspider -t crawl spiderName www.xxx.com

      • 爬虫文件中发生的变化有哪些?

        • 当前爬虫类的父类为CrawlSpider
        • 爬虫类中多了一个类变量叫做rules
          • LinkExtractor:链接提取器
            • 可以根据allow参数表示的正则在当前页面中提取符合正则要求的链接
          • Rule:规则解析器
            • 可以接收链接提取器提取到的链接,并且对每一个链接进行请求发送
            • 可以根据callback指定的回调函数对每一次请求到的数据进行数据解析
        • 思考:如何将一个网站中所有的链接都提取到呢?
          • 只需要在链接提取器的allow后面赋值一个空正则表达式即可
        • 目前在scrapy中有几种发送请求的方式?
          • start_urls列表可以发送请求
          • scrapy.Request()
          • scrapy.FormRequest()
          • Rule规则解析器
  • 注意:

    • 链接提取器和规则解析器是一一对应的(一对一的关系)
    • 建议在使用crawlSpider实现深度爬取的时候,需要配合手动请求发送的方式进行搭配!
  • USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36'
    
  • 爬取不同页码下的标题数据

    • import scrapy
      from time import sleep
      from scrapy.linkextractors import LinkExtractor
      from scrapy.spiders import CrawlSpider, Rule
      #LinkExtractor链接提取器:可以根据指定的规则进行链接的提取
      #Rule规则解析器:可以根据指定规则对请求到的数据进行数据解析
      class SunSpider(CrawlSpider):
          name = 'sun'
          # allowed_domains = ['www.xxx.com']
          start_urls = ['https://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']
      
          #link就表示创建出来的一个链接提取器
          #allow参数后面要跟一个正则表达式,就可以作为链接提取的规则
          #link首先会去start_urls表示的页面中进行链接的提取
          link = LinkExtractor(allow=r'id=1&page=(\d+)')
          rules = (
              #创建了一个规则解析器
              #链接提取器提取到的链接会发送给Rule这个规则解析器
              #规则解析器接收到链接后,会对链接进行请求发送,并且根据callback指定的函数进行数据解析
              Rule(link, callback='parse_item', follow=True),
          )
      
          #解析方法的调用次数取决于link提取到的链接的个数
          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()
                  print(title)
      
  • 爬取页码对应页面的标题数据和详情页中的详细内容数据(深度爬取)

    • import scrapy
      from time import sleep
      from scrapy.linkextractors import LinkExtractor
      from scrapy.spiders import CrawlSpider, Rule
      from crawlPro.items import CrawlproItem
      #LinkExtractor链接提取器:可以根据指定的规则进行链接的提取
      #Rule规则解析器:可以根据指定规则对请求到的数据进行数据解析
      class SunSpider(CrawlSpider):
          name = 'sun'
          # allowed_domains = ['https://wz.sun0769.com']
          start_urls = ['https://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']
      
          #链接提取器和规则解析器一定是一对一关系
              #提取页码链接
          link = LinkExtractor(allow=r'id=1&page=\d+')
      
          rules = (
              #解析页面对应页面中的标题数据
              Rule(link, callback='parse_item', follow=False),
          )
      
          #解析方法的调用次数取决于link提取到的链接的个数
          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()
                  detail_url = 'https://wz.sun0769.com' + li.xpath('./span[3]/a/@href').extract_first()
                  item = CrawlproItem()
                  item['title'] = title
                  yield scrapy.Request(url=detail_url, callback=self.parse_detail,meta={'item':item})
      
          def parse_detail(self,response):
              item = response.meta['item']
              detail_content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]//text()').extract()
              item['content'] = detail_content
              yield item
      

分布式

  • 分布式在日常开发中并不常用,只是一个噱头!

  • 概念:

    • 可以使用多台电脑搭建一个分布式机群,使得多台对电脑可以对同一个网站的数据进行联合且分布的数据爬取。
  • 声明:

    • 原生的scrapy框架并无法实现分布式操作!why?
      • 多台电脑之间无法共享同一个调度器
      • 多台电脑之间无法共享同一个管道
  • 如何是的scrapy可以实现分布式呢?

    • 借助于一个组件:scrapy-redis
    • scrapy-redis的作用是什么?
      • 可以给原生的scrapy框架提供可被共享的调度器和管道!
      • 环境安装:pip install scrapy-redis
        • 注意:scrapy-redis该组件只可以将爬取到的数据存储到redis数据库
  • 编码流程(重点):

    • 1.创建项目

    • 2.cd 项目

    • 3.创建基于crawlSpider的爬虫文件

      • 3.1 修改爬虫文件
        • 导包:from scrapy_redis.spiders import RedisCrawlSpider
        • 修改当前爬虫类的父类为 RedisCrawlSpider
        • 将start_urls替换成redis_key的操作
          • redis_key变量的赋值为字符串,该字符串表示调度器队列的名称
        • 进行常规的请求操作和数据解析
    • 4.settings配置文件的修改

      • 常规内容修改(robots和ua等),先不指定日志等级

      • 指定可以被共享的管道类

        • ITEM_PIPELINES = {
              'scrapy_redis.pipelines.RedisPipeline': 400
          }
          
      • 指定可以被共享的调度器

        • # 使用scrapy-redis组件的去重队列
          DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
          # 使用scrapy-redis组件自己的调度器
          SCHEDULER = "scrapy_redis.scheduler.Scheduler"
          # 是否允许暂停
          SCHEDULER_PERSIST = True
          
      • 指定数据库

        • REDIS_HOST = '127.0.0.1'
          REDIS_PORT = 6379
          
    • 5.修改redis数据库的配置文件(redis.windows.conf)

      • 在配置文件中改行代码是没有没注释的:

        • bind 127.0.0.1
          #将上述代码注释即可(解除本机绑定,实现外部设备访问本机数据库
          
          如果配置文件中还存在:protected-mode = true,将true修改为false,
          修改为false后表示redis数据库关闭了保护模式,表示其他设备可以远程访问且修改你数据库中的数据
          
    • 6.启动redis数据库的服务端和客户端

    • 7.运行项目,发现程序暂定一直在等待,等待爬取任务

    • 8.需要向可以被共享的调度器的队列(redis_key的值)中放入一个起始的url

增量式

  • 爬虫应用场景分类

    • 通用爬虫

    • 聚焦爬虫

    • 功能爬虫

    • 分布式爬虫

    • 增量式:

      • 用来监测网站数据更新的情况(爬取网站最新更新出来的数据)。
      • 只是一种程序设计的思路,使用什么技术都是可以实现的。
      • 核心:
        • 去重。
          • 使用一个记录表来实现数据的去重:
            • 记录表:存储爬取过的数据的记录
            • 如何构建和设计一个记录表:
              • 记录表需要具备的特性:
                • 去重
                • 需要持久保存的
              • 方案1:使用Python的set集合充当记录表?
                • 不可以的!因为set集合无法实现持久化存储
              • 方案2:使用redis的set集合充当记录表?
                • 可以的,因为redis的set既可以实现去重又可以进行数据的持久化存储。
    • import scrapy
      from zlsPro.items import ZlsproItem
      from redis import Redis
      class ZlsSpider(scrapy.Spider):
          name = 'zls'
          # allowed_domains = ['www.xxx.com']
          start_urls = ['https://sc.chinaz.com/jianli/free.html']
          #进行redis的链接
          conn = Redis(host='127.0.0.1',port=6379)
          def parse(self, response):
              div_list = response.xpath('//*[@id="container"]/div')
              for div in div_list:
                  #简历的标题
                  title = div.xpath('./p/a/text()').extract_first()
                  #简历详情页链接(存储到记录表中)
                  detail_url = div.xpath('./p/a/@href').extract_first()
                  item = ZlsproItem()
                  item['title'] = title
                  #判断是否要进行详情页的请求发送
                  ex = self.conn.sadd('data_id',detail_url)
                  if ex == 1:#detail_url作为简历的唯一标识在记录表中不存在(简历之前没有爬取过)
                      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']
              download_url = response.xpath('//*[@id="down"]/div[2]/ul/li[1]/a/@href').extract_first()
              item['download_url'] = download_url
              yield item
      
      
#需求:https://ks.wangxiao.cn/,所有类别下的题目进行爬取
#分析思路:
    #1.可以在首页将所有的一级标题、二级标题和二级标题对应的详情页链接进行爬取和解析
    #2.发现上一步中,获取的二级标题的详情页链接,对应的是【模拟考试】的内容,并不是我们想要的【每日一练】的内容
    #3.观察【每日一练】和【模拟考试】的链接,找出相同和不同之处,然后将【模拟考试】的链接修改成【每日一练】的链接
    #4.获取了每一个二级标题对应【每日一练】的详情页链接
    #5.可以在当前页面中,点击【开始做题】,进行题目的练习,发现,需要经过登录后,才可以点击【开始做题】
    #6.可以在页面中,手动进行账号密码登录,登录成功后,打开抓包工具,刷新页面,获取登录后的cookie
    #7.抓取点击【开始做题】后对应的数据包,发现该数据包是一个post请求,发现该请求的请求参数中有3个动态变化的值(day,sign和subsign)
    #8.批量获取不同二级标题对应试题数据对应post数据包中动态变化的请求参数(day,sign和subsign)
    #9.经过分析发现,每一个二级标题对应【每日一练】页面中的每一个试题名称对应的详情页url中就包含了,这三个动态变化的请求参数
    #10.携带cookie和这三个动态变化的请求参数,进行试题内容对应的post请求
    #11.请求到json数据后,进行数据解析:题目类型、题目内容、四个选项和正确答案
    #12.将数据进行持久化存储
import requests
from lxml import etree
from time import sleep

# headers = {
#     'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36'
# }
headers = {
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
    'Cookie':'pc_426029864_exam=fangchan; mantis6894=b596f9f44b0e45b6aae8ce8e309e105b@6894; register-sign=jz1; sign=jz1; safedog-flow-item=; autoLogin=null; userInfo=%7B%22userName%22%3A%22pc_426029864%22%2C%22token%22%3A%22a6d09089-908c-49fb-8149-d239724e6a29%22%2C%22headImg%22%3Anull%2C%22nickName%22%3A%22150****0535%22%2C%22sign%22%3A%22fangchan%22%2C%22isBindingMobile%22%3A%221%22%2C%22isSubPa%22%3A%220%22%2C%22userNameCookies%22%3A%22cB1eRc1MclcnoVHZWhUk%2BA%3D%3D%22%2C%22passwordCookies%22%3A%22I%2FkEnX2w1ijTM59lRNF4q05CAUTGjdkx%22%7D; token=a6d09089-908c-49fb-8149-d239724e6a29; UserCookieName=pc_426029864; OldUsername2=cB1eRc1MclcnoVHZWhUk%2BA%3D%3D; OldUsername=cB1eRc1MclcnoVHZWhUk%2BA%3D%3D; OldPassword=I%2FkEnX2w1ijTM59lRNF4q05CAUTGjdkx; UserCookieName_=pc_426029864; OldUsername2_=cB1eRc1MclcnoVHZWhUk%2BA%3D%3D; OldUsername_=cB1eRc1MclcnoVHZWhUk%2BA%3D%3D; OldPassword_=I%2FkEnX2w1ijTM59lRNF4q05CAUTGjdkx'
}
#定义首页的url
main_url = 'https://ks.wangxiao.cn/'
#请求到了首页的页面源码数据
main_page_text = requests.get(url=main_url,headers=headers).text
#数据解析:解析出二阶页面的链接和对应的标题
tree = etree.HTML(main_page_text)
li_list = tree.xpath('//*[@id="banner"]/div[2]/ul/li')
for li in li_list:
    #获取一级标题
    c1_title = li.xpath('./p/span/text()')[0]
    #获取一级标题下对应的二级标题和二级标题详情页链接
    a_list = li.xpath('./div/a')
    for a in a_list:
        c2_title = a.xpath('./text()')[0]
        #注意:二级链接对应的是详情页中的【模拟考试】,而我们想要的是二级页面中的【每日一练】
        c2_url = 'https://ks.wangxiao.cn'+a.xpath('./@href')[0]
        #将c2_url表示的【模拟考试】的链接变换成【每日一练】的链接
        sign = c2_url.split('?')[-1]
        c2_url = 'https://ks.wangxiao.cn/practice/listEveryday?'+sign
        #https://ks.wangxiao.cn/TestPaper/list?sign=jzs1        【模拟考试】
        #https://ks.wangxiao.cn/practice/listEveryday?sign=jzs1 【每日一练】
        # print(c1_title,c2_title,c2_url)

        #批量请求二级标题下对应的练习题的内容(post请求),该post请求的请求参数中sign和subsign是动态变化的
            #批量的将不同二级标题对应的sign和subsign进行批量获取
            #经过分析发现:在二级标题对应每日一练详情页的页面源码中就存在sign和subsign动态内容值

        c2_page_text = requests.get(url=c2_url,headers=headers).text
        c2_tree = etree.HTML(c2_page_text)
        div_list = c2_tree.xpath('//div[@class="test-panel"]/div')
        if div_list: #有的二级标题对应的【每日一练】下面是没有练习题
            for div in div_list:
                c2_detail_url = div.xpath('./ul/li[4]/a/@href')[0]
                #批量从c2_detail_url解析出sign和subsign
                #/practice/getQuestion?practiceType=1   &   sign=jz1  &  subsign=22c51d8d3ccb4e309a60  &  day=20230826
                item_list = c2_detail_url.split('&')
                sign = item_list[1].split('=')[-1]
                subsign = item_list[2].split('=')[-1]
                day = item_list[3].split('=')[-1]
                # print(sign,subsign,day)
                post_url = 'https://ks.wangxiao.cn/practice/listQuestions'
                data = {"practiceType":"1","sign":sign,"subsign":subsign,"day":day}
                #获取了post请求,请求到的json格式的响应数据,在该响应数据中可以解析出试题,选项和正确答案
                ret = requests.post(url=post_url,headers=headers,json=data).json()
                sleep(2)
                for que in ret['Data'][0]['questions']:
                    type = ret['Data'][0]['paperRule']['title'] #题型
                    title = que['content'] #题目
                    choose_list = [] #存放4个选项
                    choose_state = [] #存放4个选项是否为正确答案,1表示正确答案,0表示错误答案
                    for an in que['options']:
                        choose = an['content']
                        choose_list.append(choose)
                        isRight = an['isRight'] #每一个选项是否为正确答案,返回值1表示正确答,0表示错误答案
                        choose_state.append(isRight)

                    print(c1_title,c2_title,type,title,choose_list,choose_state)
                    break
                break
            break



# import requests
# headers = {
#     'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
#     'Cookie':'pc_426029864_exam=fangchan; mantis6894=b596f9f44b0e45b6aae8ce8e309e105b@6894; register-sign=jz1; sign=jz1; safedog-flow-item=; autoLogin=null; userInfo=%7B%22userName%22%3A%22pc_426029864%22%2C%22token%22%3A%22a6d09089-908c-49fb-8149-d239724e6a29%22%2C%22headImg%22%3Anull%2C%22nickName%22%3A%22150****0535%22%2C%22sign%22%3A%22fangchan%22%2C%22isBindingMobile%22%3A%221%22%2C%22isSubPa%22%3A%220%22%2C%22userNameCookies%22%3A%22cB1eRc1MclcnoVHZWhUk%2BA%3D%3D%22%2C%22passwordCookies%22%3A%22I%2FkEnX2w1ijTM59lRNF4q05CAUTGjdkx%22%7D; token=a6d09089-908c-49fb-8149-d239724e6a29; UserCookieName=pc_426029864; OldUsername2=cB1eRc1MclcnoVHZWhUk%2BA%3D%3D; OldUsername=cB1eRc1MclcnoVHZWhUk%2BA%3D%3D; OldPassword=I%2FkEnX2w1ijTM59lRNF4q05CAUTGjdkx; UserCookieName_=pc_426029864; OldUsername2_=cB1eRc1MclcnoVHZWhUk%2BA%3D%3D; OldUsername_=cB1eRc1MclcnoVHZWhUk%2BA%3D%3D; OldPassword_=I%2FkEnX2w1ijTM59lRNF4q05CAUTGjdkx'
# }
# url = 'https://ks.wangxiao.cn/practice/listQuestions'
# #请求参数,注意:不同二级类别对应的题目的post请求中的sign和subsign这两个参数是不同
# data = {"practiceType":"1","sign":"KYjyx","subsign":"6abdd5ad4f7c9651d9d7","day":"20230826"}
#
# #注意:如果发现请求参数为json格式的字符串,则在请求的使用是用json参数处理该种形式的字符串
# ret = requests.post(url=url,headers=headers,json=data).json()
#
# #从响应回来的json格式的数据中解析出:题目,选项和正确答案
# for que in ret['Data'][0]['questions']:
#     type = ret['Data'][0]['paperRule']['title'] #题型
#     title = que['content'] #题目
#     choose_list = [] #存放4个选项
#     choose_state = [] #存放4个选项是否为正确答案,1表示正确答案,0表示错误答案
#     for an in que['options']:
#         choose = an['content']
#         choose_list.append(choose)
#         isRight = an['isRight'] #每一个选项是否为正确答案,返回值1表示正确答,0表示错误答案
#         choose_state.append(isRight)
#     print(type,title,choose_list,choose_state)
#     break


#爬虫内容回顾
    #requests(重点)
        #1.如何发起get和post请求
        #2.get和post主要的请求参数:url,headers,data/params,proxies
        #3.UA检测和UA伪装
        #4.数据解析:xpath(主流),bs4
        #5.异步爬虫:线程、线程池和协程(不重要)
        #6.selenium和pyppeteer(模拟登录,验证码处理、获取动态加载数据)
    #scrapy(其次)
        #1.项目搭建
        #2.基本的请求发送和数据解析
        #3.持久化存储:基于终端指令的持久化存储,基于管道的持久化存储
        #4.手动请求发送+深度爬取(请求传参)
        #5.中间件(下载中间件),代理,cookie
        #6.crawlSpider全站数据爬取(批量获取页码链接)
        #7.分布式
        #8.增量式

posted @   游小刀  阅读(14)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
levels of contents
点击右上角即可分享
微信分享提示