Exemple #1
0
    def start(self):
        """
        启动引擎
        1.引擎可以多次循环启动,有先后顺序
        2.先启动start_requests任务(必有),然后end_requests_1任务、end_requests_2任务、end_requests_3任务...以此类推
        3.end_request系列的任务不一定会有,这取决于所有加载成功的业务建造器
        4.当按顺序检索到所有业务建造器到某一点没有end_request任务,引擎不再循环启动,真正结束
        """

        end_num = 1
        try:
            self.__start_engine('start_requests')  # 先执行start_request任务
            while True:
                request_type = 'end_requests_%s' % end_num
                self.__start_engine(request_type)
                if not self.__while_run or end_num >= 10:  # 防止未知BUG导致死循环,限制10次内结束,根据实际需求再调整
                    cf.print_log('%s没有请求任务,引擎循环启动结束!' % request_type)
                    break
                end_num += 1
        except CheckUnPass as e:
            logger.exception(e)
        except Exception as e:
            logger.ding_exception(self.__f_exception, e, self.framework_key)
        cf.print_log('总共完成业务%s个!添加请求%s个,完成响应%s个,其中错误响应%s个!' %
                     (self.__builders_num, self.total_request_nums,
                      self.total_response_nums, self.total_error_nums))
Exemple #2
0
    def __error_callback(self, exception):
        """
        异常回调函数,触发此函数的都是框架级错误
        :param exception:(type=Exception) 基于Exception类型的错误类
        """

        try:
            raise exception  # 抛出异常后,才能被日志进行完整记录下来
        except Exception as e:
            logger.ding_exception(self.__f_exception, e, self.framework_key)
Exemple #3
0
    def __init__(self):
        """
        初始配置
        """

        # 框架级错误使用的key
        self.framework_key = 'framework'

        # 组件、中间件初始化
        try:
            self.__builders = dict()  # 建造器
            self.__scheduler = Scheduler()  # 调度器
            self.__downloader = Downloader()  # 下载器
            self.__pipelines = dict()  # 管道
            self.__builder_mws = dict()  # 建造器中间件
            self.__downloader_mws = dict()  # 下载器中间件
            self.__init_all()
            self.__builders_num = len(self.__builders)

            # 如果没开启任何业务,则可以直接关闭程序,防止引擎启动后卡死
            if self.__builders_num == 0:
                cf.print_log('没有获取到任何业务,引擎关闭!')
                sys.exit()

            # 请求与响应统计
            self.total_request_nums = 0
            self.total_response_nums = 0
            self.total_error_nums = 0
            self.last_finish = 0  # 用于记录上一次引擎循环完成了几个任务

            # 异步任务相关
            self.__while_run = True  # 引擎是否继续循环启动的标志
            self.__is_running = False  # 引擎是否运作的标志
            self.__pool = None  # 线程池,校验过配置的最大并发数后再创建
            self.request_mutex = Lock()  # 请求数互斥锁
            self.response_mutex = Lock()  # 响应数互斥锁
            self.error_mutex = Lock()  # 错误数互斥锁

            # 警告语模板
            total_warning = ',请规范写法,详情请查看报错提示信息、文档或相关类的说明。'
            self.__fr_warning = '业务建造器({})回调函数({})没有使用关键字yield%s' % total_warning
            self.__td_warning = '业务({})继承重写的方法没有返回正确的内置对象%s' % total_warning
            self.__ane_warning = '业务({})继承重写的方法没有接收正确数量的传参或参数名错误%s' % total_warning
            self.__cu_warning = '业务({})参数校验不通过%s' % total_warning
            self.__pe_warning = '业务({})下载器获取到的请求对象参数不正确%s' % total_warning
            self.__pue_warning = '业务({})组件里没有parse参数对应的解析函数%s' % total_warning
            self.__b_warning = '业务级错误!业务名称为{}。'
            self.__f_exception = '框架级错误!引擎原地爆炸!'

        # 初始化失败
        except Exception as e:
            logger.ding_exception('框架级错误!引擎初始化失败!', e, self.framework_key)
            sys.exit()
