信息服務(wù)類網(wǎng)站怎么做黑馬培訓(xùn)
前言:
之前講過(guò)一篇關(guān)于scrapy的重試機(jī)制的文章,那個(gè)是針對(duì)當(dāng)時(shí)那哥們的代碼講的,但是,發(fā)現(xiàn)后面還是有很多問(wèn)題; 本章節(jié)就著scrapy的重試機(jī)制來(lái)講一下!!!
正文:
首先,要清楚一個(gè)概念,在scrapy的中間件中,默認(rèn)會(huì)有一個(gè)scrapy重試中間件;只要你在settings.py設(shè)置中寫上:
RETRY_TIMES=3
那么他就會(huì)自動(dòng)重試!?
即使你想攔截,例如在負(fù)責(zé)控制ip的中間件中攔截他,根本攔截不下來(lái)(只有最后一次才會(huì)攔截!)
那么這個(gè)retry_times 是怎么進(jìn)行運(yùn)算的呢?
q1:明明咱們?cè)O(shè)置的是3,怎么他重試了4次???
解釋:第一次是原始請(qǐng)求,重試為0; 接著每一次都會(huì)+1,當(dāng)達(dá)到3次重試時(shí)(已經(jīng)發(fā)起了4次請(qǐng)求)的時(shí)候,就會(huì)進(jìn)入報(bào)錯(cuò)機(jī)制!
q2:面對(duì)請(qǐng)求失敗的時(shí)候,為什么def process_response攔截不了!?
解釋:因?yàn)槭莍p或者超時(shí)引起的問(wèn)題,不會(huì)走response返回體! 他直接進(jìn)入報(bào)錯(cuò)--->解決辦法:
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} 請(qǐng)求異常,嘗試重試。當(dāng)前重試次數(shù):{current_retry_times}")# 更換新的代理IPnew_proxy = self.get_proxy(spider)if new_proxy:request.meta['proxy'] = new_proxy.decode('utf-8')request.meta['retry_times'] = current_retry_times+1return request.copy()else:print("超過(guò)重試次數(shù)")self.redis.sadd(self.fail_key,request.url)
Q3:我在settings.py里面設(shè)置了重試次數(shù)為3,且每次請(qǐng)求都會(huì)從ip池中帶一個(gè)ip; 為什么前面3次重試我都沒辦法攔截這個(gè)請(qǐng)求,他自動(dòng)發(fā)起請(qǐng)求了?
解釋:在中間件中,關(guān)閉那個(gè)scrapy默認(rèn)的重試中間件! 這樣,每次請(qǐng)求的重試他就不走他中間件了,而是走你設(shè)置的中間件!!!?
DOWNLOADER_MIDDLEWARES = {
#把這個(gè)設(shè)置為NONE,關(guān)閉默認(rèn)的重試中間件'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,# 添加代理ip的中間件'爬蟲名.middlewares.RedisProxyMiddleware': 543,}
查看翻譯原重試中間件代碼:
"""
一個(gè)用于重試可能由臨時(shí)問(wèn)題如連接超時(shí)或HTTP 500錯(cuò)誤導(dǎo)致失敗的請(qǐng)求的擴(kuò)展。您可以通過(guò)修改抓取設(shè)置來(lái)改變此中間件的行為:
RETRY_TIMES - 重試失敗頁(yè)面的次數(shù)
RETRY_HTTP_CODES - 需要重試的HTTP響應(yīng)代碼失敗的頁(yè)面在抓取過(guò)程中被收集起來(lái),并在爬蟲完成抓取所有常規(guī)(非失敗)頁(yè)面后重新安排。
"""
from logging import Logger, getLogger
from typing import Optional, Unionfrom twisted.internet import defer
from twisted.internet.error import (ConnectError,ConnectionDone,ConnectionLost,ConnectionRefusedError,DNSLookupError,TCPTimedOutError,TimeoutError,
)
from twisted.web.client import ResponseFailedfrom 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_messageretry_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",
):"""返回一個(gè)新的:class:`~scrapy.Request`對(duì)象來(lái)重試指定的請(qǐng)求,如果指定請(qǐng)求的重試次數(shù)已耗盡,則返回``None``。例如,在一個(gè):class:`~scrapy.Spider`回調(diào)中,您可以如下使用它::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* 是請(qǐng)求重試的:class:`~scrapy.Spider`實(shí)例。它用來(lái)訪問(wèn):ref:`設(shè)置 <topics-settings>`和:ref:`統(tǒng)計(jì) <topics-stats>`,以及提供額外的日志上下文(參考:func:`logging.debug`)。*reason* 是一個(gè)字符串或一個(gè):class:`Exception`對(duì)象,表明為什么需要重試請(qǐng)求。它被用來(lái)命名重試統(tǒng)計(jì)信息。*max_retry_times* 是一個(gè)數(shù)字,決定了*request*可以被重試的最大次數(shù)。如果未指定或``None``,則從請(qǐng)求的: reqmeta:`max_retry_times`元鍵中讀取。如果:reqmeta:`max_retry_times`元鍵未定義或``None``,則從: setting:`RETRY_TIMES`設(shè)置中讀取。*priority_adjust* 是一個(gè)數(shù)字,決定了新請(qǐng)求的優(yōu)先級(jí)如何相對(duì)于*request*進(jìn)行調(diào)整。如果未指定,則從: setting:`RETRY_PRIORITY_ADJUST`設(shè)置中讀取。*logger* 是用于記錄消息的logging.Logger對(duì)象*stats_base_key* 是字符串,用作重試相關(guān)作業(yè)統(tǒng)計(jì)的基礎(chǔ)鍵"""settings = spider.crawler.settingsstats = spider.crawler.statsretry_times = request.meta.get("retry_times", 0) + 1if 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_timesnew_request.dont_filter = Trueif priority_adjust is None:priority_adjust = settings.getint("RETRY_PRIORITY_ADJUST")new_request.priority = request.priority + priority_adjustif 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_requeststats.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 Noneclass RetryMiddleware:# IOError在嘗試解壓空響應(yīng)時(shí)由HttpCompression中間件引發(fā)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 NotConfiguredself.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")@classmethoddef from_crawler(cls, crawler):return cls(crawler.settings)def process_response(self, request, response, spider):if request.meta.get("dont_retry", False):return responseif response.status in self.retry_http_codes:reason = response_status_message(response.status)return self._retry(request, reason, spider) or responsereturn responsedef 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,)
這段代碼是關(guān)于一個(gè)處理重試請(qǐng)求的Scrapy中間件的實(shí)現(xiàn)。主要解決因?yàn)闀簳r(shí)性問(wèn)題(比如連接超時(shí)或者HTTP 500錯(cuò)誤)導(dǎo)致的請(qǐng)求失敗。下面是對(duì)每個(gè)方法的口語(yǔ)化中文解釋:
get_retry_request
這個(gè)函數(shù)負(fù)責(zé)生成一個(gè)新的重試請(qǐng)求。如果一個(gè)請(qǐng)求因?yàn)槟承┡R時(shí)性問(wèn)題失敗了,這個(gè)函數(shù)會(huì)根據(jù)設(shè)定的重試次數(shù)來(lái)決定是否生成一個(gè)新的請(qǐng)求來(lái)再次嘗試。主要參數(shù)包括請(qǐng)求對(duì)象、蜘蛛實(shí)例、失敗原因、最大重試次數(shù)和優(yōu)先級(jí)調(diào)整等。如果達(dá)到了最大重試次數(shù),就會(huì)返回None
,表示不再重試。
簡(jiǎn)單來(lái)說(shuō),如果一個(gè)頁(yè)面因?yàn)橐恍┬?wèn)題加載失敗了,這個(gè)函數(shù)就會(huì)幫你嘗試重新加載一下,直到次數(shù)用盡或者加載成功為止。
RetryMiddleware
這個(gè)類是一個(gè)中間件,專門用來(lái)處理請(qǐng)求的重試邏輯。它繼承自Scrapy的Middleware
類,并根據(jù)Scrapy的全局設(shè)置來(lái)決定怎樣處理失敗的請(qǐng)求。
__init__
這個(gè)方法用來(lái)初始化RetryMiddleware
類的實(shí)例。它會(huì)讀取Scrapy的設(shè)置,比如是否啟用重試、重試次數(shù)、需要重試的HTTP狀態(tài)碼和重試的優(yōu)先級(jí)調(diào)整。如果沒有啟用重試,就會(huì)直接拋出NotConfigured
異常,表示這個(gè)中間件不會(huì)被使用。
簡(jiǎn)而言之,就是根據(jù)你事先設(shè)定的規(guī)則,決定這個(gè)中間件開始工作時(shí)需要注意些什么。
from_crawler
這個(gè)類方法用于實(shí)例化中間件,它利用了Scrapy的crawler.settings
來(lái)訪問(wèn)和使用配置文件中的設(shè)置。
換句話說(shuō),它是用來(lái)創(chuàng)建這個(gè)中間件實(shí)例,并確保它能按照你的設(shè)置來(lái)工作。
process_response
此方法處理每個(gè)請(qǐng)求的響應(yīng)。如果一個(gè)響應(yīng)的狀態(tài)碼位于需要重試的狀態(tài)碼列表之內(nèi),且該請(qǐng)求未被標(biāo)記為不需要重試,則該方法會(huì)嘗試重新調(diào)度請(qǐng)求。
這就像是,當(dāng)你打開一網(wǎng)頁(yè)失敗時(shí),瀏覽器會(huì)幫你刷新一下再試試。
process_exception
這個(gè)方法在請(qǐng)求過(guò)程中發(fā)生異常時(shí)被調(diào)用。如果遇到了定義在EXCEPTIONS_TO_RETRY
中的異常,且該請(qǐng)求未被標(biāo)記為不需要重試,此方法也會(huì)嘗試重新調(diào)度該請(qǐng)求。
其實(shí)就是說(shuō),如果在嘗試連接一個(gè)網(wǎng)站時(shí)遇到了問(wèn)題(比如連接超時(shí)了),這個(gè)方法會(huì)讓程序暫停一下,然后再試一次。
_retry
這個(gè)私有方法被process_response
和process_exception
調(diào)用,用來(lái)執(zhí)行重試邏輯,判斷一個(gè)請(qǐng)求是否應(yīng)該重試,并據(jù)此生成一個(gè)新的重試請(qǐng)求或者放棄重試。
說(shuō)白了,這個(gè)方法就是在決定,“這個(gè)請(qǐng)求是不是真的沒救了?還是我們?cè)俳o它一次機(jī)會(huì)?”。
整體來(lái)說(shuō),這段代碼的作用是幫助你自動(dòng)化地處理那些因?yàn)橐恍簳r(shí)問(wèn)題失敗的請(qǐng)求,讓爬蟲更加健壯、容錯(cuò)性更好。