DEV Community

drake
drake

Posted on

python装饰器的一种高危使用方式

  • 爬虫失败告警的一个装饰器样例
import traceback
import os
from utils.lark_bot import sender
from config import env

def ErrorMonitor(spider_name, user=None):
    """
    捕获异常并且发送消息的装饰器,用于加在各个爬虫的解析方法上
    :param spider_name:爬虫名
    :param user: 用户名
    24个小时内单个爬虫的故障只告警一次
    """
    pod_name = os.getenv("POD_NAME")
    webhook = 'https://open.larksuite.com/open-apis/bot/v2/hook/dc183d37-05f8-436d-bbde-cfa7b163a79b'
    title = f'{spider_name}\n POD_NAME: {pod_name}\n @{user}'
    key_base = 'process:failed:filter:{}'
    from utils.redisdb import redis_cli
    redis_c = redis_cli()   
    # 捕获异常
    def catch_exception(func):
        def inner(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            # 捕获异常,发送告警
            except Exception as e:
                err_info = traceback.format_exc()
                print(err_info)
                key = key_base.format(spider_name)
                filter_status = redis_c.get(key)
                if filter_status:
                    print('过滤该告警')
                    return
                # 只有线上环境才告警
                if env == 'prod':
                    sender(err_info, url=webhook, title=title)
                    # 24个小时内单个爬虫的故障只告警一次
                    redis_c.setex(key, 24*60*60, 1)
                raise e
        return inner
    return catch_exception
Enter fullscreen mode Exit fullscreen mode
  • 该装饰器如果被大量使用会导致2个严重的问题
  • 1、redis连接数被打满
  • 2、慢加载,爬虫启动极慢
  • 为何会有这个问题呢?这得从python的装饰器特性说起

python装饰器一旦被使用,那么不管被装饰的方法有没有执行,有没有被调用;只要被定义后者别别的代码作为模块导入,那么其就会自动执行

  • 除了inner这个嵌套方法内部代码不会被执行,其他的代码都会被自动执行;
  • 如果这个装饰器大量被使用,那么redis连接这个代码就会被大量执行,但是这并不是我们想要的
  • 我们想要的是,被装饰的方法真正执行的时候才创建redis连接
  • 这就导致了程序慢加载和redis连接数过多的问题

而做出如下修改,则可以避免上述问题

  • 把redis连接移到inner方法内,并且进一步移到只有程序异常的时候才连接
import traceback
import os
from utils.lark_bot import sender
from config import env

def ErrorMonitor(spider_name, user=None):
    """
    捕获异常并且发送消息的装饰器,用于加在各个爬虫的解析方法上
    :param spider_name:爬虫名
    :param user: 用户名
    24个小时内单个爬虫的故障只告警一次
    """
    pod_name = os.getenv("POD_NAME")
    webhook = 'https://open.larksuite.com/open-apis/bot/v2/hook/dc183d37-05f8-436d-bbde-cfa7b163a79b'
    title = f'{spider_name}\n POD_NAME: {pod_name}\n @{user}'
    key_base = 'process:failed:filter:{}'

    # 捕获异常
    def catch_exception(func):
        def inner(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            # 捕获异常,发送告警
            except Exception as e:
                from utils.redisdb import redis_cli
                redis_c = redis_cli()   
                err_info = traceback.format_exc()
                print(err_info)
                key = key_base.format(spider_name)
                filter_status = redis_c.get(key)
                if filter_status:
                    print('过滤该告警')
                    return
                # 只有线上环境才告警
                if env == 'prod':
                    sender(err_info, url=webhook, title=title)
                    # 24个小时内单个爬虫的故障只告警一次
                    redis_c.setex(key, 24*60*60, 1)
                raise e
        return inner
    return catch_exception

Enter fullscreen mode Exit fullscreen mode

Top comments (0)