爬虫工作量由小到大的思维转变---<第四十二章 Scrapy Redis 重试机制(ip相关)>

前言:

之前讲过一篇关于scrapy的重试机制的文章,那个是针对当时那哥们的代码讲的,但是,发现后面还是有很多问题; 本章节就着scrapy的重试机制来讲一下!!!

正文:

首先,要清楚一个概念,在scrapy的中间件中,默认会有一个scrapy重试中间件;只要你在settings.py设置中写上:

RETRY_TIMES=3 

那么他就会自动重试! 

即使你想拦截,例如在负责控制ip的中间件中拦截他,根本拦截不下来(只有最后一次才会拦截!)

那么这个retry_times 是怎么进行运算的呢?

q1:明明咱们设置的是3,怎么他重试了4次?  
解释:第一次是原始请求,重试为0; 接着每一次都会+1,当达到3次重试时(已经发起了4次请求)的时候,就会进入报错机制!
q2:面对请求失败的时候,为什么def process_response拦截不了!?
解释:因为是ip或者超时引起的问题,不会走response返回体! 他直接进入报错--->解决办法:
 def process_exception(self, request, exception, spider):

        if isinstance(exception, (ConnectError, ConnectionRefusedError, TCPTimedOutError)):
            current_retry_times = request.meta.get('retry_times', 0)
            if current_retry_times <=self.max_failures:
                # 记录失败的代理
                proxy = request.meta.get('proxy', '')
                self.remove_proxy(proxy, spider)
                print(f"代理 {proxy} 请求异常,尝试重试。当前重试次数:{current_retry_times}")

                # 更换新的代理IP
                new_proxy = self.get_proxy(spider)
                if new_proxy:
                    request.meta['proxy'] = new_proxy.decode('utf-8')
                    request.meta['retry_times'] = current_retry_times+1

                    return request.copy()
            else:
                print("超过重试次数")
                self.redis.sadd(self.fail_key,request.url)
Q3:我在settings.py里面设置了重试次数为3,且每次请求都会从ip池中带一个ip; 为什么前面3次重试我都没办法拦截这个请求,他自动发起请求了?
解释:在中间件中,关闭那个scrapy默认的重试中间件! 这样,每次请求的重试他就不走他中间件了,而是走你设置的中间件!!! 
DOWNLOADER_MIDDLEWARES = {
#把这个设置为NONE,关闭默认的重试中间件
    'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
    # 添加代理ip的中间件
    '爬虫名.middlewares.RedisProxyMiddleware': 543,


}

查看翻译原重试中间件代码:

"""
一个用于重试可能由临时问题如连接超时或HTTP 500错误导致失败的请求的扩展。

您可以通过修改抓取设置来改变此中间件的行为:
RETRY_TIMES - 重试失败页面的次数
RETRY_HTTP_CODES - 需要重试的HTTP响应代码

失败的页面在抓取过程中被收集起来,并在爬虫完成抓取所有常规(非失败)页面后重新安排。
"""
from logging import Logger, getLogger
from typing import Optional, Union

from twisted.internet import defer
from twisted.internet.error import (
    ConnectError,
    ConnectionDone,
    ConnectionLost,
    ConnectionRefusedError,
    DNSLookupError,
    TCPTimedOutError,
    TimeoutError,
)
from twisted.web.client import ResponseFailed

from scrapy.core.downloader.handlers.http11 import TunnelError
from scrapy.exceptions import NotConfigured
from scrapy.http.request import Request
from scrapy.spiders import Spider
from scrapy.utils.python import global_object_name
from scrapy.utils.response import response_status_message

retry_logger = getLogger(__name__)


