def _run(self): """ Hace el parseo, no se debe redefinir en las clases que derivan del parser """ #self.log(str(self.__context.options.continue_on_item)) continue_on = self.__context.options.continue_on_item if continue_on == ContinueOnItem.OnlyFinalize: # solo finalize self.__do_only_finalize() return elif continue_on == ContinueOnItem.DoAllParsing: # hace todo: paginado + procesamiento + finalize im_db_file = self.__context.options.item_manager_database_file self.__item_manager = ItemManager(im_db_file, replace_items = True) self.__make_paging_call_sequence() to_process = self.__item_manager.unprocessed_items elif continue_on == ContinueOnItem.ContinueLast: # no hace el paginado, agarra los items sin procesar im_db_file = self.__context.options.item_manager_database_file self.__item_manager = ItemManager(im_db_file, replace_items = False) to_process = self.__item_manager.unprocessed_items self.log("Continuando corrida, quedan por procesar %d items" % \ len(to_process)) else: # empieza incondicionalmente desde un cierto item im_db_file = self.__context.options.item_manager_database_file self.__item_manager = ItemManager(im_db_file, replace_items = False) self.__item_manager.set_processed_from(continue_on, False) to_process = self.__item_manager.unprocessed_items self.log("Continuando corrida, quedan por procesar %d items" % \ len(to_process)) self.__process_items(to_process) self.__finalize_call_sequence()
class ListBasedParser(BaseParser): "Un parser basado en una lista de items" browser_type = BrowserFactory.browser_type """ Enumerado para indicar el tipo de browser, toma los valores * *BasicBrowser* para BasicBrowser * *WebKitBrowser* para WebKitBrowser * *DefaultBrowser* para denotar al browser usado por defecto """ def __init__(self, context): BaseParser.__init__(self, context) check_if_any_type(context, sdf.Context) self.__context = context self.__item_manager = None self.__pipeline = None self.__browser_pool = None def _run(self): """ Hace el parseo, no se debe redefinir en las clases que derivan del parser """ #self.log(str(self.__context.options.continue_on_item)) continue_on = self.__context.options.continue_on_item if continue_on == ContinueOnItem.OnlyFinalize: # solo finalize self.__do_only_finalize() return elif continue_on == ContinueOnItem.DoAllParsing: # hace todo: paginado + procesamiento + finalize im_db_file = self.__context.options.item_manager_database_file self.__item_manager = ItemManager(im_db_file, replace_items = True) self.__make_paging_call_sequence() to_process = self.__item_manager.unprocessed_items elif continue_on == ContinueOnItem.ContinueLast: # no hace el paginado, agarra los items sin procesar im_db_file = self.__context.options.item_manager_database_file self.__item_manager = ItemManager(im_db_file, replace_items = False) to_process = self.__item_manager.unprocessed_items self.log("Continuando corrida, quedan por procesar %d items" % \ len(to_process)) else: # empieza incondicionalmente desde un cierto item im_db_file = self.__context.options.item_manager_database_file self.__item_manager = ItemManager(im_db_file, replace_items = False) self.__item_manager.set_processed_from(continue_on, False) to_process = self.__item_manager.unprocessed_items self.log("Continuando corrida, quedan por procesar %d items" % \ len(to_process)) self.__process_items(to_process) self.__finalize_call_sequence() def __process_items(self, to_process): "Procesa los items" self.log("Comenzando procesado. Usando %d browsers" % self.get_paralel_num()) # crea el pool de browsers self.__browser_pool = _BrowserPool() for _ in range(self.get_paralel_num()): self.__browser_pool.add_browser(self.__get_browser( self.get_process_item_browser_type() ) ) # pone a procesar todos los items progress = eta.Progress(len(to_process), unit = "pag") pipeline = ItemProcesorPipeline(self.get_paralel_num()) start = time() # convierte los items a una lista para tenerlos todos antes de # agregarlos al pipeline to_process = list(to_process) for item_manager_item in to_process: # print "pusheando %d" % item_manager_item.item_num pipeline.push(self.__process_item_call_sequence, item_manager_item, len(to_process), progress, pipeline ) pipeline.wait_end() # espera a que termine de procesar processing_time_str = eta.time_string(time() - start) self.log("Procesamiento terminado en %s" % processing_time_str) def __do_only_finalize(self): "solo hace el finalize" im_db_file = self.__context.options.item_manager_database_file self.__item_manager = ItemManager(im_db_file, replace_items = False) self.log("Continuando corrida con finalize") self.__finalize_call_sequence() def __get_browser(self, browser_type): "Obtiene un browser según una clase dada" if browser_type == self.browser_type.BasicBrowser: return BrowserFactory.get_instance().get_basicbrowser() elif browser_type == self.browser_type.WebKitBrowser: show_images = self.__context.options.webkitbrowser_showimages show = self.__context.options.webkitbrowser_show return BrowserFactory.get_instance().get_webkitbrowser(show, show_images) elif browser_type == self.browser_type.DefaultBrowser: if self.__context.options.use_webkitbrowser: return self.__get_browser(self.browser_type.WebKitBrowser) else: return self.__get_browser(self.browser_type.BasicBrowser) else: raise LogicError("Unknown browser type") def __make_paging_call_sequence(self): "hace el paginado y chequea la respuesta" self.log("Comenzado paginado") start = time() browser = self.__get_browser(self.get_paging_browser_type()) paging_res = self.make_paging(MakePagingContext(self.__context,browser)) # chequea el tipo de los elementos retornados if not hasattr(paging_res, '__iter__'): raise TypeError("make_paging() returned %s, but it must return a" +\ " list" % type(paging_res).__name__) # chequea uno a uno los elementos retornados for paged_item in paging_res: if not isinstance(paged_item, Item): raise TypeError("make_paging must return a list of Item " +\ "objects, but it returned an item of type %s" % ( type(paged_item).__name__)) paging_time = time() - start self.log("Fin paginado, en %s . Obtenidos %s items" % (eta.time_string(paging_time), len(paging_res)) ) # agrega los items self.__item_manager.add(paging_res) return paging_res def __process_item_call_sequence(self, item_manager_item, total_items, eta_obj, pipeline): "Llamada al procesar el item" #print "procesando %d" % item_manager_item.item_num start_time = time() # print "pool %d" % item_manager_item.item_num browser = self.__browser_pool.get_browser() # print "pool_done %d" % item_manager_item.item_num try: processed_ok = False # carga la url del item a parsear try: browser.load_page(item_manager_item.stored_item.url) except: raise LoadPageError("Error al cargar la página %s" % \ item_manager_item.stored_item.url) self.process_item ( ProcessItemContext(self.__context, browser), item_manager_item.stored_item ) processed_ok = True item_manager_item.store_item() #print "semi processed %d" % item_manager_item.item_num except: e_str = self.__handle_last_error("Error procesando item %d" % (item_manager_item.item_num + 1)) self.__item_manager.add_error(item_manager_item.item_num, e_str) # cancela el procesamiento en el primer error if self.__context.options.list_based_parser_die_on_first_error: pipeline.cancel_queue() self.__browser_pool.free_browser(browser) if processed_ok: item_manager_item.processed = True processed_res_str = "OK" else: processed_res_str = "ERROR" process_time = time() - start_time eta_obj.increment() self.log( "Procesado item %d (%s) [%d/%d] en %.2fs Vel: %s ETA: %s" % (item_manager_item.item_num, processed_res_str, eta_obj.history[-1][0], total_items, process_time, eta_obj.overall_rate_str(), eta_obj.time_remaining_str()) ) def __finalize_call_sequence(self): "Hace todo lo necesario para la llamada a finalize, si corresponde" starttime = time() processed_items = self.__item_manager.stored_processed_items unprocessed_items = self.__item_manager.stored_unprocessed_items msg = "Comenzando finalización. Procesados %d items, " msg += "no procesados %d items" self.log(msg % (len(processed_items), len(unprocessed_items))) try: self.finalize(FinalizeContext(self.__context), processed_items, unprocessed_items) except: e_str = self.__handle_last_error("Error en llamada a finalize()") self.__item_manager.add_error(-1, e_str) ellapsed = eta.time_string(time() - starttime) self.log("Finalización completada en %s" % ellapsed) def __handle_last_error(self, pre_string = None): """ Función para loggear un error correctamente, pone una línea con la descripción del error, se usa pre_string ahi. Se imprime el error en el log y se devuelve la cadena del error impresa """ if pre_string == None: pre_string = "Error" error_type, error_value, trbk = sys.exc_info() tb_list = traceback.format_tb(trbk) error_str = "%s: %s: %s\n" % ( pre_string, error_type.__name__, error_value) if len(tb_list) > 1: error_str += "Traceback:" for i in tb_list[1:]: # se ignora la 1ra llamada (esta función) error_str += "\n" + i self.log(error_str + "\n") return error_str ######################## Funciones redefinibles ########################## def get_paralel_num(self): """ Define la cantidad de items que se procesan en paralelo. Por defecto es 2 """ return 2 def get_paging_browser_type(self): """ Devuelve la clase de browser que se usa para hacer el paginado. Los valores que se aceptan son * ListBasedBrowser.browser_type.BasicBrowser para usar BasicBrowser * ListBasedBrowser.browser_type.WebKitBrowser para usar WebKitBrowser * ListBasedBrowser.browser_type.DefaultBrowser para usar el browser por defecto (se usa WebkitBrowser si se define ``-W`` en las opciones). Esta es la opción usada por defecto """ return self.browser_type.DefaultBrowser def get_process_item_browser_type(self): """ Igual que :func:`get_paging_browser_type` pero para definir el browser que se usa al procesar items """ return self.browser_type.DefaultBrowser def make_paging(self, context): "Hace el paginado" raise NotImplementedError def process_item(self, context, item): "Procesa un item." raise NotImplementedError def finalize(self, context, processed_items, unprocessed_items): "Se llama luego de procesar los items" pass
def __do_only_finalize(self): "solo hace el finalize" im_db_file = self.__context.options.item_manager_database_file self.__item_manager = ItemManager(im_db_file, replace_items = False) self.log("Continuando corrida con finalize") self.__finalize_call_sequence()