Esempio n. 1
0
class Engine(object):
    def __init__(self):
        self.spider = Spider()
        self.scheduler = Scheduler()
        self.pipeline = Pipeline()
        self.downloader = Downloader()

    def start(self):
        """启动整个引擎,主要调用逻辑代码写在_start_engine中"""
        self._start_engine()

    def _start_engine(self):
        '''实现个业务之间对接'''
        # 爬虫模块发出初始化请求
        start_request = self.spider.start_request()
        # 把初始化请求添加给调度器
        self.scheduler.add_request(start_request)
        # 从调度器获取请求对象
        request = self.scheduler.get_request()
        # 调用下载器发送请求
        response = self.downloader.get_response(request)
        # 调用爬虫处理响应方法,处理响应,得到结果
        result = self.spider.parse(response)
        if isinstance(result, Request):
            self.scheduler.add_request(result)
        else:
            self.pipeline.process_item(result)
Esempio n. 2
0
class Engine():
    def __init__(self):
        self.spider = Spider()
        self.scheduler = Scheduler()
        self.downloader = Downloader()
        self.pipeline = Pipeline()

        self.spider_mid = SpiderMiddleware()
        self.downloader_mid = DownloaderMiddleware()

    def start(self):
        # 被调用执行引擎逻辑的入口函数
        self._start_engine()

    def _start_engine(self):
        # 框架运行的逻辑

        # 1. 爬虫模块发出初始请求
        start_request = self.spider.start_request()

        # 利用爬虫中间件预处理请求对象
        start_request = self.spider_mid.process_request(start_request)

        # 2. 把初始请求添加给调度器
        self.scheduler.add_request(start_request)

        # 3. 从调度器获取请求对象
        request = self.scheduler.get_request()

        # 利用下载器中间件预处理请求对象
        request = self.downloader_mid.process_request(request)

        # 4. 利用下载器发起请求
        response = self.downloader.get_response(request)

        # 利用下载器中间件预处理响应对象
        response = self.downloader_mid.process_response(response)

        # 利用爬虫中间件预处理响应对象
        response = self.spider_mid.process_response(response)

        # 5. 利用爬虫的解析响应的方法,处理响应,得到结果
        result = self.spider.parse(response)

        # 6. 判断结果对象
        if isinstance(result, Request):

            # 利用爬虫中间件预处理请求对象
            result = self.spider_mid.process_request(result)

            # 6.1 如果是请求对象,那么就再交给调度器
            self.scheduler.add_request(result)

        else:

            # 6.2 否则,就交给管道处理
            self.pipeline.process_item(result)
Esempio n. 3
0
class Engine(object):
    def __init__(self):
        self.spider = Spider()
        self.scheduler = Scheduler()
        self.pipeline = Pipeline()
        self.downloader = Downloader()
        self.spider_mid = SpiderMiddleware()  # 初始化爬虫中间件对象
        self.downloader_mid = DownloaderMiddleware()  # 初始化下载器中间件对象

    def start(self):
        """启动整个引擎,主要调用逻辑代码写在_start_engine中"""
        start = datetime.now()  # 获取当前时间
        logger.info('开始运行时间:%s' % start)
        self._start_engine()
        stop = datetime.now()
        logger.info('运行结束时间:%s' % stop)
        # 运行总耗时时间
        logger.info('耗时: %.2f' % (stop - start).total_seconds())

    def _start_engine(self):
        '''实现个业务之间对接'''
        # 爬虫模块发出初始化请求
        start_request = self.spider.start_request()

        # 爬虫中间件
        start_request = self.spider_mid.process_request(start_request)
        # 把初始化请求添加给调度器
        self.scheduler.add_request(start_request)
        # 从调度器获取请求对象
        request = self.scheduler.get_request()

        # 利用下载中间件预处理请求对象
        request = self.downloader_mid.process_request(request)
        # 调用下载器发送请求
        response = self.downloader.get_response(request)

        # 下载中间件预处理对象
        response = self.downloader_mid.process_response(response)
        # 调用爬虫处理响应方法,处理响应,得到结果
        result = self.spider.parse(response)
        if isinstance(result, Request):
            result = self.spider_mid.process_request(result)
            self.scheduler.add_request(result)
        else:
            self.pipeline.process_item(result)
