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 函數
__init__ 函數
process_response 函數
process_exception 函數
_retry 函數
get_retry_request 函數
環節總結:整個源碼分析流程到這里就結束了,接下來我們一起進入源碼重寫環節來解決下娜娜遇到的問題吧,我相信大家會豁然開朗的。
四、源碼重寫
重寫 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 模块的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 选购电脑cpu 酷睿 区别 GPU选
- 下一篇: 材质 动态自发光