Exemple #4
0
    def __init_all(self):
        """
        把通过脚本传参开启的业务初始化为引擎能用的格式
        """

        # 开始加载
        cf.print_log('开始加载所有业务模块...')

        # 默认对象
        # 默认对象是完全一样功能的对象,无需每循环一个就开辟一块内存,在循环前先创建
        pipeline = Pipeline()  # 默认管道
        builder_mw = BuilderMiddleware()  # 默认建造器中间件
        downloader_mw = DownloaderMiddleware()  # 默认下载器中间件

        # 1.校验并添加业务建造器
        # 业务建造器是整个业务最重要的组件,其中一步校验没通过都将跳过该业务
        # 主参数为*,会把对应模块下所有业务都开启
        # 注意,如果为*,引擎会把该模块下所有文件(夹)名称作为code,请不要把无关文件(夹)放在对应模块下
        for type_, codes in argv.main_key_dict.items():
            module_name = p_parser[pk_main][type_][pk_module]
            code_list = os.listdir(
                '%s/%s' % (business_name,
                           module_name)) if codes == '*' else codes.split(',')

            # 1.1 校验模块
            for code in set(code_list):
                cf.print_log('加载%s模块下的%s...' % (module_name, code))
                try:
                    m = import_module('%s.%s.%s.%s' %
                                      (business_name, module_name, code, code))
                except ModuleNotFoundError:
                    logger.exception(
                        '%s为%s的模块不存在,请检查目录结构是否正确!如使用*,请不要把无关文件(夹)放在对应模块下!' %
                        (type_, code))
                    continue

                # 1.2 校验建造器规范
                try:
                    obj = getattr(m, p_parser[pk_main][type_][pk_builder])
                except AttributeError:
                    logger.exception(
                        '%s为%s的模块下没找到根据config配置的建造器类名%s,请规范写法。' %
                        (type_, code, p_parser[pk_main][type_][pk_builder]))
                    continue
                obj_name = obj.name  # 建造器名称(业务名称)
                if not issubclass(obj, Builder):  # 校验是否继承内置建造器
                    logger.exception('业务建造器(%s)没有继承内置建造器,请规范写法。' % obj_name)
                    continue

                # 1.3 校验业务名称唯一性
                if obj_name is None:
                    logger.exception(
                        '%s为%s的模块下的建造器对象%s没有name属性,name属性识别对应业务,必须有且是唯一值!' %
                        (type_, code, p_parser[pk_main][type_][pk_builder]))
                    continue
                elif obj_name in self.__builders.keys():
                    logger.exception(
                        '%s为%s的模块下的建造器对象%s的name属性(%s)与其他业务重复,name属性识别对应业务,必须有且是唯一值!'
                        % (type_, code, p_parser[pk_main][type_][pk_builder],
                           obj_name))
                    continue
                elif obj_name in self.__builders.keys() or not isinstance(
                        obj_name, str):
                    logger.exception(
                        '%s为%s的模块下的建造器对象%s的name属性不为字符串类型!请规范写法。' %
                        (type_, code, p_parser[pk_main][type_][pk_builder]))
                    continue

                # 1.4 全部校验通过,添加业务建造器
                try:
                    self.__builders[obj_name] = obj()
                except Exception as e:
                    logger.ding_exception('业务建造器(%s)初始化失败!' % obj_name, e,
                                          obj_name)
                    continue

                # 2.添加业务管道
                # 业务管道、中间件等都是附加组件,可以没有,没有则使用默认
                # 如果这些附加组件没有分别继承对应内置父类,也使用默认
                # 如果初始化失败,则使用默认
                try:
                    obj = getattr(m, p_parser[pk_main][type_][pk_pipeline])
                except AttributeError:
                    self.__pipelines[obj_name] = pipeline
                else:
                    if issubclass(obj, Pipeline):
                        try:
                            self.__pipelines[obj_name] = obj()
                        except Exception as e:
                            self.__pipelines[obj_name] = pipeline
                            logger.ding_exception(
                                '业务管道(%s)初始化失败,已更换成默认管道!' % obj_name, e,
                                obj_name)
                    else:
                        self.__pipelines[obj_name] = pipeline
                        logger.exception('业务管道(%s)没有继承内置管道,已更换成默认管道!请规范写法。' %
                                         obj_name)

                # 3.添加业务建造器中间件
                try:
                    obj = getattr(m, p_parser[pk_main][type_][pk_builder_mw])
                except AttributeError:
                    self.__builder_mws[obj_name] = builder_mw
                else:
                    if issubclass(obj, BuilderMiddleware):
                        try:
                            self.__builder_mws[obj_name] = obj()
                        except Exception as e:
                            self.__builder_mws[obj_name] = builder_mw
                            logger.ding_exception(
                                '业务建造器中间件(%s)初始化失败,已更换成默认建造器中间件!' % obj_name,
                                e, obj_name)
                    else:
                        self.__builder_mws[obj_name] = builder_mw
                        logger.exception(
                            '业务建造器中间件(%s)没有继承内置建造器中间件,已更换成默认建造器中间件!请规范写法。')

                # 4.添加业务下载器中间件
                try:
                    obj = getattr(m,
                                  p_parser[pk_main][type_][pk_downloader_mw])
                except AttributeError:
                    self.__downloader_mws[obj_name] = downloader_mw
                else:
                    if issubclass(obj, DownloaderMiddleware):
                        try:
                            self.__downloader_mws[obj_name] = obj()
                        except Exception as e:
                            self.__downloader_mws[obj_name] = downloader_mw
                            logger.ding_exception(
                                '业务下载器中间件(%s)初始化失败,已更换成默认下载器中间件!' % obj_name,
                                e, obj_name)
                    else:
                        self.__downloader_mws[obj_name] = downloader_mw
                        logger.exception(
                            '业务下载器中间件(%s)没有继承内置下载器中间件,已更换成默认下载器中间件!请规范写法。')

        # 加载完成
        cf.print_log('所有业务模块加载完成!')
