使用代理IP中间件
有时我们需要编写自己的一些下载器中间件,如使用代理,更换user-agent等。
对于请求的中间件实现 process_request
(request, spider);
对于处理回复中间件实现process_response
(request, response, spider);
以及异常处理实现 process_exception
(request, exception, spider)
- process_request(request, spider)
每当scrapy进行一个request请求时,这个方法被调用。通常它可以返回1.None 2.Response对象 3.Request对象 4.抛出IgnoreRequest对象
如果返回None,Scrapy将继续处理此请求,执行所有其他中间件,直到最后调用适当的下载处理程序来执行请求(并下载其响应)。(通常返回None较常见 )
如果它返回一个Response对象,Scrapy就不会麻烦调用任何其他process_request()或process_exception()方法,或者相应的下载函数;它将返回该响应。安装的中间件的process_response()方法总是在每个响应上调用。
如果它返回一个request对象,Scrapy将停止调用process_Request方法并重新安排返回的请求。执行新返回的请求后,将对下载的响应调用相应的中间件链。
- process_response(request, response, spider)
当请求发出去返回时这个方法会被调用,它会返回 1.Response对象 2.Request对象 3.抛出IgnoreRequest对象
1.若返回Response对象,它会被下个中间件中的process_response()处理
2.若返回Request对象,中间链停止,然后返回的Request会被重新调度下载
3.抛出IgnoreRequest,回调函数 Request.errback将会被调用处理,若没处理,将会忽略
- process_exception(request, exception, spider)
当下载处理模块或process_request()抛出一个异常(包括IgnoreRequest异常)时,该方法被调用
通常返回None,它会一直处理异常
- from_crawler(cls, crawler)
这个类方法通常是访问settings和signals的入口函数
自定义中间件
-
先开启settings中的设置
DOWNLOADER_MIDDLEWARES = { 'qianmu.middlewares.RandomProxyMiddleware': 749, }
数字越小,越靠近引擎,数字越大越靠近下载器,所以数字越小的,process_request()优先处理;数字越大的,process_response()优先处理;若需要关闭某个中间件直接设为None即可
-
在middlewares.py创建中间件类
from collections import defaultdict from scrapy import signals import random from scrapy.exceptions import NotConfigured class RandomProxyMiddleware(object): def __init__(self, settings): # 预先保留在settings中的代理IP列表 self.proxies = settings.getlist('PROXIES') # Python中通过Key访问字典,当Key不存在时,会引发‘KeyError’异常。为了避免这种情况的发生, # 可以使用collections类中的defaultdict()方法来为字典提供默认值。默认类型int 初始值为0 self.stats = defaultdict(int) self.max_failed = 3 @classmethod def from_crawler(cls, crawler): if not crawler.settings.getbool('HTTPPROXY_ENABLED'): raise NotConfigured return cls(crawler.settings) def process_request(self, request, spider): if self.proxies and not request.meta.get('proxy') and request.url not in spider.start_urls: request.meta['proxy'] = random.choice(self.proxies) def process_response(self, request, response, spider): cur_proxy = request.meta.get('proxy') if response.status in (401, 403): self.stats[cur_proxy] += 1 if self.stats[cur_proxy] >= self.max_failed: self.remove_proxy(cur_proxy) del request.meta['proxy'] return request return response def process_exception(self, request, exception, spider): cur_proxy = request.meta.get('proxy') if cur_proxy and isinstance(exception, (ConnectionRefusedError, TimeoutError)): self.remove_proxy(cur_proxy) del request.meta['proxy'] # 若返回Request对象,中间链停止,然后返回的Request会被重新调度下载 return request # 若返回Response对象,它会被下个中间件中的process_response()处理 return response def remove_proxy(self, proxy): if proxy in self.proxies: self.proxies.remove(proxy)