Esempio n. 4
0
class Engine():
    def __init__(self, spiders):
        self.spiders = spiders
        self.scheduler = Scheduler()
        self.downloader = Downloader()
        self.pipeline = Pipeline()

        self.spider_mid = SpiderMiddleware()
        self.downloader_mid = DownloaderMiddleware()

        self.total_response_nums = 0
        self.total_request_nums = 0

    def start(self):
        # 被调用执行引擎逻辑的入口函数

        start = datetime.now()  # 起始时间
        logger.info("开始运行时间:%s" % start)  # 使用日志记录起始运行时间
        self._start_engine()
        stop = datetime.now()  # 结束时间
        logger.info("结束运行时间:%s" % stop)  # 使用日志记录结束运行时间
        logger.info("耗时:%.2f" % (stop - start).total_seconds())  # 使用日志记录运行耗时
        logger.info("总的请求数量:{}".format(self.total_request_nums))
        logger.info("总的响应数量:{}".format(self.total_response_nums))

    def _start_requests(self):
        """把爬虫中所有起始的url构造request并添加到请求队列中"""
        for spider_name, spider in self.spiders.items():
            for start_request in spider.start_requests():

                # 1. 爬虫模块发出初始请求
                # 利用爬虫中间件预处理请求对象
                start_request = self.spider_mid.process_request(start_request)
                # 给request对象增加spider_name的属性
                start_request.spider_name = spider_name
                # 2. 把初始请求添加给调度器
                self.scheduler.add_request(start_request)
                # 总请求数 + 1
                self.total_request_nums += 1

    def _execute_request_response_item(self):
        """队列中取出一个request,直到处理完毕"""
        # 3. 从调度器获取请求对象
        request = self.scheduler.get_request()
        # 判断队列是否取空
        if request is None:
            return  # 提前终止
        # 利用下载器中间件预处理请求对象
        request = self.downloader_mid.process_request(request)
        # 4. 利用下载器发起请求
        response = self.downloader.get_response(request)
        # 传递meta
        response.meta = request.meta
        # 利用下载器中间件预处理响应对象
        response = self.downloader_mid.process_response(response)
        # 利用爬虫中间件预处理响应对象
        response = self.spider_mid.process_response(response)
        # 根据爬虫名获取爬虫类对象
        spider = self.spiders[request.spider_name]
        # 5. 利用爬虫的解析响应的方法,处理响应,得到结果
        # request.parse指定的解析函数 = getattr(爬虫类对象, 指定的解析函数的字符串)
        parse_func = getattr(spider, request.parse)
        results = parse_func(response)
        for result in results:
            # 6. 判断结果对象
            if isinstance(result, Request):
                # 利用爬虫中间件预处理请求对象
                result = self.spider_mid.process_request(result)
                # 给request对象增加spider_name的属性
                result.spider_name = request.spider_name
                # 6.1 如果是请求对象,那么就再交给调度器
                self.scheduler.add_request(result)
                # 总请求数 + 1
                self.total_request_nums += 1
            else:
                # 6.2 否则,就交给管道处理
                self.pipeline.process_item(result)
        # 总响应数 +1
        self.total_response_nums += 1

    def _start_engine(self):
        # 框架运行的逻辑

        self._start_requests()  # 把所有初始request放入队列

        while True:
            time.sleep(0.1)
            self._execute_request_response_item()  # 处理一个从队列中取出的request

            # 程序退出的条件
            if self.scheduler.q.empty():
                break  # 判断队列为空