Exemple #5
0
    def __execute_request_response_item(self):
        """
        处理后续请求与响应
        """

        # 3.调用调度器,获取请求对象
        # 如果获取请求对象的时候出错,就抛出框架级错误
        # 抛出错误后完成请求数+1(否则引擎会陷入死循环卡死而无法关闭),并直接结束该次任务
        try:
            request = self.__scheduler.get_request()
            if request is None:  # 如果没有获取到请求对象,直接结束
                return
            builder_name = request.builder_name  # 业务名称
            parse_name = request.parse  # 解析函数
        except Exception as e:
            self.__statistics_lock('error')
            self.__statistics_lock('response')
            logger.ding_exception(self.__f_exception, e, self.framework_key)
            return

        # 4.调用下载器,获取响应对象
        try:
            builder = self.__builders[builder_name]  # 业务建造器对象
            downloader_mw = self.__downloader_mws[builder_name]
            request = self.__check_return(self.__check_argument(
                downloader_mw.process_request, request),
                                          right_obj=Request)  # 下载器请求处理
            try:
                response = self.__downloader.get_response(request)
            except Exception as e:  # 下载过程中出错,把原生错误对象与请求对象交回给建造器处理
                result = builder.downloader_error_callback(e, request)
                if isinstance(result, Request):  # 如果返回的是一个请求对象,则再次添加去调度器
                    self.__add_request(result, builder_name)
                return
            response = self.__check_return(self.__check_argument(
                downloader_mw.process_response, response),
                                           right_obj=Response)  # 下载器响应处理
            response.meta = request.meta  # 信息(数据)互传

            # 5.调用建造器,解析响应对象
            response = self.__check_return(self.__check_argument(
                self.__builder_mws[builder_name].process_response, response),
                                           right_obj=Response)  # 建造器响应处理
            response_list = self.__check_return(
                self.__check_argument(self.__check_parse(builder, parse_name),
                                      response))

            # 6.根据响应对象类型,把该对象添加至调度器或交给管道
            for result in response_list:
                pipeline_result = None
                if isinstance(result, Request):
                    self.__add_request(result, builder_name)
                elif isinstance(result, Item):
                    pipeline_result = self.__check_argument(
                        self.__check_parse(self.__pipelines[builder_name],
                                           result.parse), result)
                    if pipeline_result is not None:  # 如果不是返回None,还需要校验是否yield生成器
                        self.__check_return(pipeline_result)
                else:
                    raise TypeDifferent([Request, Item])

                # 7.管道处理完数据对象后,根据处理结果返回的对象类型,添加请求对象至调度器或结束当次响应任务
                if pipeline_result is not None:
                    for one_request in pipeline_result:
                        self.__add_request(one_request, builder_name)

        # 8.完成一个响应,响应+1
        # 无论是正常执行还是报错,都需要完成响应,否则引擎会一直卡死
        except FaultReturn:
            self.__statistics_lock('error')
            logger.exception(self.__fr_warning.format(builder_name,
                                                      parse_name))
        except TypeDifferent:
            self.__statistics_lock('error')
            logger.exception(self.__td_warning.format(builder_name))
        except ArgumentNumError:
            self.__statistics_lock('error')
            logger.exception(self.__ane_warning.format(builder_name))
        except ParameterError:
            self.__statistics_lock('error')
            logger.exception(self.__pe_warning.format(builder_name))
        except ParseUnExist:
            self.__statistics_lock('error')
            logger.exception(self.__pue_warning.format(builder_name))
        except Exception as e:
            self.__statistics_lock('error')
            logger.ding_exception(self.__b_warning.format(builder_name), e,
                                  builder_name)
        finally:
            self.__statistics_lock('response')
