之前讲过一篇关于scrapy的重试机制的文章,那个是针对当时那哥们的代码讲的,但是,发现后面还是有很多问题; 本章节就着scrapy的重试机制来讲一下!!!
首先,要清楚一个概念,在scrapy的中间件中,默认会有一个scrapy重试中间件;只要你在settings.py设置中写上:
RETRY_TIMES=3
那么他就会自动重试!
即使你想拦截,例如在负责控制ip的中间件中拦截他,根本拦截不下来(只有最后一次才会拦截!)
那么这个retry_times 是怎么进行运算的呢?
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)
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_response
和process_exception
调用,用来执行重试逻辑,判断一个请求是否应该重试,并据此生成一个新的重试请求或者放弃重试。
说白了,这个方法就是在决定,“这个请求是不是真的没救了?还是我们再给它一次机会?”。
整体来说,这段代码的作用是帮助你自动化地处理那些因为一些暂时问题失败的请求,让爬虫更加健壮、容错性更好。