zoukankan      html  css  js  c++  java
  • 爬虫之Scrapy框架

    • 框架:具有很强的通用性,且封装了一些通用实现方法的项目模板
    • scrapy(异步框架):
      • 高性能的网络请求
      • 高性能的数据解析
      • 高性能的持久化存储
      • 高性能的全站数据爬取
      • 高性能的深度爬取
      • 高性能的分布式

    Scrapy环境安装

    IOS和Linux

    • pip install scrapy

    windows

          a. pip3 install wheel
    
          b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
          
          # Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl; Python是3.5版本的就选择cp35下载
    
          c. 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl
        
          # 安装失败可能是这个文件的版本导致的,即使Python版本都是对的,可以重新下载一个32位的试试
          # 还安装失败的话就下载其python版本的,总有一个能成功
    
          d. pip3 install pywin32
    
          e. pip3 install scrapy
    

    安装完成后,输入``scrapy`测试一下,出现如下图显示,即安装成功。

    Scrapy的基本使用

    创建工程

    • scrapy startprojct proNmame

      cd proNmame进入到工程目录下执行爬虫文件

    proName		# 工程名字
    	spiders		# 爬虫包(文件夹)
    		__init__.py
        __init__.py
        items.py
        middlewares.py
        pipelines.py
        settings.py		# 创建好的工程的配置文件
    scrapy.cfg		# scrapy的配置文件,不用修改
    

    创建爬虫文件

    • 创建爬虫文件是py源文件
    • scrapy genspider spiderName www.xxx.com 网址后期可以修改
      • spiders包下创建一个py文件
    # -*- coding: utf-8 -*-
    import scrapy
    
    
    class FirstSpider(scrapy.Spider):	# scrapy.Spider所有爬虫类的父类
        # name表示的爬虫文件的名称,当前爬虫文件的唯一标识
        name = 'first'
        
        # 允许的域名,通常会注释掉
        # allowed_domains = ['www.xx.com']
        
        # 起始的url列表,最开始要爬的网址列表
        # 作用:可以将内部的列表元素进行get请求的发送
        start_urls = ['http://www.sougou.com/','www.baidu.com']
    
        # 调用parse方法解析数据,方法调用的次数由start_urls列表元素个数决定的
        def parse(self, response):	# response表示一个响应对象,
            pass
    

    基本配置

    • UA伪装

    • robots协议的不遵从

      settings.py中将ROBOTSTXT_OBEY = True修改为False

    • 指定日志等级

      settings.py中添加LOG_LEVEL = 'ERROR'

    执行工程

    • scrapy crawl spiderName

    • 执行工程是不展示日志文件

      scrapy crawl spiderName --nolog

      这种方式下程序报错,不会展示;设置好日志等级后直接执行工程即可。

    数据解析

    • response.xpath('xpath表达式')

    • etree的不同之处:

      取文本/属性:返回的是一个Selector对象,文本数据是存储在该对象中

      • Selector对象[0].extract()返回字符串
      • Selector对象.extract_first()返回字符串
      • Selector对象.extract()返回列表

    常用操作

    • 如果列表只有一个元素用Selector对象.extract_first(),返回字符串
    • 如果列表有多个元素Selector对象.extract(),返回列表,列表里装的是字符串

    spiderName.py文件

    # -*- coding: utf-8 -*-
    import scrapy
    
    class DuanziSpider(scrapy.Spider):
        name = 'duanzi'
        # allowed_domains = ['www.xx.com']
        start_urls = ['https://duanziwang.com/']
    
        def parse(self, response):
            article_list = response.xpath('/html/body/section/div/div/main/article')  # 基于xpath表达式解析
            for article in article_list:
                title = article.xpath('./div[1]/h1/a/text()')[0]    # 返回一个Selector对象
                # <Selector xpath='./div[1]/h1/a/text()' data='关于健康养生、延年益寿的生活谚语_段子网收录最新段子'>
                title = article.xpath('./div[1]/h1/a/text()')[0].extract()    # 返回字符串
                # 关于健康养生、延年益寿的生活谚语_段子网收录最新段子
                title = article.xpath('./div[1]/h1/a/text()').extract_first()   # 返回字符串
                # 关于健康养生、延年益寿的生活谚语_段子网收录最新段子
                title = article.xpath('./div[1]/h1/a/text()').extract()     # 返回列表
                # ['关于健康养生、延年益寿的生活谚语_段子网收录最新段子']
                print(title)
                break
    

    持久化存储

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

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

      指定后缀:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle',通常用csv

      指令scrapy crawl spiderName -o filePath

    案例:将文本数据持久化存储

    # -*- coding: utf-8 -*-
    import scrapy
    
    class DuanziSpider(scrapy.Spider):
        name = 'duanzi'
        # allowed_domains = ['www.xx.com']
        start_urls = ['https://duanziwang.com/']
    
        # 基于终端指令的持久化存储
        def parse(self, response):
            article_list = response.xpath('/html/body/section/div/div/main/article')  # 基于xpath表达式解析
            all_data = []
            for article in article_list:
                title = article.xpath('./div[1]/h1/a/text()').extract_first()
                content = article.xpath('./div[2]/p//text()').extract()
                content = ''.join(content)
                dic = {
                    "title": title,
                    "content": content
                }
                all_data.append(dic)
            return all_data
    # 终端指令
    # scrapy crawl spiderName -o duanzi.csv
    

    基于管道的持久化存储

    scrapy建议使用管道持久化存储

    实现流程

    • 数据解析(spiderName .py

    • 实例化item类型对象(items.py

      items.py的item类中定义相关的属性

      fieldNmae = scrapy.Field()

    • 将解析的数据存储封装到item类型的对象中(spiderName .py

      item['fileName'] = value 给item对象的fieldNmae属性赋值

    • 将item对象提交给(spiderName .py

      yield item 将item提交给优先级最高的管道

    • 在管道中接收item,可以将item中存储的数据进行任意形式的持久化存储(pipelines.py

      process_item():负责接收item对象且对其进行持久化存储

    • 在配置文件settings.py中开启管道机制

      找到如下代码,取消注释

      ITEM_PIPELINES = {
          # 300表示的是优先级,数值越小,优先级越高
         'duanziPro.pipelines.DuanziproPipeline': 300,
      }
      

    案例:将文本数据持久化存储

    按上述在settings.py找到管道代码,取消注释。

    spiderName .py

    # -*- coding: utf-8 -*-
    import scrapy
    from duanziPro.items import DuanziproItem
    
    class DuanziSpider(scrapy.Spider):
        name = 'duanzi'
        # allowed_domains = ['www.xx.com']
        start_urls = ['https://duanziwang.com/']
        # 基于管道的持久化存储
        def parse(self, response):
            article_list = response.xpath('/html/body/section/div/div/main/article')  # 基于xpath表达式解析
            for article in article_list:
                title = article.xpath('./div[1]/h1/a/text()').extract_first()
                content = article.xpath('./div[2]/pre/code//text()').extract()
                content = ''.join(content)
                print(content)
                # 实例化item对象
                item = DuanziproItem()
                # 通过中括号的形式访问属性给其赋值
                item['title'] = title
                item['content'] = content
                # 向管道提交item
                yield item
    

    items.py

    import scrapy
    
    class DuanziproItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        # 使用固有属性定义了两个属性
        # Field是一个万能数据类型
        title = scrapy.Field()
        content = scrapy.Field()
    

    pipelines.py

    class DuanziproPipeline(object):
        # 重写父类的该方法:该方法只会在爬虫开始的时候执行一次
        fp = None
        
        # 打开文件
        def open_spider(self, spider):
            print('open spider')
            self.fp = open('./duanzi.txt', 'w', encoding='utf-8')
    
        # 关闭文件
        def close_spider(self, spider):
            print('close spider')
            self.fp.close()
    
        # 接收爬虫文件返回item对象,process_item方法每调用一次可接收一个item对象
        # item参数:接收到的某一个item对象
        def process_item(self, item, spider):
            # 取值
            title = item['title']
            content = item['content']
            self.fp.write(title + ":" + content + "
    ")
            return item
    

    管道存储细节处理

    • 管道文件中的管道类表示的是什么?

      一个管道类对应的就是一种存储形式(文本文件,数据库)

      如果想要实现数据备份,则需要使用多个管道类(多种存储形式:MySQL,Redis)

    • process_item中的 retutn item

      将item传递给下一个即将被执行(按照配置文件中ITEM_PIPELINES得权重排序)的管道类

    存储到MySQL

    pipelines.py中添加如下代码

    import pymysql
    
    class MysqlPipeline(object):
        conn = None
        cursor = None
    
        def open_spider(self, spider):
            self.conn = pymysql.Connect(host='127.0.0.1', port=3306, user='root', password='123', db='spider',
                                        charset='utf8')
    
        def process_item(self, item, spider):
            # 取值
            title = item['title']
            content = item['content']
            self.cursor = self.conn.cursor()
            # sql语句
            sql = 'insert into duanzi values ("%s","%s")' % (title, content)
            try:
                self.cursor.execute(sql)
                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()
    

    settings.py中将MysqlPipeline类注册到ITEM_PIPELINES中

    ITEM_PIPELINES = {
        # 300表示的是优先级,数值越小,优先级越高
        'duanziPro.pipelines.DuanziproPipeline': 300,
        'duanziPro.pipelines.MysqlPipeline': 301,
    }
    

    存储到Redis

    • 因为redis有的版本不支持存储字典,下载2.10.6版本

      pip install redis==2.10.6

    pipelines.py中添加如下代码

    from redis import Redis
    
    
    class RedisPipeline(object):
        conn = None
    
        def open_spider(self, spider):
            self.conn = Redis(host='127.0.0.1', port=6379, password='yourpassword')
    
        def process_item(self, item, spider):
            self.conn.lpush('duanziList', item)
            # 报错:因为redis有的版本不支持存储字典,pip install redis==2.10.6
    

    settings.py中将RedisPipeline类注册到ITEM_PIPELINES中

    ITEM_PIPELINES = {
        # 300表示的是优先级,数值越小,优先级越高
        'duanziPro.pipelines.DuanziproPipeline': 300,
        'duanziPro.pipelines.RedisPipeline': 301,
    }
    

    手动发送请求

    • 可以在start_urls这个列表中添加url,但是比较繁琐

    • get请求发送

      yield scrapy.Request(url,callback)

      • url:指定好请求的url
      • callback:callback指定的回调函数一定会被执行(数据解析)
    • post请求发送

      yield scrapy.FormRequest(url,callback,formdata)

      • formdata存放请求参数,字典类型
    • 父类中start_requests请求发送的原理

    # 简单模拟父类的方法,主要看yield
    def start_requests(self):
        for url in self.start_urls:
            # 发起get请求
            yield scrapy.Request(url=url,callback=self.parse)
            # 发起post请求,formdata存放请求参数
            yield scrapy.FormRequest(url=url,callback=self.parse,formdata={})
    

    代码实现

    • 主要是在spiderName .py中使用递归方法,且明确递归结束的条件;

      使用父类yield实现全站爬取

    # -*- coding: utf-8 -*-
    import scrapy
    from duanziPro.items import DuanziproItem
    
    
    class DuanziSpider(scrapy.Spider):
        name = 'duanzi'
        # allowed_domains = ['www.xx.com']
        start_urls = ['https://duanziwang.com/']
        
        # 手动请求的发送,对其他页码的数据进行请求操作
        # 定义通用url模板
        url = "https://duanziwang.com/page/%d/"
        pageNum = 2
    
        def parse(self, response):
            article_list = response.xpath('/html/body/section/div/div/main/article')  # 基于xpath表达式解析
            all_data = []
            for article in article_list:
                title = article.xpath('./div[1]/h1/a/text()').extract_first()
                content = article.xpath('./div[2]/pre/code//text()').extract()
                content = ''.join(content)
                # 实例化item对象
                item = DuanziproItem()
                # 通过中括号的形式访问属性给其赋值
                item['title'] = title
                item['content'] = content
                # 向管道提交item
                yield item
            if self.pageNum < 5:
                new_url = format(self.url%self.pageNum)
                self.pageNum += 1
                # 递归实现全站数据爬取,callback指定解析的方法
                yield scrapy.Request(url=new_url, callback=self.parse)
    
    • pipelines.py中实现数据持久化存储
    class DuanziproPipeline(object):
        # 重写父类的该方法:该方法只会在爬虫开始的时候执行一次
        fp = None
    
        def open_spider(self, spider):
            print('open spider')
            self.fp = open('./duanzi.txt', 'w', encoding='utf-8')
    
        # 关闭fp
        def close_spider(self, spider):
            print('close spider')
            self.fp.close()
    
        # 接收爬虫文件返回item对象,process_item方法每调用一次可接收一个item对象
        # item参数:接收到的某一个item对象
        def process_item(self, item, spider):
            # 取值
            title = item['title']
            content = item['content']
            self.fp.write(title + ":" + content + "
    ")
            # 将item转交给下一个即将被执行的管道类
            return item
    
    • settings.py中开启管道类
    ITEM_PIPELINES = {
        # 300表示的是优先级,数值越小,优先级越高
        'duanziPro.pipelines.DuanziproPipeline': 300,
    }
    

    yield在scrapy中的使用

    • 向管道中提交item对象

      yield item

    • 手动请求发送

      yield scrapy.Request(url,callback)

    五大核心组件

    • 引擎(Scrapy Engine)

      处理整个系统的数据流,触发事物(框架核心)。

    • 调度器(Scheduer)

      用来接收引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回。

    • 下载器(Downloader)

      用于下载网页内容,并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效模型上的)。

    • 爬虫(Spiders)

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

    • 管道(item Pipeline)

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

    五大核心组件的工作流程


    当执行爬虫文件时,5大核心组件就在工作了

    首先执行爬虫文件spider,spider的作用是
    (1)解析(2)发请求,原始的url存储在于spider中
    1:当spider执行的时候,首先对起始的url发送请求,将起始url封装成请求对象
    2:将请求对象传递给引擎
    3:引擎将请求对象传递给调度器(内部含有队列和过滤器两个机制),调度器将请求存储在队列(先进先出)中
    4:调度器从队列中调度出url的相应对象再将请求传递给引擎
    5:引擎将请求对象通过下载中间件发送给下载器
    6:下载器拿到请求到互联网上去下载
    7:互联网将下载好的数据封装到响应对象给到下载器
    8:下载器将响应对象通过下载中间件发送给引擎
    9:引擎将封装了数据的响应对象回传给spider类parse方法中的response对象
    10:spider中的parse方法被调用,response就有了响应值
    11:在spider的parse方法中进行解析代码的编写;
    (1)会解析出另外一批url,(2)会解析出相关的文本数据
    12: 将解析拿到的数据封装到item中
    13:item将封装的文本数据提交给引擎
    14:引擎将数据提交给管道进行持久化存储(一次完整的请求数据)
    15:如果parder方法中解析到的另外一批url想继续提交可以继续手动进行发请求
    16:spider将这批请求对象封装提交给引擎
    17:引擎将这批请求对象发配给调度器
    16:这批url通过调度器中过滤器过滤掉重复的url存储在调度器的队列中
    17:调度器再将这批请求对象进行请求的调度发送给引擎

    引擎作用:
    1:处理流数据 2:触发事物
    引擎根据相互的数据流做判断,根据拿到的流数据进行下一步组件中方法的调用

    下载中间件: 位于引擎和下载器之间,可以拦截请求和响应对象;拦截到请求和响应对象后可以
    篡改页面内容和请求和响应头信息。
    爬虫中间件:位于spider和引擎之间,也可以拦截请求和响应对象,不常用。

    请求传参

    • 作用

      实现深度爬取。

    • 深度爬取

      爬取的数据不在同一张页面中。

    • 在进行手动发送请求的时候,可以将一个meta字典传递给callback指定的回调函数

      • yield scrapy.Request(url,callback,meta={})

      • 在回调函数中接收meta

        response.meta['key'] 将meta字典中key对应的value值取出

    案例:电影名字和简介爬取

    spiderName.py

    # -*- coding: utf-8 -*-
    import scrapy
    from moviePro.items import MovieproItem
    
    class MovieSpider(scrapy.Spider):
        name = 'movie'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['https://www.4567kan.com/frim/index1.html']
        # 通用url
        url = 'https://www.4567kan.com/frim/index1-%d.html'
        pagNum = 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:
                mv_name = li.xpath('./div/a/@title').extract_first()
                item = MovieproItem()
                item['name'] = mv_name
                detail_url = "https://www.4567kan.com/" + li.xpath('./div/a/@href').extract_first()
                yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})
                # meta 就是一个字典,可以将字典传给callback指定的回调函数,实现请求传参
            # 全站信息的爬取,测试前4页
            if self.pagNum < 5:
                new_url = format(self.url % self.pagNum)
                self.pagNum += 1
                yield scrapy.Request(url=new_url,callback=self.parse)
        # 自定义解析方法,解析详情页电影简介
        def parse_detail(self, response):
            desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[3]//text()').extract_first()
            # 接收请求传参传递过来的item
            item = response.meta['item']
            item['desc'] = desc
            yield item
    

    中间件

    爬虫中间件

    位于spider和引擎之间,也可以拦截请求和响应对象,不常用。

    下载中间件(推荐)

    位于引擎和下载器之间,可以拦截请求和响应对象;拦截到请求和响应对象后可以
    篡改页面内容和请求和响应头信息。

    作用:拦截所有请求和响应

    为什么要拦截请求?

    • UA伪装(篡改请求头信息)

      process_request()方法中,

      request.headers['User-Agent'] ="请求头信息"

    • 设置代理

      process_exception()方法中,

      request.meta['proxy'] = 'https://ip:prot'

      return request

    工程项目中middlewares.py就是中间件。

    from scrapy import signals
    import random
    
    # UA池
    user_agent_list = [
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
        "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
        "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
        "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
        "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
        "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
        "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
        "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
        "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
    ]
    PROXY_HTTP = ["ip:port"]
    PROXY_HTTPS = ["ip:port"]
    
    # 下载中间件
    class MiddleproDownloaderMiddleware(object):
    
        def process_request(self, request, spider):
            """
            拦截正常请求
            :param request: 拦截到的正常请求
            :param spider: 爬虫类实例化的对象
            :return: 
            """
            # UA伪装
            request.headers['User-Agent'] = random.choice(user_agent_list)
            return None
    
        def process_response(self, request, response, spider):
            """
            process_response函数:拦截所有的响应
            :param request: 响应对应的请求
            :param response: 拦截到的响应
            :param spider: 爬虫类实例化的对象
            :return: 返回处理后的响应
            """
            return response
    
        def process_exception(self, request, exception, spider):
            """
            拦截发生异常的请求;对异常请求进行修正,让其变成正常请求
            :param request: 拦截到的发生异常的请求对象
            :param exception: 拦截到的异常信息
            :param spider: 爬虫类实例化的对象
            :return: 将修正后的请求对象进行重新发送
            """
            # 代理操作
            if request.url.split(":")[0] == 'http':
                request.meta['proxy'] = "http:{}".format(random.choice(PROXY_HTTP))
            else:
                request.meta['proxy'] = "https:{}".format(random.choice(PROXY_HTTPS))
            return request  # 将修正后的请求对象进行重新发送
    

    为什么要拦截响应?

    • 篡改响应数据

    案例:网易新闻数据爬取

    • 实现流程:

      1,解析出5个板块对应的url

      2,对5个板块的url发起请求

      3,获取板块的页面源码数据

      ​ 问题:数据为动态加载,源码数据中没有新闻标题和详情页的url

      ​ 解决:将响应数据进行篡改,改成包含动态加载的数据

    • selenium帮助我们捕获到包含了动态加载的响应数据

      selenium在scrapy中的使用

      • 实例化一个浏览器对象(爬虫文件中)
      • 在中间件process_response中进行selenium后续的操作
      • 在爬虫文件的爬虫类冲重写一个closed(self,spider),关闭浏览器

    在settings.py基本设置一下,打开下载中间件

    spiderNmae.py

    # -*- coding: utf-8 -*-
    import scrapy
    from selenium import webdriver
    from wangyiPro.items import WangyiproItem
    
    class WangyiSpider(scrapy.Spider):
        name = 'wangyi'
        # allowed_domains = ['www.xx.com']
        start_urls = ['https://news.163.com/']
        # 5个板块页面的url
        model_urls = []
        # 实例化浏览器对象
        bro = webdriver.Chrome(executable_path=r'D:Reptilejupyteronceagain爬虫Scrap框架chromedriver.exe')
    
        # 数据解析,解析5个板块对应页面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')
            index = [3, 4, 6, 7, 8]
            for i in index:
                model_url = li_list[i].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):
            div_list = response.xpath('/html/body/div[1]/div[3]/div[4]/div[1]/div/div/ul/li/div/div')
            for div in div_list:
                title = div.xpath('./a/img/@alt').extract_first()
                new_detail_url = div.xpath('./a/@href').extract_first()
                if new_detail_url:
                    item = WangyiproItem()
                    item['title'] = title
                    # 对新闻的详情页发请求,解析出新闻的内容
                    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="endText"]//text()').extract()
            content = ''.join(content)
            # print(content)
            item['content'] = content
            yield item
    
        # 整个程序结束时调用一次:父类的方法
        def closed(self, spider):
            self.bro.quit()
    

    middlewares.py

    from time import sleep
    
    
    class WangyiproDownloaderMiddleware(object):
    
        # 拦截响应,篡改指定响应对象的响应数据
        def process_response(self, request, response, spider):
            # 获取5个板块对应的url
            model_urls = spider.model_urls
            bro = spider.bro
            if request.url in model_urls:  # 成立之后定位到的response就是某一个板块对应的response
                # 指定响应数据的篡改
                # 参数body就是响应数据
                bro.get(request.url)
                sleep(1)
                page_text = bro.page_source  # 作为新的响应数据,包含动态加载数据源
                return HtmlResponse(url=request.url, body=page_text, encoding='utf-8', request=request)
            else:
                return response
    

    items.py

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

    Scrapy爬大文本数据

    大文本数据就是量级大的二进制数据,如图片,压缩包,音频,视频...

    • 爬虫文件中将二进制资源的url进行爬取和解析,将其存储到item中向管道提交

    • 在管道文件中指定对应的管道类

      父类:from scrapy.pipelines.images import ImagesPipeline

      配置文件中进行如下操作

      # 自动创建一个指定的文件夹
      IMAGES_STORE = './imgLib'
      

    案例:校花图片的爬取

    自定义一个关于ImagesPipeline该父类的管道类,在pipelines.py中重写如下三个方法

    from scrapy.pipelines.images import ImagesPipeline
    import scrapy
    
    
    class ImgproPipeline(ImagesPipeline):
        # 发起请求
        def get_media_requests(self, item, info):
            imgSrc = item['imgSrc']
            # 请求传参,将meta字典传递给了file_path这个方法
            yield scrapy.Request(url=imgSrc, meta={'item': item})
    
        # 定制get_media_request请求到数据持久化存储的路径(文件夹路径+文件名称)
        def file_path(self, request, response=None, info=None):
            # 通过request.meta接收请求传参传递过来的meta字典
            imgName = request.meta['item']['imgName']
            return imgName
        	# 如果配置文件中没指定文件夹
            # return '文件夹/%s.jpg' % (image_guid)
    
        def item_completed(self, results, item, info):
            return item
    

    CrawlSpider

    全站数据爬取

    • CrawlSpider就是Spider的一个子类

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

      scrapy genspider -t crawl spiderName www.xxx.com

    • LinkExtractor(allow=r'正则表达式'):链接提取器

      • 作用:可以根据指定的指定的规则(allow)进行链接提取
    • Rule(link,callback,follow=True):规则解析器

      link:链接提取

      callback:回调函数,字符串反射调用函数,解析数据

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

      • 作用:

        1.将链接提取器提取到的链接进行请求发送(get)请求发送

        2.请求到的数据根据指定的规则进行数据解析

    • 深度爬取

      手动发送请求解析数据,请求传参和LinkExtractor,Rule一起使用实现深度爬取

    Rule(LinkExtractor(allow=r'正则表达式提取指定url'),callback='函数名',follow=True(提取全站页码url)/Flase(提取当前叶页码url))

    使用链接提取器提取详情页的url实现深度爬取

    案例:阳光问政

    spiderName.py中代码

    • 问题:实例化在持久化存储后无法实现数据一一对应的汇总
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    # 实例化了两个item对象
    from sunPro.items import TitleItem, ContentItem
    
    class SunSpider(CrawlSpider):
        name = 'sun'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']
    
        # 根据正则提取全站页码
        link = LinkExtractor(allow=r'id=1&page=d+')
        # 提取标题对应的详情页链接
        link_detail = LinkExtractor(allow=r'politics/index?id=d+')
        rules = (
            Rule(link, callback='parse_item', follow=True),
            Rule(link_detail, callback='parse_detail', 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()
                detail_url =
                item = TitleItem()
                item['title'] = title
                yield item
    
        def parse_detail(self, response):
            content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first()
            item = ContentItem()
            item['content'] = content
            yield item
    
    • 解决上述问题

      手动发送请求解析详情页的内容,请求传参,传给一个item

    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    from sunPro.items import SunproItem
    
    
    class SunSpider(CrawlSpider):
        name = 'sun'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['http://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),
        )
    
        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 = "http://wz.sun0769.com/" + li.xpath('./span[3]/a/@href').extract_first()
                item = SunproItem()
                item['title'] = title
                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]/pre/text()').extract_first()
            item = response.meta['item']
            item['content'] = content
            yield item
    

    分布式

    实际应用中很少,一般都是面试时问道,主要是文原理。

    • 概念:搭建一个分布式机群,共同执行一组代码,联合对同一个资源的数据进行分布且联合爬取

    • 实现方式:

      简称:scrapy + redis

      全称:Scrapy框架 + scrapy-redis组件

    • 原生的scrapy框架无法实现分布式

      原生scrapy的调度器和管道无法共享

    • scrapy-redis组件的作用

      可以给原生的scrapy框架,提供可以被共享的管道和调度器

    • 环境安装

      pip install scrapy-redis

    • 实现流程

      修改爬虫文件中爬虫类对应的操作

      • 导包:from scrapy_redis.spiders import RedisCrawlSpider

        CrawlSpider     导入 RedisCrawlSpider
        Spider			导入 RedisSpider
        
      • 爬虫类的父类修改成RedisCrawlSpider

      • 将start_urls删除,添加一个redis_key='可以被共享调度器队列的名称'

      • 进行常规的请求和解析和向管道提交item操作即可

      settings.py进行配置

        • 配置管道
        ITEM_PIPELINES = {
            # scrapy组件中有管道,是基于redis的,所以目前只能用redis存储
            'scrapy_redis.pipelines.RedisPipeline':400,
        }
        
        • 调度器的配置
        # 增加了一个去重容器类的配置,作用使用Redis的set集合来存储请求的指纹数据,从而实现请求去重的持久化
        DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
        # 使用scrapy-redis组件自己的调度器
        SCHEDULER = "scraoy_redis.scheduler.Scheduler"
        # 配置调度器是否要持久化,也就是爬虫结束,是否清空Redis中请求对列和去重的set,true表示持久化存储,不清空
        SCHEDULER_PERSIST = True
        
        • 对Redis进行配置
        REDIS_HOST = 'redis服务的ip地址'
        REDIS_PORT = 6379
        

      对redis配置文件进行修改

      • 56行:# bind 127.0.0.1
      • 75行:protected-mode no

      启动Redis服务和客户端

      • 携带配置文件启动redis,在redis安装目录下运行cmd

        redis-server.exe redis.windows.conf

      • 启动客户端

        redis-cli

      启动程序

      • 在终端中进入到爬虫文件对应的目录中

        scrapy runspider spiderName.py

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

        redisl-cli

        lpush redis_key的属性值(被共享的调度器队列名称) 起始的网址

    示例代码

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    from scrapy_redis.spiders import RedisCrawlSpider
    from fbsPro.items import FbsproItem
    
    
    class FbsSpider(RedisCrawlSpider):
        name = 'fbs'
        # allowed_domains = ['www.xxx.com']
        # start_urls = ['http://www.xxx.com/']
        redis_key = 'sunQueue'  # 可被共享的调度器队列的名称
        rules = (
            Rule(LinkExtractor(allow=r'id=1&page=d+'), callback='parse_item', follow=True),
        )
    
        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
    

    增量式

    • 监测网站数据更新情况,以便于爬取到最新更新的网站

    • 核心:去重

    • 记录仪:

      特性:永久性存储(redis中的set)

      爬取过的数据对应的url

      • 可以以明文的形式存储(url数据长度较短)

      • 记录的数据对其生成一个数据指纹(url数据长度比较长)

        数据指纹就是该组数据的唯一标识

    示例代码

    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    from redis import Redis
    from zlsPro.items import ZlsproItem
    
    
    class ZlsSpider(CrawlSpider):
        name = 'zls'
        # allowed_domains = ['www.xxx.com']
        start_urls = ['http://wz.sun0769.com/political/index/politicsNewest?id=1&page=1']
        # 创建redis的链接对象
        conn = Redis(host="127.0.0.1", port=6379, password='redis的密码,没有就不写')
        rules = (
            Rule(LinkExtractor(allow=r'id=1&page=d+'), callback='parse_item', follow=False),
        )
    
        def parse_item(self, response):
            # 解析出标题和详情页的url(详情页的url需要存储到记录表中)
            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 = "http://wz.sun0769.com/" + li.xpath('./span[3]/a/@href').extract_first()
                item = ZlsproItem()
                item['title'] = title
                # 将进行请求发送的详情页的url去记录表中进行查看
                ex = self.conn.sadd('urls', detail_url)
                if ex == 1:
                    print('数据已更新,可爬取')
                    yield scrapy.Request(url=detail_url, callback=self.parse_deatil, meta={'item': item})
                else:
                    print('数据未更新,不可爬')
    
        def parse_deatil(self, response):
            item = response.meta['item']
            content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first()
            item['content'] = content
            yield item
    
  • 相关阅读:
    PIL 和 pythonopencv 从内存字节码中读取图片并转为np.array格式
    【转载】 什么是元类
    【转载】 Py之cupy:cupy的简介、安装、使用方法之详细攻略
    【转载】 vscode如何在最新版本中配置c/c++语言环境中的launch.json和tasks.json?
    【转载】 Ubuntu下使用VSCode的launch.json及tasks.json编写
    Javascript高级程序设计第二版第六章面向对象程序设计(ObjectOriented Programming)简称OOP编程笔记
    Javascript高级程序设计第二版第五章引用类型笔记
    css权重简单之谈
    编辑神器VIM下安装zencoding
    显示层3s后隐藏
  • 原文地址:https://www.cnblogs.com/Golanguage/p/12571714.html
Copyright © 2011-2022 走看看