Exemple #6
0
    def __start_request(self, request_type):
        """
        处理每次引擎循环启动的起始请求
        :param request_type:(type=str) 引擎循环启动的类型
        """

        # 1.调用建造器,获取请求对象列表
        no_task = True  # 该点是否没有任务的标记
        for builder_name, builder in self.__builders.items():
            a_gc = True if builder.auto_gc and request_type == 'start_requests' else False  # 通用流程标记
            try:
                try:
                    if a_gc:  # 使用通用的游戏数据采集流程
                        start_list = self.__check_return(
                            self.__check_argument(
                                builder.auto_game_collection))
                    else:  # 其余走正常流程
                        start_list = self.__check_return(
                            self.__check_argument(
                                getattr(builder, request_type)))
                except AttributeError:
                    self.__add_request(Request('test', parse='_funny'),
                                       builder_name)  # 彩蛋请求让引擎有机会关闭
                    continue
                else:
                    no_task = False
                empty = True  # 标记是否空start_list

                # 2.添加请求对象到调度器中
                for start_request in start_list:
                    empty = False
                    self.__add_request(start_request, builder_name)

                # 当编写人比较皮,所有业务建造器中的start都是一个空列表时,会出现引擎卡死的现象
                # 为了防止这种现象,如果start_list为空时,加个彩蛋请求,让引擎有机会关闭
                if empty:
                    self.__add_request(Request('test', parse='_funny'),
                                       builder_name)

            # 处理异常
            except FaultReturn:  # 校验yield不通过
                self.__add_request(Request('test', parse='_funny'),
                                   builder_name)  # 彩蛋请求,作用同上
                logger.exception(
                    self.__fr_warning.format(
                        builder_name,
                        'auto_game_collection' if a_gc else request_type))
            except TypeDifferent:  # 校验类型不通过
                self.__add_request(Request('test', parse='_funny'),
                                   builder_name)
                logger.exception(self.__td_warning.format(builder_name))
            except ArgumentNumError:  # 校验函数传参不通过
                self.__add_request(Request('test', parse='_funny'),
                                   builder_name)
                logger.exception(self.__ane_warning.format(builder_name))
            except CheckUnPass:  # start校验不通过
                self.__add_request(Request('test', parse='_funny'),
                                   builder_name)
                logger.exception(self.__cu_warning.format(builder_name))
            except Exception as e:  # 其他业务级错误
                self.__add_request(Request('test', parse='_funny'),
                                   builder_name)
                logger.ding_exception(self.__b_warning.format(builder_name), e,
                                      builder_name)

        # 3.检索所有业务建造器后,如该点没有任务,则标记不再循环启动引擎
        if no_task:
            self.__while_run = False