def get_retry_request(
    request: Request,
    *,
    spider: Spider,
    reason: Union[str, Exception] = "unspecified",
    max_retry_times: Optional[int] = None,
    priority_adjust: Optional[int] = None,
    logger: Logger = retry_logger,
    stats_base_key: str = "retry",
):
    """
    返回一个新的:class:`~scrapy.Request`对象来重试指定的请求,如果指定请求的重试次数已耗尽,则返回``None``。

    例如,在一个:class:`~scrapy.Spider`回调中,您可以如下使用它::

        def parse(self, response):
            if not response.text:
                new_request_or_none = get_retry_request(
                    response.request,
                    spider=self,
                    reason='empty',
                )
                return new_request_or_none

    *spider* 是请求重试的:class:`~scrapy.Spider`实例。它用来访问:ref:`设置 `和:ref:`统计 `,以及提供额外的日志上下文(参考:func:`logging.debug`)。

    *reason* 是一个字符串或一个:class:`Exception`对象,表明为什么需要重试请求。它被用来命名重试统计信息。

    *max_retry_times* 是一个数字,决定了*request*可以被重试的最大次数。如果未指定或``None``,则从请求的: reqmeta:`max_retry_times`元键中读取。如果:reqmeta:`max_retry_times`元键未定义或``None``,则从: setting:`RETRY_TIMES`设置中读取。

    *priority_adjust* 是一个数字,决定了新请求的优先级如何相对于*request*进行调整。如果未指定,则从: setting:`RETRY_PRIORITY_ADJUST`设置中读取。

    *logger* 是用于记录消息的logging.Logger对象

    *stats_base_key* 是字符串,用作重试相关作业统计的基础键
    """
    settings = spider.crawler.settings
    stats = spider.crawler.stats
    retry_times = request.meta.get("retry_times", 0) + 1
    if max_retry_times is None:
        max_retry_times = request.meta.get("max_retry_times")
        if max_retry_times is None:
            max_retry_times = settings.getint("RETRY_TIMES")
    if retry_times <= max_retry_times:
        logger.debug(
            "重试 %(request)s (失败 %(retry_times)d 次): %(reason)s",
            {"request": request, "retry_times": retry_times, "reason": reason},
            extra={"spider": spider},
        )
        new_request: Request = request.copy()
        new_request.meta["retry_times"] = retry_times
        new_request.dont_filter = True
        if priority_adjust is None:
            priority_adjust = settings.getint("RETRY_PRIORITY_ADJUST")
        new_request.priority = request.priority + priority_adjust

        if callable(reason):
            reason = reason()
        if isinstance(reason, Exception):
            reason = global_object_name(reason.__class__)

        stats.inc_value(f"{stats_base_key}/count")
        stats.inc_value(f"{stats_base_key}/reason_count/{reason}")
        return new_request
    stats.inc_value(f"{stats_base_key}/max_reached")
    logger.error(
        "放弃重试 %(request)s (失败 %(retry_times)d 次): %(reason)s",
        {"request": request, "retry_times": retry_times, "reason": reason},
        extra={"spider": spider},
    )
    return None


class RetryMiddleware:
    # IOError在尝试解压空响应时由HttpCompression中间件引发
    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):
        max_retry_times = request.meta.get("max_retry_times", self.max_retry_times)
        priority_adjust = request.meta.get("priority_adjust", self.priority_adjust)
        return get_retry_request(
            request,
            reason=reason,
            spider=spider,
            max_retry_times=max_retry_times,
            priority_adjust=priority_adjust,
        )

这段代码是关于一个处理重试请求的Scrapy中间件的实现。主要解决因为暂时性问题(比如连接超时或者HTTP 500错误)导致的请求失败。下面是对每个方法的口语化中文解释:

get_retry_request

这个函数负责生成一个新的重试请求。如果一个请求因为某些临时性问题失败了,这个函数会根据设定的重试次数来决定是否生成一个新的请求来再次尝试。主要参数包括请求对象、蜘蛛实例、失败原因、最大重试次数和优先级调整等。如果达到了最大重试次数,就会返回None,表示不再重试。

简单来说,如果一个页面因为一些小问题加载失败了,这个函数就会帮你尝试重新加载一下,直到次数用尽或者加载成功为止。

RetryMiddleware

这个类是一个中间件,专门用来处理请求的重试逻辑。它继承自Scrapy的Middleware类,并根据Scrapy的全局设置来决定怎样处理失败的请求。

__init__

这个方法用来初始化RetryMiddleware类的实例。它会读取Scrapy的设置,比如是否启用重试、重试次数、需要重试的HTTP状态码和重试的优先级调整。如果没有启用重试,就会直接抛出NotConfigured异常,表示这个中间件不会被使用。

简而言之,就是根据你事先设定的规则,决定这个中间件开始工作时需要注意些什么。

from_crawler

这个类方法用于实例化中间件,它利用了Scrapy的crawler.settings来访问和使用配置文件中的设置。

换句话说,它是用来创建这个中间件实例,并确保它能按照你的设置来工作。

process_response

此方法处理每个请求的响应。如果一个响应的状态码位于需要重试的状态码列表之内,且该请求未被标记为不需要重试,则该方法会尝试重新调度请求。

这就像是,当你打开一网页失败时,浏览器会帮你刷新一下再试试。

process_exception

这个方法在请求过程中发生异常时被调用。如果遇到了定义在EXCEPTIONS_TO_RETRY中的异常,且该请求未被标记为不需要重试,此方法也会尝试重新调度该请求。

其实就是说,如果在尝试连接一个网站时遇到了问题(比如连接超时了),这个方法会让程序暂停一下,然后再试一次。

_retry

这个私有方法被process_responseprocess_exception调用,用来执行重试逻辑,判断一个请求是否应该重试,并据此生成一个新的重试请求或者放弃重试。

说白了,这个方法就是在决定,“这个请求是不是真的没救了?还是我们再给它一次机会?”。

整体来说,这段代码的作用是帮助你自动化地处理那些因为一些暂时问题失败的请求,让爬虫更加健壮、容错性更好。

你可能感兴趣的:(scrapy爬虫开发,爬虫,scrapy)