Esempio n. 5
0
class Engine():
    def __init__(self):
        self.spider = Spider()
        self.scheduler = Scheduler()
        self.downloader = Downloader()
        self.pipeline = Pipeline()

        self.spider_mid = SpiderMiddleware()
        self.downloader_mid = DownloaderMiddleware()

    def start(self):
        # 被调用执行引擎逻辑的入口函数

        start = datetime.now()  # 起始时间
        logger.info("开始运行时间:%s" % start)  # 使用日志记录起始运行时间
        self._start_engine()
        stop = datetime.now()  # 结束时间
        logger.info("结束运行时间:%s" % stop)  # 使用日志记录结束运行时间
        logger.info("耗时:%.2f" % (stop - start).total_seconds())  # 使用日志记录运行耗时

    def _start_engine(self):
        # 框架运行的逻辑

        # 1. 爬虫模块发出初始请求
        start_request = self.spider.start_request()

        # 利用爬虫中间件预处理请求对象
        start_request = self.spider_mid.process_request(start_request)

        # 2. 把初始请求添加给调度器
        self.scheduler.add_request(start_request)

        # 3. 从调度器获取请求对象
        request = self.scheduler.get_request()

        # 利用下载器中间件预处理请求对象
        request = self.downloader_mid.process_request(request)

        # 4. 利用下载器发起请求
        response = self.downloader.get_response(request)

        # 利用下载器中间件预处理响应对象
        response = self.downloader_mid.process_response(response)

        # 利用爬虫中间件预处理响应对象
        response = self.spider_mid.process_response(response)

        # 5. 利用爬虫的解析响应的方法,处理响应,得到结果
        result = self.spider.parse(response)

        # 6. 判断结果对象
        if isinstance(result, Request):

            # 利用爬虫中间件预处理请求对象
            result = self.spider_mid.process_request(result)

            # 6.1 如果是请求对象,那么就再交给调度器
            self.scheduler.add_request(result)

        else:

            # 6.2 否则,就交给管道处理
            self.pipeline.process_item(result)
