Cómo atrapar y manejar varias excepciones en scrapy

Prefacio
    Al usar scrapy para tareas de rastreo grandes (el tiempo de rastreo es en días), no importa cuán buena sea la velocidad de la red del host, después del rastreo, siempre encontrará que "item_scraped_count" en el registro de scrapy no es igual al número de semillas pre-sembradas, siempre hay una parte El rastreo de semillas falla. Hay dos tipos de fallas, como se muestra en la figura a continuación (la siguiente figura muestra el registro cuando se completa el rastreo irregular):

Las excepciones comunes en scrapy incluyen, pero no se limitan a: error de descarga (área azul), código http 403/500 (área naranja).

No importa qué tipo de excepción, podemos referirnos al método de escritura de middleware de reintento de scrapy para escribir nuestro propio middleware.

El texto
     usa el IDE, y ahora cualquier archivo en el proyecto scrapy se escribe con el siguiente código:

desde scrapy.downloadermiddlewares.retry importe RetryMiddleware,
mantenga presionada la tecla Ctrl y haga clic con el botón izquierdo del mouse para hacer clic en RetryMiddleware para ingresar la ubicación del archivo del proyecto donde se encuentra el middleware. También puede encontrar la ubicación del middleware al ver el archivo. La ruta es: sitios-paquetes / scrapy / downloadermiddlewares / retry.RetryMiddleware

El código fuente del middleware es el siguiente:

class RetryMiddleware(object):
 
    # IOError is raised by the HttpCompression middleware when trying to
    # decompress an empty response
    EXCEPTIONS_TO_RETRY = (defer.TimeoutError, TimeoutError, DNSLookupError,
                           ConnectionRefusedError, ConnectionDone, ConnectError,
                           ConnectionLost, TCPTimedOutError, ResponseFailed,
                           IOError, TunnelError)
 
    def __init__(self, settings):
        if not settings.getbool('RETRY_ENABLED'):
            raise NotConfigured
        self.max_retry_times = settings.getint('RETRY_TIMES')
        self.retry_http_codes = set(int(x) for x in settings.getlist('RETRY_HTTP_CODES'))
        self.priority_adjust = settings.getint('RETRY_PRIORITY_ADJUST')
 
    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler.settings)
 
    def process_response(self, request, response, spider):
        if request.meta.get('dont_retry', False):
            return response
        if response.status in self.retry_http_codes:
            reason = response_status_message(response.status)
            return self._retry(request, reason, spider) or response
        return response
 
    def process_exception(self, request, exception, spider):
        if isinstance(exception, self.EXCEPTIONS_TO_RETRY) \
                and not request.meta.get('dont_retry', False):
            return self._retry(request, exception, spider)
 
    def _retry(self, request, reason, spider):
        retries = request.meta.get('retry_times', 0) + 1
 
        retry_times = self.max_retry_times
 
        if 'max_retry_times' in request.meta:
            retry_times = request.meta['max_retry_times']
 
        stats = spider.crawler.stats
        if retries <= retry_times:
            logger.debug("Retrying %(request)s (failed %(retries)d times): %(reason)s",
                         {'request': request, 'retries': retries, 'reason': reason},
                         extra={'spider': spider})
            retryreq = request.copy()
            retryreq.meta['retry_times'] = retries
            retryreq.dont_filter = True
            retryreq.priority = request.priority + self.priority_adjust
 
            if isinstance(reason, Exception):
                reason = global_object_name(reason.__class__)
 
            stats.inc_value('retry/count')
            stats.inc_value('retry/reason_count/%s' % reason)
            return retryreq
        else:
            stats.inc_value('retry/max_reached')
            logger.debug("Gave up retrying %(request)s (failed %(retries)d times): %(reason)s",
                         {'request': request, 'retries': retries, 'reason': reason},
                         extra={'spider': spider})

Mirando el código fuente, podemos encontrar que para la respuesta que devuelve el código http, el middleware será procesado por el método process_response. El método de procesamiento es relativamente simple. Probablemente sea para determinar si response.status está en la colección self.retry_http_codes definida. Esta colección es una lista, definida en el archivo default_settings.py, definida de la siguiente manera:

RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408]
es determinar primero si el código http está en este conjunto, si es así, ingrese la lógica de reintento y devuelva la respuesta directamente si no está en el conjunto. De esta forma, se ha implementado la respuesta al código HTTP pero la respuesta anormal.

Sin embargo, el manejo de otra excepción es diferente. La excepción que acabo de mencionar es precisamente un error de solicitud HTTP (tiempo de espera), y cuando ocurre otra excepción, es una excepción de código real como se muestra a continuación ( Si no se procesa):

