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 史泰龙
  • 相关阅读:
    什么是多线程中的上下文切换?
    什么是基本表?什么是视图?
    什么是存储过程?用什么来调用?
    随意写文件命令?怎么向屏幕输出带空格的字符串,比如” hello world”?
    NULL 是什么意思 ?
    Mock 或 Stub 有什么区别?
    什么叫视图?游标是什么?
    什么是微服务中的反应性扩展?
    什么是线程组,为什么在 Java 中不推荐使用?
    Java 中用到的线程调度算法是什么?
  • 原文地址:https://www.cnblogs.com/jason-Gan/p/10676567.html
Copyright © 2011-2022 走看看