Esempio n. 6
0
class Engine():
    def __init__(self):
        self.spiders = self._auto_import_instances(path=SPIDERS,
                                                   isspider=True)  # 爬虫字典
        self.pipelines = self._auto_import_instances(path=PIPELINES)
        self.spider_mids = self._auto_import_instances(path=SPIDER_MIDDLEWARES)
        self.downloader_mids = self._auto_import_instances(
            path=DOWNLOADER_MIDDLEWARES)

        if SCHEDULER_PERSIST:
            self.collector = ReidsStatsCollector()
        else:
            self.collector = NormalStatsCollector()
        # self.total_response_nums = 0
        # self.total_request_nums = 0

        self.scheduler = Scheduler(self.collector)
        self.downloader = Downloader()
        self.pool = Pool(5)  # os.cpu_count() or 1
        self.is_running = True

    def _auto_import_instances(self, path=[], isspider=False):
        """传入项目配置中爬虫,管道,中间件配置变量(list),返回爬虫字段 或 包含管道类中间件对象的list"""

        results = {} if isspider else []

        for p in path:  # p == 'spiders.baidu.BaiduSpider' or 'pipelines.BaiduPipeline'
            py_name_str = p.rsplit('.', 1)[0]  # 'spiders.baidu' or 'pipelines'
            cls_name_str = p.rsplit('.',
                                    1)[1]  # 'BaiduSpider' or 'BaiduPipeline'
            # 动态导入py文件模块对象
            # importlib.import_module(模块/py文件名字的字符串) == 模块/py文件对象
            py_obj = importlib.import_module(py_name_str)
            # getattr(模块/py文件对象, 类名称字符串) == 类对象 # 还没有实例化
            cls_obj = getattr(py_obj, cls_name_str)

            if isspider:
                results[cls_obj.name] = cls_obj()
            else:
                results.append(cls_obj())
        return results

    def start(self):
        # 框架启动的入口函数
        start = datetime.now()  # 起始时间
        logger.info("开始运行时间:%s" % start)  # 使用日志记录起始运行时间
        self._start_engine()
        stop = datetime.now()  # 结束时间
        logger.info("结束运行时间:%s" % stop)  # 使用日志记录结束运行时间
        logger.info("耗时:%.2f" % (stop - start).total_seconds())  # 使用日志记录运行耗时
        logger.info("总的请求数量:{}".format(self.collector.request_nums))
        logger.info("总的响应数量:{}".format(self.collector.response_nums))
        logger.info("重复请求数量:{}".format(self.collector.repeat_request_nums))
        self.collector.clear()  # 清除计数统计!

    def _start_requests(self):
        """专门处理所有爬虫起始url:构造request,加入队列"""
        def func(spider_name, spider):
            """对一个爬虫的所有起始url构造request,并入队"""
            # 1. 爬虫模块发出初始请求
            for start_request in spider.start_requests():
                # 给request添加spider_name属性
                start_request.spider_name = spider_name
                # 利用爬虫中间件预处理请求对象
                for spider_mid in self.spider_mids:
                    start_request = spider_mid.process_request(
                        start_request, spider)
                # 2. 把初始请求添加给调度器
                self.scheduler.add_request(start_request)
                # 总请求数 +1
                # self.total_request_nums += 1
                self.collector.incr(self.collector.request_nums_key)

        for spider_name, spider in self.spiders.items():
            # 异步的让每个爬虫的起始request入队
            self.pool.apply_async(func, args=(spider_name, spider))

    def _execute_request_response_item(self):
        """只处理一个请求对象:从队列取出一个request->response->item进管道/request入队"""
        # 3. 从调度器获取请求对象,交给下载器发起请求,获取一个响应对象
        request = self.scheduler.get_request()
        if request is None:  # 此时队列取空了
            return  # 提前终止函数
        # 根据爬虫的名字来获取爬虫类对象 # 爬虫名字在请求对象进入队列之前已经赋值了!
        spider = self.spiders[request.spider_name]

        # 利用下载器中间件预处理请求对象
        for downloader_mid in self.downloader_mids:
            request = downloader_mid.process_request(request, spider)
        # 4. 利用下载器发起请求
        response = self.downloader.get_response(request)
        # meta属性的传递!!!
        response.meta = request.meta
        # 利用下载器中间件预处理响应对象
        for downloader_mid in self.downloader_mids:
            response = downloader_mid.process_response(request, response,
                                                       spider)
        # 利用爬虫中间件预处理响应对象
        for spider_mid in self.spider_mids:
            response = spider_mid.process_response(request, response, spider)

        # 5. 利用爬虫的解析响应的方法,处理响应,得到结果
        # 利用getattr函数和爬虫类中构造的request指定的解析函数名称字符串 获取指定解析函数对象
        # func = getattr(类, 类中函数的字符串) # func还没有执行被调用
        parse_func = getattr(spider, request.parse)
        # 调用request.parse指定的解析函数
        if request.parse == 'start_requests':  # 防止爬虫中request.parse指定为start_requests函数
            results = parse_func()
        else:
            results = parse_func(response)  # 解析函数有可能返回可迭代对象或None
        if results is None:  # 如果解析函数返回None就提前return
            # 总响应数 +1
            # self.total_response_nums += 1
            self.collector.incr(self.collector.response_nums_key)
            return

        for result in results:
            # 6. 判断结果对象
            if isinstance(result, Request):
                # 给request添加spider_name属性
                result.spider_name = request.spider_name
                # 利用爬虫中间件预处理请求对象
                for spider_mid in self.spider_mids:
                    result = spider_mid.process_request(result, spider)
                # 6.1 如果是请求对象,那么就再交给调度器
                self.scheduler.add_request(result)
                # 总请求数 +1
                # self.total_request_nums += 1
                self.collector.incr(self.collector.request_nums_key)
            else:
                # 6.2 否则,就交给管道处理
                for pipeline in self.pipelines:
                    result = pipeline.process_item(result, spider)
        # 总响应数 +1
        # self.total_response_nums += 1
        self.collector.incr(self.collector.response_nums_key)

    def _error_callback(self, exception):
        try:
            raise exception  # 抛出异常后,才能被日志进行完整记录下来
        except Exception as e:
            logger.exception(e)

    def _callback(self, xxx):
        if self.is_running:
            # print(self.total_response_nums, self.total_request_nums, self.scheduler.total_repeat_nums)

            self.pool.apply_async(self._execute_request_response_item,
                                  callback=self._callback,
                                  error_callback=self._error_callback)

    def _start_engine(self):
        """引擎执行的逻辑"""
        self._start_requests()  # 把所有起始url构造request入队

        for i in range(MAX_ASYNC_THREAD_NUMBER):
            # 不断 处理一个请求对象,直到该request处理完毕
            self.pool.apply_async(self._execute_request_response_item,
                                  callback=self._callback,
                                  error_callback=self._error_callback)
        while True:  # 退出条件
            time.sleep(0.01)
            # 总响应数 + 重复的请求数 == 总请求数
            if self.collector.response_nums + self.collector.repeat_request_nums == self.collector.request_nums:
                self.is_running = False
                # print(self.total_response_nums, self.total_request_nums, self.scheduler.total_repeat_nums)
                break
