下载器中间件
引擎engine将request对象交给下载器之前,会经过下载器中间件;此时,中间件提供了一个方法 process_request,可以对 request对象进行设置随机请求头、IP代理、Cookie等;当下载器完成下载时,获得到 response对象,将它交给引擎engine的过程中,再一次经过 下载器中间件;此时,中间件提供了另一个方法 process_response;可以判断 response对象的状态码,来决定是否将 response提交给引擎
- process_request(request, spider)
- process_response(request, response, spider)
- process_exception(request, exception, spider)
方法1:process_request(request,spider)
当每个request通过下载中间件时,该方法被调用。
process_request() 必须返回其中之一: 返回 None 、返回一个 Response 对象、返回一个 Request 对象或raise IgnoreRequest 。
-
返回 None 时,,Scrapy将继续处理该request,执行其他的中间件的相应方法,直到合适的下载器处理函数(download handler)被调用, 该request被执行(其response被下载)。
-
如果其返回 Response 对象,Scrapy将不会调用 任何 其他的 process_request() 或 process_exception() 方法,或相应地下载函数; 其将返回该response。 已安装的中间件的 process_response() 方法则会在每个response返回时被调用。
-
如果其返回 Request对象,Scrapy则停止调用 process_request方法并重新调度返回的request。当新返回的request被执行后, 相应地中间件链将会根据下载的response被调用。
-
如果其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception() 方法会被调用。如果没有任何一个方法处理该异常, 则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常, 则该异常被忽略且不记录(不同于其他异常那样)。
方法2 process_response(request, response, spider)
process_request() 必须返回以下之一: 返回一个 Response 对象、 返回一个 Request 对象或raise一个 IgnoreRequest 异常。
-
如果其返回一个 Response (可以与传入的response相同,也可以是全新的对象), 该response会被在链中的其他中间件的 process_response() 方法处理。
-
如果其返回一个 Request 对象,则中间件链停止, 返回的request会被重新调度下载。处理类似于 process_request() 返回request所做的那样。
-
如果其抛出一个 IgnoreRequest 异常,则调用request的errback(Request.errback)。 如果没有代码处理抛出的异常,则该异常被忽略且不记录(不同于其他异常那样)。
方法3 process_exception(request, exception, spider)
process_exception() 应该返回以下之一: 返回 None 、 一个 Response 对象、或者一个 Request 对象。
-
如果其返回 None ,Scrapy将会继续处理该异常,接着调用已安装的其他中间件的 process_exception() 方法,直到所有中间件都被调用完毕,则调用默认的异常处理。
-
如果其返回一个 Response 对象,则已安装的中间件链的 process_response() 方法被调用。Scrapy将不会调用任何其他中间件的 process_exception() 方法。
-
如果其返回一个 Request 对象, 则返回的request将会被重新调用下载。这将停止中间件的 process_exception() 方法执行,就如返回一个response的那样。
内置的BASE 下载器中间件如下:
{
'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware': 100, #爬虫协议
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware': 300, #http认证
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware': 350, #下载超时中间件
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware': 400, #默认的headers
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 500, #user-agent中间件
'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550, #调试
'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware': 560, #ajax抓取
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware': 580, #网页数据刷新
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 590, #http压缩
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': 600, #重定向
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': 700, #cookies
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 750,# 代理
'scrapy.downloadermiddlewares.stats.DownloaderStats': 850, #下载状态码
'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware': 900, #http缓存
}
禁用中间件
想禁用 RetryMiddleware中间件,只需要在settings文件种的中间件字典里加入, ‘scrapy.downloadermiddlewares.retry.RetryMiddleware’: None,
优先级:
权重越小,越靠近引擎process_request,权重越大,越靠近下载器process_response
爬虫中间件
Spider中间件是在引擎及Spider的特定钩子,处理spider的输入(response)和输出(item及requests).
Spider Midleware 有如下三个作用:
- 我们可以在 Downmloader生成的Response发送给 Spider之前,也就是在 Response发送给Spide|之前对 Response进行处理。
- 我们可以在 Spider生成的Request发送给 Scheduler之前,也就是在 Request发送给 Schedule之前对Request 进行处理。
- 我们可以在 Spider生成的Item发送给 Item Pipeline之前,也就是在 Item发送给Item Pipeline之前对Item 进行处理
内置的SPIDER_MIDDLEMARES_BASE 爬虫中间件如下
{
'scrapy.spidermiddlewares.httperror.HttpErrorMiddleware':50,
'scrapy.spidermiddlewares.offsite.0ffsiteNiddleware':500,
'scrapy.spidermiddlewares.referer.RefererNiddleware':700,
'scrapy. spidermiddlewares.urllength.UrlLengthMiddleware':800,
'scrapy.spidermiddlewares.depth. epthMiddleware':900,
}
1、process_spider_input(response,spider)
- 如果其返回None,Scrapy将会继续处理该response,调用所有其他中间件直到spider处理该response。
- 如果其抛出一个异常(exception),Scrapy将不会调用任何其他中间件的process_spider_input()方法,并调用request的errback。errback的输出将会以另一个方向被输入到中间链中,使用process_spider_output()方法来处理,当其抛出异常时则带调用process_spider_exception()。
参数:
response(Response对象) - 被处理的response
spider(Spider对象) - 该response对应的spider
2、process_spider_output(response,result,spider)
当Spider处理response返回result,该方法被调用.
该方法必须返回包含request或item对象的可迭代对象iterable.
参数:
response(Response对象) - 生成该输出的response
result(包含Reques或Item对象的可迭代对象(iterable)) - spider返回的result
spider(Spider对象) - 其结果被处理的spider
3、process_spider_exception(response,exception,spider)
当spider或(其他spider中间件的)process_spider_input()抛出异常时,该方法被调用.
返回None:Scrapy将继续处理该异常,调用中间件链的其他中间件的process_spider_exception;
返回一个包含Response或item对象的可迭代对象(iterable):中间件;链的process_spider_output方法被调用,其他的process_spider_exception将不会被调用.
参数:
response(Response对象) - 异常被抛出时被处理的response
exception(Exception对象) - 被抛出的异常
spider(Spider对象) - 抛出异常的spider
4.process_start_requests(start_requests,spider)
该方法以spider启动的request为参数被调用,执行的过程类似于process_spider_output(),只不过其没有相关联的response并且必须返回request(不是item)。
其接受一个可迭代的对象(start_requests参数)且必须返回一个包含Request对象的可迭代对象。
当在您的spider中间件实现该方法时,您必须返回一个可迭代对象(类似于参数start_requests)且不要遍历所有的start_requests。该迭代器会很大(甚至是无限),进而导致内存溢出。Scrapy引擎再其具有能力处理start_requests时将会拉起request,因此start_requests迭代器会变得无限,而由其它参数来停止spider(例如时间限制或者item/page计数)。
参数:
start_requests(b包含Request的可迭代对象) - start requests
spider(Spider对象) - start request所属的spider
执行顺序
1、 爬虫中间件的process_start_requests方法
2、 爬虫的start_requests方法
3、 爬虫中间件的process_spider_input方法
4、 爬虫中间件的process_spider_output方法
5、 爬虫的parse方法
6、 爬虫中间件的process_spider_output方法
常用例子
1、设置随机UA
from fake_useragent import UserAgent
class UserAgentMiddleWare(object):
def process_request(self, request, spider):
request.headers['User-Agent'] = UserAgent().random
或者
from scrapy import signals
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware
import random
class RotateUserAgentMiddleware(UserAgentMiddleware):
# for more user agent strings,you can find it in http://www.useragentstring.com/pages/useragentstring.php
user_agent_list = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
"(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]
def process_request(self, request, spider):
ua = random.choice(self.user_agent_list)
if ua:
# 显示当前使用的useragent
# print("********Current UserAgent:%s************" % ua)
# 记录
spider.logger.info('Current UserAgent: ' + ua)
request.headers['User-Agent'] = ua
2、设置代理
class ProxyMiddleware(object):
def process_request(self, request, spider):
# 代理服务器(产品官网 www.16yun.cn)
proxyHost = "****"
proxyPort = "****"
# 代理验证信息
proxyUser = "*****"
proxyPass = "****"
request.meta['proxy'] = "http://{0}:{1}".format(proxyHost, proxyPort)
# 添加验证头
encoded_user_pass = base64ify(proxyUser + ":" + proxyPass)
request.headers['Proxy-Authorization'] = 'Basic ' + encoded_user_pass
# 设置IP切换头(根据需求)
tunnel = random.randint(1, 10000)
request.headers['Proxy-Tunnel'] = str(tunnel)
3、设置重连
使用IDE,现在scrapy项目中任意一个文件敲上以下代码:
from scrapy.downloadermiddlewares.retry import RetryMiddleware
该中间件的源代码如下:
class RetryMiddleware(object):
# IOError is raised by the HttpCompression middleware when trying to
# decompress an empty response
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):
retries = request.meta.get('retry_times', 0) + 1
retry_times = self.max_retry_times
if 'max_retry_times' in request.meta:
retry_times = request.meta['max_retry_times']
stats = spider.crawler.stats
if retries <= retry_times:
logger.debug("Retrying %(request)s (failed %(retries)d times): %(reason)s",
{'request': request, 'retries': retries, 'reason': reason},
extra={'spider': spider})
retryreq = request.copy()
retryreq.meta['retry_times'] = retries
retryreq.dont_filter = True
retryreq.priority = request.priority + self.priority_adjust
if isinstance(reason, Exception):
reason = global_object_name(reason.__class__)
stats.inc_value('retry/count')
stats.inc_value('retry/reason_count/%s' % reason)
return retryreq
else:
stats.inc_value('retry/max_reached')
logger.debug("Gave up retrying %(request)s (failed %(retries)d times): %(reason)s",
{'request': request, 'retries': retries, 'reason': reason},
extra={'spider': spider})
查看源码我们可以发现,对于返回http code的response,该中间件会通过process_response方法来处理,处理办法比较简单,大概是判断response.status是否在定义好的self.retry_http_codes集合中,通过向前查找,这个集合是一个列表,定义在default_settings.py文件中,定义如下:
RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408, 429]
先判断http code是否在这个集合中,如果在,就进入retry的逻辑,不在集合中就直接return response。这样就已经实现对返回http code但异常的response的处理了。
但是对另一种异常的处理方式就不一样了,刚才的异常准确的说是属于HTTP请求error(超时),而另一种异常发生的时候则是如下图这种实实在在的代码异常(不处理的话),比如TimeoutError, DNSLookupError异常,这种异常处理见👇 异常模版
4、异常模版
from twisted.internet import defer
from twisted.internet.error import TimeoutError, DNSLookupError, \
ConnectionRefusedError, ConnectionDone, ConnectError, \
ConnectionLost, TCPTimedOutError
from scrapy.http import HtmlResponse
from twisted.web.client import ResponseFailed
from scrapy.core.downloader.handlers.http11 import TunnelError
class ProcessAllExceptionMiddleware(object):
ALL_EXCEPTIONS = (defer.TimeoutError, TimeoutError, DNSLookupError,
ConnectionRefusedError, ConnectionDone, ConnectError,
ConnectionLost, TCPTimedOutError, ResponseFailed,
IOError, TunnelError)
def process_response(self,request,response,spider):
#捕获状态码为40x/50x的response
if str(response.status).startswith('4') or str(response.status).startswith('5'):
# 重新发起请求
return request
#其他状态码不处理
return response
def process_exception(self,request,exception,spider):
#捕获几乎所有的异常
if isinstance(exception, self.ALL_EXCEPTIONS):
#在日志中打印异常类型
print('Got exception: %s' % (exception))
# 重新发起请求
return request
#打印出未捕获到的异常
print('not contained exception: %s'%exception)
网友评论