日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Scrapy 源码分析之 RetryMiddleware 模块

發布時間:2023/12/15 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Scrapy 源码分析之 RetryMiddleware 模块 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這是「進擊的Coder」的第 689?篇技術分享

作者:TheWeiJun

來源:逆向與爬蟲的故事

閱讀本文大概需要 13 分鐘。

時隔一個多月,scrapy 章節又迎來了重大更新,今天分享的主題是 RetryMiddleware 中間件。文中若有錯誤內容,歡迎各位讀者多多指正。在閱讀的同時不要忘記點贊+關注哦??


?目錄


一、問題思考

二、文檔查尋

三、源碼分析

四、源碼重寫

五、總結分享


趣味模塊

??????娜娜是一名爬蟲工程師,最近小娜在采集數據過程中遇到了難題。原因是因為任務積壓代理超時了,所有的 request 全部無法下載了。娜娜很是苦惱,不知道如何解決這類型問題。后來小娜看了 TheWeiJun 發表的文章,存在的問題立馬迎刃而解,接下來,讓我們一起去看看他們是怎么做的吧。

一、問題思考

Question?

?①使用 scrapy 框架時,如果請求失敗,如何保證該請求成功率?

Question

?②scrapy 的重試機制是否了解,默認是幾次?在什么樣的情況下觸發?

Question

?③scrapy 重試機制,重試狀態碼有哪些,我們是否可以動態定義?

Question

?④scrapy 在重試過程中,如何實時更換代理?如何清除失效的代理?

前言:那么帶著這些問題,接下來我們對 Scrapy 源碼進行分析探索吧,我相信這篇文章會讓大家受益匪淺!


二、文檔查尋

1、查看官網文檔,搜索指定的模塊 RetryMiddleware,搜索結果如下:

說明觀察搜索結果,我們發現官方文檔中存在對?RetryMiddleware 模塊的解釋,接下來讓我們點進去,一起去看看官方說明吧。

2、點擊搜索結果,查看官方對當前模塊的說明解釋截圖如下:

說明:觀察上面的截圖,我們發現上面提到的問題大家應該已經知道了部分答案吧。但是還是不夠清晰,接下來,讓我帶大家進入源碼分析環節一探究竟吧!


三、源碼分析

RetryMiddleware 模塊源碼如下:

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', ):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("Retrying %(request)s (failed %(retry_times)d times): %(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_requestelse:stats.inc_value(f'{stats_base_key}/max_reached')logger.error("Gave up retrying %(request)s (failed %(retry_times)d times): ""%(reason)s",{'request': request, 'retry_times': retry_times, 'reason': reason},extra={'spider': spider},)return Noneclass RetryMiddleware:# IOError is raised by the HttpCompression middleware when trying to# decompress an empty responseEXCEPTIONS_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,)

環節說明:代碼一共也就 94 行,但是卻能實現多個功能。在好奇心的驅使下,我們還是對源碼進行一一講解分析吧。

  • from_crawler 函數

#?類方法,創建當前class的實例對象,參數:當前spider settings對象 @classmethod def from_crawler(cls, crawler):return?cls(crawler.settings)
  • __init__ 函數

""" 這里涉及到了settings.py配置文件中定義的一些參數。 RETRY_ENABLED:?用于開啟中間件,默認為True RETRY_TIMES: 重試次數, 默認為2 RETRY_HTTP_CODES:?遇到哪些返回狀態碼需要重試,?一個列表,默認為[500,?503,?504,?400,?408] RETRY_PRIORITY_ADJUST:調整相對于原始請求的重試請求優先級,默認為-1 """ 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')
  • process_response 函數

  • process_exception 函數

EXCEPTIONS_TO_RETRY = (defer.TimeoutError, TimeoutError, DNSLookupError,ConnectionRefusedError, ConnectionDone, ConnectError,ConnectionLost, TCPTimedOutError, ResponseFailed,IOError, TunnelError)def process_response(self, request, response, spider):#?處理request請求,確定是否需要請求重試,重試觸發機制,前面提到的問題.if request.meta.get('dont_retry', False):return response#?檢查response狀態碼是否在重試機制list中,如果存在就要調用_retry方法進行重試if response.status in self.retry_http_codes:reason = response_status_message(response.status)return self._retry(request, reason, spider) or response#?不存在會返回response,但會被spider?parse方法是過濾掉,只處理200狀態碼return responsedef process_exception(self, request, exception, spider):#?如果產生了EXCEPTIONS_TO_RETRY列表中的異常錯誤并且重試機制為開啟狀態,則會調用_retry方法進行重試。if (isinstance(exception, self.EXCEPTIONS_TO_RETRY)and not request.meta.get('dont_retry', False)):return self._retry(request, exception, spider)
  • _retry 函數

  • get_retry_request 函數

#?該方法獲取最大重試次數,和請求重試優先級,然后調用get_retry_request方法 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,) """ 讀取當前重試次數和最大重試次數進行比較,如果小于等于最大重試次數: 利用copy方法在原來的request上復制一個新request,并更新其retry_times, 并將dont_filter設為True來防止因url重復而被過濾。如果超出最大重試次數: 記錄重試失敗請求量,并放棄該請求記錄到logger日志中,logger級別為:error""" 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', ):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("Retrying %(request)s (failed %(retry_times)d times): %(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_requestelse:stats.inc_value(f'{stats_base_key}/max_reached')logger.error("Gave up retrying %(request)s (failed %(retry_times)d times): ""%(reason)s",{'request': request, 'retry_times': retry_times, 'reason': reason},extra={'spider': spider},)return None

環節總結:整個源碼分析流程到這里就結束了,接下來我們一起進入源碼重寫環節來解決下娜娜遇到的問題吧,我相信大家會豁然開朗的。


四、源碼重寫

重寫 RetryMiddleware 源碼后完整代碼如下:

class RetryMiddleware: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)response.last_content = request.metareturn 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)request.meta['proxy'] = "xxx:xxxx"request.headers['Proxy-Authorization'] = "proxyauth"return get_retry_request(request,reason=reason,spider=spider,max_retry_times=max_retry_times,priority_adjust=priority_adjust,)

重寫總結:我們只需要在 _retry 函數中實時更換代理即可,如果涉及到代理池需要剔除失敗代理的問題,同樣在 _retry 函數中刪除代理池中指定代理即可。我們還可以自定義重試機制狀態碼,大家可自行添加即可!


五、總結分享

? ? ? 通過本次案例分析,上面的幾個問題我們都已經得到了答案。今天分享到這里就結束了,歡迎大家關注下期文章,我們不見不散??。最后希望大家多多轉發、點贊、在看支持一波

End

崔慶才的新書《Python3網絡爬蟲開發實戰(第二版)》已經正式上市了!書中詳細介紹了零基礎用 Python 開發爬蟲的各方面知識,同時相比第一版新增了 JavaScript 逆向、Android 逆向、異步爬蟲、深度學習、Kubernetes 相關內容,?同時本書已經獲得 Python 之父 Guido 的推薦,目前本書正在七折促銷中!

內容介紹:《Python3網絡爬蟲開發實戰(第二版)》內容介紹

掃碼購買

點個在看你最好看

總結

以上是生活随笔為你收集整理的Scrapy 源码分析之 RetryMiddleware 模块的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。