Puede crear un proyecto fragmentario, completar una url no válida en start_url para simular este tipo de excepción. Es más conveniente que RetryMiddleware también proporcione un método de manejo para este tipo de excepción: process_exception

Al mirar el código fuente, puede analizar la lógica de procesamiento aproximada: primero defina una colección para almacenar todo tipo de excepciones, y luego determine si la excepción entrante existe en la colección. Si lo hace (no analice, no intente), ingrese la lógica de reintento, no en Solo ignóralo.

De acuerdo, ahora que comprende cómo las excepciones son raras, la idea general también debería estar ahí. A continuación se publica una plantilla práctica de middleware para el manejo de excepciones:

from twisted.internet import defer
from twisted.internet.error import TimeoutError, DNSLookupError, \
    ConnectionRefusedError, ConnectionDone, ConnectError, \
    ConnectionLost, TCPTimedOutError
from scrapy.http import HtmlResponse
from twisted.web.client import ResponseFailed
from scrapy.core.downloader.handlers.http11 import TunnelError
 
class ProcessAllExceptionMiddleware(object):
    ALL_EXCEPTIONS = (defer.TimeoutError, TimeoutError, DNSLookupError,
                      ConnectionRefusedError, ConnectionDone, ConnectError,
                      ConnectionLost, TCPTimedOutError, ResponseFailed,
                      IOError, TunnelError)
    def process_response(self,request,response,spider):
        #捕获状态码为40x/50x的response
        if str(response.status).startswith('4') or str(response.status).startswith('5'):
            #随意封装,直接返回response,spider代码中根据url==''来处理response
            response = HtmlResponse(url='')
            return response
        #其他状态码不处理
        return response
    def process_exception(self,request,exception,spider):
        #捕获几乎所有的异常
        if isinstance(exception, self.ALL_EXCEPTIONS):
            #在日志中打印异常类型
            print('Got exception: %s' % (exception))
            #随意封装一个response,返回给spider
            response = HtmlResponse(url='exception')
            return response
        #打印出未捕获到的异常
        print('not contained exception: %s'%exception)


Ejemplo de código de análisis de araña:

class TESTSpider(scrapy.Spider):
    name = 'TEST'
    allowed_domains = ['TTTTT.com']
    start_urls = ['http://www.TTTTT.com/hypernym/?q=']
    custom_settings = {
        'DOWNLOADER_MIDDLEWARES': {
            'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
            'TESTSpider.middlewares.ProcessAllExceptionMiddleware': 120,
        },
        'DOWNLOAD_DELAY': 1,  # 延时最低为2s
        'AUTOTHROTTLE_ENABLED': True,  # 启动[自动限速]
        'AUTOTHROTTLE_DEBUG': True,  # 开启[自动限速]的debug
        'AUTOTHROTTLE_MAX_DELAY': 10,  # 设置最大下载延时
        'DOWNLOAD_TIMEOUT': 15,
        'CONCURRENT_REQUESTS_PER_DOMAIN': 4  # 限制对该网站的并发请求数
    }
    def parse(self, response):
        if not response.url: #接收到url==''时
            print('500')
            yield TESTItem(key=response.meta['key'], _str=500, alias='')
        elif 'exception' in response.url:
            print('exception')
            yield TESTItem(key=response.meta['key'], _str='EXCEPTION', alias='')

Nota: El Order_code de este middleware no puede ser demasiado grande. Si es demasiado grande, estará más cerca del descargador (Orden ejecutada por middleware predeterminado, haga clic aquí para ver), tendrá prioridad sobre RetryMiddleware para procesar la respuesta, pero este middleware se usa para la parte inferior, Es decir, cuando una respuesta 500 ingresa en la cadena de middleware, debe ser procesada primero por el middleware de reintento, y no puede ser procesada por el middleware que escribimos primero. No tiene la función de reintentar. Cuando se recibe la respuesta 500, la solicitud se abandona directamente y la solicitud se devuelve directamente. Esto no es razonable. Solo después del reintento, todavía hay solicitudes anormales que deben ser manejadas por el middleware que escribimos. En este momento, puede manejar lo que quiera, como reintentar nuevamente y devolver una respuesta reconstruida.

Verifiquemos cómo funciona (probando una URL no válida). La siguiente figura muestra el middleware no habilitado:

Luego habilite el middleware para ver el efecto:

Publicó 150 artículos originales · elogió 149 · 810,000 visitas

Supongo que te gusta

Origin blog.csdn.net/chaishen10000/article/details/103452164
Recomendado
Clasificación