Esempio n. 7
0
class Engine():
    def __init__(self):
        self.spiders = self._auto_import_ret(path=SPIDERS, isspider=True)
        self.scheduler = Scheduler()
        self.downloader = Downloader()
        self.pipelines = self._auto_import_ret(path=PIPELINES)

        self.spider_mids = self._auto_import_ret(path=SPIDER_MIDDLEWARES)
        self.downloader_mids = self._auto_import_ret(
            path=DOWNLOADER_MIDDLEWARES)

        self.total_request_nums = 0
        self.total_response_nums = 0

        self.pool = Pool(MAX_REQUEST_NUMS)
        self.is_running = True

    def _auto_import_ret(self, path=[], isspider=False):
        """利用动态导包的方式,自动根据配置获取爬虫字典或管道、中间件列表"""
        ret = {} if isspider else []
        for p in path:  # 'spiders.baidu.BaiduSpider'
            py_name_str = p.rsplit('.', 1)[0]  # 'spiders.baidu'
            cls_name_str = p.rsplit('.', 1)[1]  # 'BaiduSpider'
            # 利用importlib.import_module函数来根据执行位置.模块名字符串来获取模块对象
            py_obj = importlib.import_module(py_name_str)
            cls = getattr(py_obj, cls_name_str)  #此时返回的是没有实例化的类对象!
            if isspider:
                ret[cls.name] = cls()
            else:
                ret.append(cls())
        return ret

    def start(self):
        # 入口函数
        start_time = datetime.datetime.now()
        logger.info('开始的时间:{}'.format(start_time))
        self._start_engine()
        end_time = datetime.datetime.now()
        logger.info('结束的时间:{}'.format(end_time))
        logger.info('耗时:{}'.format(end_time - start_time))
        logger.info('总请求数:{}'.format(self.total_request_nums))
        logger.info('总响应数:{}'.format(self.total_response_nums))
        logger.info('重复的请求数:{}'.format(self.scheduler.repeat_request_nums))

    def _start_requests(self):
        """把爬虫的所有起始url全部构造成request,放入队列"""
        # 1. 爬虫模块发出初始请求
        for spider_name, spider in self.spiders.items():
            for start_request in spider.start_requests():
                # 给request添加爬虫名的属性
                start_request.spider_name = spider_name
                # 利用爬虫中间件预处理请求对象
                for spider_mid in self.spider_mids:
                    start_request = spider_mid.process_request(
                        start_request, spider)
                # 2. 把初始请求添加给调度器
                self.scheduler.add_request(start_request)
                # 总请求数 + 1
                self.total_request_nums += 1

    def execute_request_response_item(self):
        """把队列中取出的一个request进行处理,直到不再需要该request"""
        # 3. 从调度器获取请求对象
        request = self.scheduler.get_request()
        if request is None:
            return  # 此时请求队列被取空
        # 通过request携带的spider_name来按键取出爬虫类对象
        spider = self.spiders[request.spider_name]

        # 利用下载器中间件预处理请求对象
        for downloader_mid in self.downloader_mids:
            request = downloader_mid.process_request(request, spider)
        # 4. 利用下载器发起请求
        response = self.downloader.get_response(request)
        # 传递meta!!!
        response.meta = request.meta
        # 利用下载器中间件预处理响应对象
        for downloader_mid in self.downloader_mids:
            response = downloader_mid.process_response(response, spider)
        # 利用爬虫中间件预处理响应对象
        for spider_mid in self.spider_mids:
            response = spider_mid.process_response(response, spider)

        # request.parse == 解析函数名字符串
        # 函数 = getattr(类对象, 类的函数名字符串)
        # 获取request在构造时指定的解析函数
        parse_func = getattr(spider, request.parse)

        # 5. 利用爬虫的解析响应的方法,处理响应,得到结果
        results = parse_func(response)

        for result in results:
            # 6. 判断结果对象
            if isinstance(result, Request):
                # 给request添加爬虫名的属性
                result.spider_name = request.spider_name
                # result.spider_name = spider.name # 结果同上一行
                # 利用爬虫中间件预处理请求对象
                for spider_mid in self.spider_mids:
                    result = spider_mid.process_request(result, spider)
                # 6.1 如果是请求对象,那么就再交给调度器
                self.scheduler.add_request(result)
                # 总请求数 + 1
                self.total_request_nums += 1
            else:
                # 6.2 否则,就交给管道处理
                for pipeline in self.pipelines:
                    result = pipeline.process_item(result, spider)

        # 总响应数 + 1
        self.total_response_nums += 1

    def _callback(self, temp):
        if self.is_running:
            self.pool.apply_async(func=self.execute_request_response_item,
                                  callback=self._callback)

    def _start_engine(self):
        # 调用其他组件的属性或函数来组织运行的逻辑

        self._start_requests()

        for i in range(MAX_REQUEST_NUMS):  # 通过配置来控制并发规模
            self.pool.apply_async(func=self.execute_request_response_item,
                                  callback=self._callback)

        while True:
            # 程序退出的条件:总响应数 + 重复请求数 == 总请求数
            if self.total_response_nums + self.scheduler.repeat_request_nums == self.total_request_nums:
                self.is_running = False
                break
