zoukankan      html  css  js  c++  java
  • 自己动手实现爬虫scrapy框架思路汇总

    这里先简要温习下爬虫实际操作:

    cd ~/Desktop/spider
    
    scrapy startproject lastspider # 创建爬虫工程
    cd lastspider/ # 进入工程
    
    scrapy genspider github github.cn # 创建scrapy爬虫
    
    scrapy genspider -t crawl gitee gitee.com # 创建crawlspider爬虫
    
    # github==========================================
    # github.py
    # -*- coding: utf-8 -*-
    import scrapy
    
    
    class GithubSpider(scrapy.Spider):
        name = 'github'
        allowed_domains = ['github.cn']
        start_urls = ['http://github.cn/']
    
        def parse(self, response):
            # 实现逻辑--简单以大分类-中间分类-小分类-商品列表-商品详情-商品价格为例
            # 1. 获取大分类的分组
            # 2. 获取和大分类呼应的中间分类组
            # 3. 遍历提取大分类组和中间分类组的数据,同时获取小分类组
            # 4. 遍历小分类组,提取小分类对应的列表页url,发送列表页请求,callback指向列表页处理函数
            # 5. 如果当前分类页有翻页,则提取下一页url继续发送请求,callback指向自己,以便循环读取
            # 6. 构建列表页数据处理函数,获取列表页商品的分组,遍历提取各商品列表页数据,包括url
            # 7. 根据提取的商品url,构建请求,callback指向详情页数据处理函数
            # 8. 如果当前商品列表页有翻页,则提取下一页url继续发送请求,callback指向自己,以便循环读取
            # 9. 构建商品详情页函数,提取详情页信息,如果其中有数据需要单独发送请求,则再构建请求获取该数据,在最后的请求函数中,需要yield item
            # 10. 上述最终yield 的item 通过在settings中设置spiderpiplelines,就会进入指定的spiderpiplelines中通过process_item()进行进一步数据处理,比如清洗和保存。
            pass
    
    # pipelines.py
    class GithubPipeline:
        def open_spider(self,spider): #在爬虫开启的时候执行一次
            if spider.name == 'github':
                # 准备好mongodb数据库及集合,以便接收数据
                client = MongoClient() # host='127.0.0.1',port=27017 左侧是默认值
                self.collection = client["db"]["col"]
                # self.f = open("a.txt","a")
                pass
            
        def process_item(self, item, spider):
            if spider.name == 'github':
                # 数据清洗
                item['content'] = self.process_content(item["content"])
                # 往数据库集合中添加数据
                # 如果item是通过item.py中定义的类实例对象,则不能直接存入mongodb,需要dict(item),如果是字典则可以直接存入。
                self.collection.insert_one(item)
                # pprint(item)
                return item
            
        def process_content(self,content): #处理content字段的数据
            # 对数据进行处理,常用方法-正则匹配,字符串切片,替换等待
            return content
    
        def close_spdier(self,spider): #爬虫关闭的时候执行一次
            if spider.name == 'github':
                # self.f.close()
                pass
        
        
    #================================================
    # 字典推导式
    # {keys(i):values(i) for i in list1}
    # eg:  dict1 = {i.split('=')[0]:i.split('=')[1] for i in str_list}
    
    # gitee ===========================================================
    # gitee.py 
    # -*- coding: utf-8 -*-
    import scrapy
    from scrapy.linkextractors import LinkExtractor
    from scrapy.spiders import CrawlSpider, Rule
    
    
    class GiteeSpider(CrawlSpider):
        name = 'gitee'
        allowed_domains = ['gitee.com']
        start_urls = ['http://gitee.com/']
    
        rules = (
            Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
        )
    
        def parse_item(self, response):
            item = {}
            #item['domain_id'] = response.xpath('//input[@id="sid"]/@value').get()
            #item['name'] = response.xpath('//div[@id="name"]').get()
            #item['description'] = response.xpath('//div[@id="description"]').get()
            return item
    # =============================================

    爬虫框架

    什么是框架,为什么需要开发框架

    • 框架:为了解决一类问题而开发的程序,能够提高开发效率

    • 第三方的框架不能够满足需求,在特定场景下使用,能够满足特定需求

    scrapy_plus中有哪些内置对象和核心模块

    • core

      • engine

      • scheduler

      • downloader

      • pipeline

      • spider

    • http

      • request

      • response

    • middlewares

      • downloader_middlewares

      • spider_middlewares

    • item

    scrapy_plus实现引擎的基础逻辑

      1. 调用爬虫的start_request方法,获取start_request请求对象

    • 调用爬虫中间件的process_request方法,传入start-request,返回start_request

      1. 调用调度器的add_request,传入start_request

      1. 调用调度器的get_request方法,获取请求

    • 调用下载器中间件的process_request,传入请求,返回请求

      1. 调用下载器的get_response方法,传入请求,返回response

    • 调用下载器中间件的process_response方法,传入response,返回response

    • 调用爬虫中间件的process_response方法,传入response,返回response

      1. 调用spider的parse方法,传入resposne,得到结果

    • 调用爬虫中间件的process_request方法,传入request,返回request

      1. 判断结果的类型,如果是请求对象,调用调度器的add_request,传入请求对象

      1. 否则调用管道的process_item方法,传入结果

    如何在项目文件中添加配置文件能够覆盖父类的默认配置

      1. 在框架中conf文件夹下,建立default_settings,设置默认配置

      1. 在框架的conf文件夹下,建立settings文件,导入default_settings中的配置

      1. 在项目的文件夹下,创建settings文件,设置用户配置

      1. 在框架的conf文件夹下的settings中,导入settings中的配置,会覆盖框架中的默认配置

    • url地址补全

      urllib.parse.urljoin(完整的url地址demo,不全的url地址) 返回的是补全后的url地址.

    • 提取对象身上的方法,再单独使用.

    class Test:
       def func(self,param):
      print('this is {}'.format(param))
           
    test = Test()
    ret = getattr(test,'func')
    print(ret)
    ret('python') # this is python
    • python中内置发送请求的方法

      import requests

      # 发送get请求
      req = requests.get('https://www.python.org')
      # 或者--上面的实现其实本质上是下面代码
      req = requests.request('GET', 'http://httpbin.org/get')

      # 发送post请求
      payload = dict(key1='value1', key2='value2')
      req = requests.post('http://httpbin.org/post', data=payload)

      # 或者--上面的实现其实本质上是下面代码
      req = requests.request('POST', 'http://httpbin.org/post',data=payload)

      # put,options,patch,delete方法同上

    框架开发分析:

    • 了解框架,框架思路分析

    • 框架雏形

      • http模块和item模块(传递的数据)

      • core模块(五大核心模块)

      • 框架中间件

      • 框架安装

      • 框架运行

    • 框架完善

      • 日志模块使用

        import logging
        # 日志的五个等级,等级依次递增
        # 默认是WARNING等级
        logging.DEBUG
        logging.INFO
        logging.WARNING
        logging.ERROR
        logging.CRITICAL
        # 设置日志等级
        logging.basicConfig(level=logging.INFO)
        # 使用
        logging.debug('DEBUG')
        logging.info('INFO')
        logging.warning('WARNING')
        logging.error('ERROR')
        logging.critical('CRITICAL')

        # 捕获异常信息到日志
        try:
           raise Exception('异常')
        expect Exception as e:
           logging.exception(e)
           
        # 可以对日志输出格式进行自定义
        %(name)s Logger的名字
        %(asctime)s 字符串形式的当前时间
        %(filename)s 调用日志输出函数的模块的文件名
        %(lineno)d 调用日志输出函数的语句所在的代码行
        %(levelname)s 文本形式的日志级别
        %(message)s 用户输出的消息
        # 默认日志格式
        '%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s: %(message)s'  

        利用logger封装日志模块

        # 自行封装了一个Logger类
        # 思考在框架中那些地方需要输出日志信息
        # 1. 引擎启动时--输出开始时间
        # 2. 引擎结束时--输出结束时间,以及总耗时
      • 配置文件实现

        # 思路
        # 1. 先设置个默认配置文件default_settings.py(里面可以包含用户看不到的配置)
        # 2. 再在同级下生成settings,把default_Settings中配置全部导入
        # 3. 考虑到用户使用在外部要修改配置,所以框架也会在外层生成个settings.py文件
        # 4. 如何把用户和默认的同时兼顾呢,就是把用户修改了的覆盖默认的
        # 5. 覆盖的方法就是在default_settings的同级目录下创建settings.py,先导入默认配置,再导入用户配置。参考项目的启动顺序,到外部的settings.py文件,直接 from settings import * 即可。
      • 实现同时多请求

        """
        实现思路:
        1.设置start_url为一个列表
        2.爬虫组件 遍历该列表,来构造请求,返回生成器对象
        3.引擎组件 遍历上述请求生成器,逐一添加到调度器中
        4.同时为了避免阻塞,取的过程设置为不等待取,queue.get(block=False),取不到返回none
        5.可能得到none,所以引擎中要先判断,如果没有,就直接return
        """
      • 实现同时多解析函数

        """
        多解析函数就是:实现scrapy请求中的callback
        实现思路:
        1.在request模块,添加callback,meta两个参数
        2.在response中添加meta参数,接收request中的meta
        3.在引擎中调用callback指定的函数解析响应
          即使用getattr方法,获取爬虫对象上的callback方法,然后来解析响应
          parse = getattr(self.spider,request.callback)
        """
      • 实现同时多个spider文件执行

        """
        多个spider文件,先考虑传列表
        首先引擎中修改代码,spiders参数变成列表,遍历该列表,进行各爬虫请求入调度器队列
        然后,执行 请求-响应-item,但是考虑上上述多解析函数,解析请求的方法是爬虫中的callback,该过程中,并不知道请求对应的爬虫是谁,只知道爬虫列表。 考虑构建请求对应的爬虫,即绑定,遍历爬虫文件构建请求的构成中,给请求赋值个爬虫索引属性-
        request.spider_index = self.spiders.index(spider)
        如此可知请求对应的爬虫是,spider = self.spiders[request.spider_index]
        上述方法用字典页可以实现。看效率,字典可能好些吧。
        """
      • 实现多个管道

        """
        修改引擎,让pipeline由外界传入,在爬虫传item到pipelines中处理时,process_item()传入两个参数,item和spider,以此来决定用哪个爬虫
        """
      • 实现项目中传入多个中间件

        """
        不同的中间件可以实现对请求或者是响应对象进行不同的处理,通过不同的中间件实现不同的功能,让逻辑更加清晰
        修改engine让spider_middleware,download_middleware由外界传入,因为传入的都是列表
        所以,使用中间件时,遍历使用。
        for downloader_mid in self.download_mids:
        request = downloader_mid.process_request(request)

        for spider_mid in self.spider_mids:
        start_resquest = spider_mid.process_request(start_resquest)
        """
      • 动态模块导入

        """
        通过前面的代码编写,我们已经能够完成大部分的任务,但是在main.py 中的代码非常臃肿,对应的我们可以再•settings.py 配置哪些爬虫,管道,中间件需要开启,•能够让整个代码的逻辑更加清晰

        利用importlib.import_modle能够传入模块的路径,即可即可实现根据模块的位置的字符串,导入该模块的功能.


        1.从conf.settings配置中导入 SPIDERS,PIPELINES,SPIDER_MIDDLEWARE,DOWNLOAD_MIDDLEWARE,
        上述分别都是列表
        把 self.spiders=spiders 列表替换为,从配置文件中构建的列表
        eg:‘spider.kcspider.BaiduSpider’
        eg:'pipelines.BaiduPipeline
        首先从中切割出模块和类--从右按.切割,左边为模块名,右边为类名
        调用方法
        self.spiders = _auto_import_instance(SPIDERS,isspider=True)

        def _auto_import_instance(path,isspider)
        if isspider:
        instance={}
        else:
        instanct=[]
          for p in path:
              module_name = p.rsplit('.',1)[0]
              cls_name = p.rsplit('.',1)[1]
              # 动态导入模块
              ret = importlib.import_module(module_name)
              cls = getattr(module,cls_name)
              if isspider:
                  instance[cls_name]=cls()
              else:
              instance.append(cls())
            return instance
        """
      • 请求去重

        """
        去重的是爬虫创建的request对象,scrapy_redis中使用的是hash.sha1创建文件指纹的方式。

        根据请求的url、请求方法、请求参数、请求体进行唯一标识,进行比对,由于这四个数据加到一起,内容较长,因此使用求指纹的方式来进行去重判断。

        指纹计算方法,最常用的就是md5、sha1等hash加密算法,来求指纹

        考虑去重的调用
        引擎中在添加爬虫组件生成的请求对象入调度器队列之前,会先去重再添加
          
        """
      • 使用线程/协程池

        def _callback(self, temp):
               '''执行新的请求的回调函数,实现循环'''
               if self.running is True:  # 如果还没满足退出条件,那么继续添加新任务,否则不继续添加,终止回调函数,达到退出循环的目的
                   self.pool.apply_async(self._execute_request_response_item, callback=self._callback)

           def _start_engine(self):
               '''依次调用其他组件对外提供的接口,实现整个框架的运作(驱动)'''
               self.running = True  # 启动引擎,设置状态为True
               # 向调度器添加初始请求
               self.pool.apply_async(self._start_requests)  # 使用异步

               self.pool.apply_async(self._execute_request_response_item, callback=self._callback)  # 利用回调实现循环
               
        # ===========================================
        # 协程
        # scrapy_plus/async/coroutine.py
        '''
        由于gevent的Pool的没有close方法,也没有异常回调参数
        引出需要对gevent的Pool进行一些处理,实现与线程池一样接口,实现线程和协程的无缝转换
        '''
        from gevent.pool import Pool as BasePool
        import gevent.monkey
        gevent.monkey.patch_all()    # 打补丁,替换内置的模块


        class Pool(BasePool):
           '''协程池
          使得具有close方法
          使得apply_async方法具有和线程池一样的接口
          '''
           def apply_async(self, func, args=None, kwds=None, callback=None, error_callback=None):
               return super().apply_async(func, args=args, kwds=kwds, callback=callback)

           def close(self):
               '''什么都不需要执行'''
               pass
    • 框架升级

      • 分布式爬虫,scrapy-redis

        """
        利用redis实现队列

        注意pickle模块的使用:如果将对象存入redis中,需要先将其序列化为二进制数据,取出后反序列化就可以再得到原始对象
        接口定义一致性:利用redis使用一个Queue,使其接口同python的内置队列接口一致,可以实现无缝转换
        redis -- 存储指纹和待抓取的请求对象
        mysql -- 数据存储
        """
      • 增量爬虫

        """
        增量抓取,意即针对某个站点的数据抓取,当网站的新增数据或者该站点的数据发生了变化后,自动地抓取它新增的或者变化后的数据
        设计原理:
        1.定时向目标站点发起请求
        2.关闭对目标站点请求的去重判断
        3.对抓取来的数据,入库前进行数据判断,只存储新增或改变的数据
        """
      • 断点续爬

        """
        断点续爬的效果:爬虫程序中止后,再次启动,对已发送的请求不再发起,而是直接从之前的队列中获取请求继续执行。
        意味着要实现以下两点:
        1.去重标识(历史请求的指纹)持久化存储,使得新的请求可以和之前的请求进行去重对比
        2.请求队列的持久化
        之前的分布式实现了上述两点,但可能会出现问题:
        1.如果其中部分或全部执行体被手动关闭或异常中止,那么这不未被正常执行的请求体,就会丢失,因为请求后,队列中就删除了该请求。
        解决办法:创建请求备份容器,当请求成功后,再把该请求从容器中删除,而不是队列中的,pop删除取出请求。这样的话,当队列中请求全部执行完毕后,备份容器中的请求就是丢失的请求,接下来只需要把它们重新放回请求队列中重新执行就好了。

        2.如果某个请求无论如何都无法执行成功,那么这里可能造成死循环。
        解决办法:考虑给request请求对象设置‘重试次数’属性。
        a1.每次从队列中弹出一个请求时,就把它在备份容器中对应的‘重试次数’+1
        a2. 每次从队列中弹出一个请求后,先判断它的'重试次数'是否超过配置的'最大重试次数',如果超过,就不再处理该对象,把它记录到日志中,同时从备份容器中删除该请求。否则就继续执行。
        """
    • 框架项目实战

    <人追求理想之时,便是坠入孤独之际.> By 史泰龙
  • 相关阅读:
    推荐系统 蒋凡译 第一章 引言 读书笔记
    神经网络与深度学习 邱锡鹏 第5章 卷积神经网络 读书笔记
    神经网络与深度学习 邱锡鹏 第4章 前馈神经网络 读书笔记
    神经网络与深度学习 邱锡鹏 第3章 线性模型 读书笔记
    神经网络与深度学习 邱锡鹏 第2章 机器学习概述 读书笔记
    神经网络与深度学习 邱锡鹏 第1章 绪论 作业
    神经网络与深度学习 邱锡鹏 第1章 绪论 读书笔记
    算法笔记 上机训练实战指南 第13章 专题扩展 学习笔记
    算法笔记 第13章 专题扩展 学习笔记
    算法笔记 上机训练实战指南 第11章 提高篇(5)--动态规划专题 学习笔记
  • 原文地址:https://www.cnblogs.com/jason-Gan/p/10676567.html
Copyright © 2011-2022 走看看