Esempio n. 8
0
class Engine():
    def __init__(self):
        self.spiders = self._auto_import_instances(path=SPIDERS, isspider=True)
        self.scheduler = Scheduler()
        self.downloader = Downloader()
        self.pipelines = self._auto_import_instances(path=PIPELINES)
        self.spider_mids = self._auto_import_instances(path=SPIDER_MIDDLEWARES)
        self.downloader_mids = self._auto_import_instances(
            path=DOWNLOADER_MIDDLEWARES)

        self.total_response_nums = 0
        self.total_request_nums = 0

    def _auto_import_instances(self, path=[], isspider=False):
        """根据项目配置动态返回爬虫字典 或管道中间件列表"""
        instances = {} if isspider else []
        for p in path:  # p = 'pipelines.BaiduPipeline'
            py_name_str = p.rsplit('.', 1)[0]  # 'spipelines'
            cls_name_str = p.rsplit('.', 1)[1]  # 'BaiduPipeline'
            py_obj = importlib.import_module(py_name_str)  # 获取py文件对象
            cls_obj = getattr(py_obj, cls_name_str)  # 获取py文件中的类对象,此时没有实例化!
            if isspider:
                instances[cls_obj.name] = cls_obj()
            else:
                instances.append(cls_obj())
        return instances

    def start(self):
        # 被调用执行引擎逻辑的入口函数

        start = datetime.now()  # 起始时间
        logger.info("开始运行时间:%s" % start)  # 使用日志记录起始运行时间
        self._start_engine()
        stop = datetime.now()  # 结束时间
        logger.info("结束运行时间:%s" % stop)  # 使用日志记录结束运行时间
        logger.info("耗时:%.2f" % (stop - start).total_seconds())  # 使用日志记录运行耗时
        logger.info("总的请求数量:{}".format(self.total_request_nums))
        logger.info("总的响应数量:{}".format(self.total_response_nums))

    def _start_requests(self):
        """把爬虫中所有起始的url构造request并添加到请求队列中"""
        for spider_name, spider in self.spiders.items():
            for start_request in spider.start_requests():
                # 1. 爬虫模块发出初始请求
                # 利用爬虫中间件预处理请求对象
                for spider_mid in self.spider_mids:
                    start_request = spider_mid.process_request(
                        start_request, spider)
                # 给request对象增加spider_name的属性
                start_request.spider_name = spider_name
                # 2. 把初始请求添加给调度器
                self.scheduler.add_request(start_request)
                # 总请求数 + 1
                self.total_request_nums += 1

    def _execute_request_response_item(self):
        """队列中取出一个request,直到处理完毕"""
        # 3. 从调度器获取请求对象
        request = self.scheduler.get_request()
        # 判断队列是否取空
        if request is None:
            return  # 提前终止
        # 根据爬虫名获取爬虫类对象
        spider = self.spiders[request.spider_name]
        # 利用下载器中间件预处理请求对象
        for downloader_mid in self.downloader_mids:
            request = downloader_mid.process_request(request, spider)
        # 4. 利用下载器发起请求
        response = self.downloader.get_response(request)
        # 传递meta
        response.meta = request.meta
        # 利用下载器中间件预处理响应对象
        for downloader_mid in self.downloader_mids:
            response = downloader_mid.process_response(request, response,
                                                       spider)
        # 利用爬虫中间件预处理响应对象
        for spider_mid in self.spider_mids:
            response = spider_mid.process_response(request, response, spider)
        # 5. 利用爬虫的解析响应的方法,处理响应,得到结果
        # request.parse指定的解析函数 = getattr(爬虫类对象, 指定的解析函数的字符串)
        parse_func = getattr(spider, request.parse)
        results = parse_func(response)
        for result in results:
            # 6. 判断结果对象
            if isinstance(result, Request):
                # 利用爬虫中间件预处理请求对象
                for spider_mid in self.spider_mids:
                    result = spider_mid.process_request(result, spider)
                # 给request对象增加spider_name的属性
                result.spider_name = request.spider_name
                # 6.1 如果是请求对象,那么就再交给调度器
                self.scheduler.add_request(result)
                # 总请求数 + 1
                self.total_request_nums += 1
            else:
                # 6.2 否则,就交给管道处理
                for pipeline in self.pipelines:
                    result = pipeline.process_item(result, spider)
        # 总响应数 +1
        self.total_response_nums += 1

    def _start_engine(self):
        # 框架运行的逻辑

        self._start_requests()  # 把所有初始request放入队列

        while True:
            time.sleep(0.1)
            self._execute_request_response_item()  # 处理一个从队列中取出的request

            # 程序退出的条件
            if self.scheduler.q.empty():
                break  # 判断队列为空
Esempio n. 9
0
class Engine(object):
    """
    1.对其他模块进行初始化
    2.启动引擎(实现引擎调用核心逻辑)
    """
    def __init__(self):
        self.spiders = self.__auto_import(
            settings.SPIDERS, is_spider=True)  # 这里传递过来的是一个字典{爬虫名:爬虫对象}
        # 创建统计器对象
        self.stats_collector = StatsCollector()
        # 把统计器对象传递给调度器
        self.scheduler = Scheduler(self.stats_collector)
        self.downloader = Downloader()
        self.pipelines = self.__auto_import(settings.PIPELINES)
        self.spider_middlewares = self.__auto_import(
            settings.SPIDER_MIDDLEWARES)
        self.downloader_middlewares = self.__auto_import(
            settings.DOWNLOADER_MIDDLEWARES)
        self.pool = Pool()  # 创建线程池对象
        # 定义变量,用于记录起始请求完成的爬虫数量
        self.start_request_finished_spider_count = 0

    @staticmethod
    def __auto_import(full_names, is_spider=False):
        """
        动态导入:根据配置信息自动创建对象,封装成需要的格式返回
        :param full_names: 配置全类名(路径)列表
        :param is_spider: 判断是不是爬虫对象
        :return: 配置类的对象列表或者字典
        """
        # 如果是爬虫就是字典,否则是列表
        instances = {} if is_spider else []
        for full_name in full_names:
            result = full_name.rsplit('.', maxsplit=1)
            # 模块名
            module_name = result[0]
            # 类名
            class_name = result[1]
            # 根据模块名,导入模块,获取模块对象
            module = importlib.import_module(module_name)
            # 根据类名,从模块对象中获取类对象
            cls = getattr(module, class_name)
            # 使用类对象,创建实例对象
            instance = cls()
            # 如果是爬虫,存储到字典中,否则存到列表里
            if is_spider:
                instances[instance.name] = instance
            else:
                instances.append(instance)

        return instances

    def start(self):
        """启动引擎,对外提供接口"""
        start = datetime.now()
        logger.info("开始运行时间:%s" % start)
        self.__start()
        stop = datetime.now()
        logger.info("运行结束时间:%s" % stop)
        logger.info("耗时:%.2f秒" % (stop - start).total_seconds())
        # 总起始请求数量
        logger.info('总起始请求数量:%s' % self.stats_collector.start_request_nums)
        # 记录总请求数量
        logger.info('总请求数量:%s' % self.stats_collector.request_nums)
        # 总过滤请求数量
        logger.info('过滤掉的请求数量:%s' % self.stats_collector.repeat_request_nums)
        # 总响应数量
        logger.info('总响应处理数量:%s' % self.stats_collector.response_nums)
        # 如果启用分布式,当前程序结束的时候,清空统计信息
        if settings.SCHEDULER_PERSIST:
            self.stats_collector.clear()
            if not settings.FP_PERSIST:
                # 如果不开启断点续爬,清空指纹和请求队列数据
                self.scheduler.clear()

    @staticmethod
    def __error_callback(e):
        """错误回调函数"""
        try:
            raise e
        except Exception as e:
            logger.exception(e)

    def __callback_execute(self, temp):
        """异步线程池回调函数"""
        self.pool.apply_async(self.__execute_request_response_item,
                              callback=self.__callback_execute,
                              error_callback=self.__error_callback)

    def __start(self):
        """私有启动引擎的方法,实现核心代码"""
        # 添加起始请求到调度器中
        # 异步执行__add_start_requests任务
        self.pool.apply_async(self.__add_start_requests,
                              error_callback=self.__error_callback)

        # 配置多少个异步任务,这个异步调用就执行多少次
        for i in range(settings.ASYNC_COUNT):
            # 异步执行__execute_request_response_item任务
            self.pool.apply_async(self.__execute_request_response_item,
                                  callback=self.__callback_execute,
                                  error_callback=self.__error_callback)

        # 让主线程等待一下,让上面的异步任务启动起来
        time.sleep(1)

        while True:
            # 死循环进行轮询,非常消耗cpu性能,稍睡一下,降低消耗
            time.sleep(0.1)

            # 当所有爬虫的起始请求都执行完了才结束
            if self.start_request_finished_spider_count >= len(self.spiders):
                # 当所有请求都处理完成,要结束循环
                if self.stats_collector.response_nums >= self.stats_collector.request_nums:
                    # 没有请求了,退出循环
                    break

    def __execute_request_response_item(self):
        """处理请求、响应和数据方法"""
        # 从调度器获取请求对象,交给下载器发起请求,获取一个响应对象
        request = self.scheduler.get_request()

        # 取出该请求对应爬虫对象,根据爬虫名去爬虫字典中取出爬虫对象
        spider = self.spiders[request.spider_name]

        # 遍历下载器中间件列表,获取每一个下载器中间件
        for downloader_middleware in self.downloader_middlewares:
            # 利用下载器中间件预处理请求对象
            request = downloader_middleware.process_request(request)
        # 利用下载器发起请求
        response = self.downloader.get_response(request)
        # 把请求的meta数据传递给response
        response.meta = request.meta

        # 利用下载器中间件预处理响应对象
        # 遍历下载器中间件列表,获取每个下载器中间件
        for downloader_middleware in self.downloader_middlewares:
            response = downloader_middleware.process_response(response)

        # 遍历爬虫中间件列表,获取每一个爬虫中间件
        for spider_middleware in self.spider_middlewares:
            # 调用爬虫中间件的process_response方法,处理响应
            response = spider_middleware.process_response(response)

        # 如果有该请求有对应解析函数callback,就使用callback来解析数据
        if request.callback:
            # 接收解析函数,处理结果
            results = request.callback(response)
        else:
            # 如果没有callback就使用parse函数来解析数据
            results = spider.parse(response)

        # 判断results是不是可迭代对象,如果不可迭代,变为可迭代的
        if not isinstance(results, Iterable):
            results = [results]
        for result in results:
            # 判断结果对象
            if isinstance(result, Request):
                # 如果是请求对象,就再交给调度器
                # 遍历爬虫中间件列表,获取每个爬虫中间件
                for spider_middleware in self.spider_middlewares:
                    # 利用爬虫中间件预处理请求对象
                    result = spider_middleware.process_request(result)

                # 设置请求对象对应的爬虫名
                result.spider_name = spider.name

                self.scheduler.add_request(result)
            else:
                # 否则,交给管道处理
                for pipeline in self.pipelines:
                    result = pipeline.process_item(result, spider)

        # 统计总的响应数量,每次递增1
        self.stats_collector.incr(self.stats_collector.response_nums_key)

    def __add_start_requests(self):
        # 遍历爬虫字典,取出爬虫对象
        for spider_name, spider in self.spiders.items():
            # 异步执行起始请求任务,防止有增量爬虫启动后不停止而后面的爬虫不执行的问题
            self.pool.apply_async(
                self.__add_one_spider_start_requests,
                args=(spider, spider_name),
                error_callback=self.__error_callback,
                callback=self.__add_one_spider_start_requests_callback)

    def __add_one_spider_start_requests_callback(self, temp):
        """每调用一次,起始请求完成数+1"""
        self.start_request_finished_spider_count += 1

    def __add_one_spider_start_requests(self, spider, spider_name):
        # 调用爬虫start_request方法,获取请求对象
        for request in spider.start_request():
            # 设置该请求对应的爬虫名
            request.spider_name = spider_name

            # 统计起始请求数量
            self.stats_collector.incr(
                self.stats_collector.start_request_nums_key)

            # 利用爬虫中间件预处理请求对象
            # 遍历爬虫中间件列表,获取每个爬虫中间件
            for spider_middleware in self.spider_middlewares:
                request = spider_middleware.process_request(request)
            # 调用调度器的add_request把请求添加到调度器中
            self.scheduler.add_request(request)