若是有学过django的同窗,应该对这个名词不陌生了,在django中,中间件能够对请求作统一批量的处理python
那么在爬虫中,中间件的做用也是作批量处理的,好比把全部请求的请求头添加一个值等等等。django
因为爬虫是一个发请求,获取响应的过程,因此在scrapy框架中有两个中间件。json
在scrapy框架中所谓的中间件本质上就是一个类,里面有一些方法。cookie
固然你想应用中间件,必需要在settings文件中注册一下,优先级仍然是数值小的优先级高,通常放在上面。框架
# 爬虫中间件 SPIDER_MIDDLEWARES = { 'spider1.middlewares.Spider1SpiderMiddleware': 543, } # 下载中间件 DOWNLOADER_MIDDLEWARES = { 'spider1.middlewares.Spider1DownloaderMiddleware': 543, }
实际上中间件的方法只有四个就好了,可是框架给咱们绑定了一个爬虫打开时的信号。scrapy
from scrapy import signals class Spider1SpiderMiddleware: @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. s = cls() # 建立spider(爬虫对象)的时候,注册一个信号 # 信号: 当爬虫的打开的时候 执行 spider_opened 这个方法 crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_spider_input(self, response, spider): # 下载完成后,执行,而后交给parse处理 return None def process_spider_output(self, response, result, spider): """ 经历过parse函数以后执行 :param response: 上一次请求返回的结果 :param result: yield的对象 包含 [item/Request] 对象的可迭代对象 :param spider: 当前爬虫对象 :return: 返回Request对象 或 Item对象 """ for i in result: yield i def process_spider_exception(self, response, exception, spider): """若是执行parse抛出异常的话 会执行这个函数 默认不对异常处理交给下一个中间件处理""" pass def process_start_requests(self, start_requests, spider): """ 爬虫启动时调用 :param start_requests: 包含 Request 对象的可迭代对象 :param spider: :return: Request 对象 """ for r in start_requests: yield r def spider_opened(self, spider): # 生成爬虫日志 spider.logger.info('Spider opened: %s' % spider.name)
爬虫开始时有个起始url,这个时候会通过爬虫中间件的 process_start_requests 方法,而后去下载,接着ide
咱们看到 process_spider_input 和 process_spider_output 这两个方法中参数都有一个response参数能够判断出函数
这两个方法都是在下载完成后,拿到response以后才会执行的。网站
当下载完成后再通过爬虫中间件,这个时候会执行 process_spider_input 方法,这个时候的response中有url
包含此次请求的全部内容,其中请求的request对象也被封装成了 response.resquest 。
而后来到 spider 文件中,经历parse方法,这个时候返回值多是Request对象或者item对象,而后若是
parse 方法中抛出异常会执行爬虫中间件的 process_spider_exception 方法,通常状况下无异常,继续走爬虫
中间件中的 process_spider_output 方法。这就是爬虫中间件中方法的在什么地方执行的过程。
至于没讲到的那两个方法,是基于信号作出的关于爬虫开启时日志的拓展。对爬虫中间件无影响,后面我会将
到关于信号实现的自定义拓展。
默认下载中间件源码:
class Spider1DownloaderMiddleware: @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. # 这个方法同上,和爬虫中间件同样的功能 s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_request(self, request, spider): """ 请求须要被下载时,通过全部下载中间件的process_request调用 spider处理完成,返回时调用 :param request: :param spider: :return: None,继续往下执行,去下载 Response对象,中止process_request的执行,开始执行process_response Request对象,中止中间件的执行,将Request从新放到调度器中 raise IgnoreRequest异常,中止process_request的执行,开始执行process_exception """ return None def process_response(self, request, response, spider): """ 下载获得响应后,执行 :param request: 请求对象 :param response: 响应对象 :param spider: 爬虫对象 :return: 返回request对象,中止中间件,将Request对象从新放到调度器中 返回response对象,转交给其余中间件process_response raise IgnoreRequest 异常: 调用Request.errback """ return response def process_exception(self, request, exception, spider): """当下载处理器(download handler)或process_request() (下载中间件)抛出异常 :return None: 继续交给后续中间件处理异常 Response对象: 中止后续process_exception方法 Request对象: 中止中间件,request将会被从新调用下载 """ pass def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)
process_request(self, request, spider)
该方法是下载器中间件类中的一个方法,该方法是每一个请求从引擎发送给下载器下载以前都会通过该方法。因此该
方法常常用来处理请求头的替换,IP的更改,Cookie等的替换。
参数:
request (Request 对象) – 处理的request
spider (Spider 对象) – 该request对应的spider
返回值:
返回 None:Scrapy将继续处理该request,执行其余的中间件的相应方法,直到合适的下载器处理函数
(download handler)被调用,该request被执行(其response被下载)。
若是其返回Response对象:Scrapy将不会调用任何其余的 process_request ( ) 或 process_exception ( )
方法,或相应地下载函数; 其将返回response。
若是其返回Request对象,Scrapy则中止调用 process_request 方法并从新调度返回的request。当新返回的
request被执行后, 相应地中间件链将会根据下载的response被调用。
若是其raise一个 IgnoreRequest 异常,则安装的下载中间件的 process_exception ( ) 方法会被调用。如
果没有任何一个方法处理该异常, 则request的 errback(Request.errback) 方法会被调用。若是没有代码处
理抛出的异常, 则该异常被忽略且不记录(不一样于其余异常那样)。
process_response(self, request, response, spider)
该方法是下载器中间件类中的一个方法,该方法是每一个响应从下载器发送给spider以前都会通过该方法。
参数:
request (Request 对象) – response所对应的request
response (Response 对象) – 被处理的response
spider (Spider 对象) – response所对应的spider
返回值:
若是其返回一个Response (能够与传入的response相同,也能够是全新的对象), 该response会被其余中间件的
process_response() 方法处理。
若是其返回一个Request对象,则返回的request会被从新调度下载。处理相似于 process_request() 返回
request所作的那样。
若是其抛出一个 IgnoreRequest 异常,则调用request的 errback(Request.errback) 。若是没有代码处理
抛出的异常,则该异常被忽略且不记录(不一样于其余异常那样)。
通常门户网站都会在请求头中的User-Agent中设置反爬,因此咱们常常会在写爬虫的时候,要手动处理
User-Agent,可是通常状况下咱们都是大批量爬取,若是在这里指定一个 User-Agent 的话,也会可能被
网站检测出来是爬虫,这里咱们提供一种随机 User-Agent 的方法。
import Faker class UserAgentDownloaderMiddleware(object): def __init__(self): self.fake = faker.Faker() def process_request(self, request, spider): # 随机生成一个User-Agent useragent = self.fake.user_agent() # 更新请求头的内容 request.headers.update({"User-Agent": useragent}) return None
有时候咱们想要爬取的数据能够须要登陆才能看到,这时候咱们就须要登陆网页。登录后的网页通常都会在本地保存
该网页的登陆信息Cookies在本地。只要获取该Cookie,那么在之后跳转到其余网页的时候,只须要携带该Cookie便可。
class UserAgentDownloaderMiddleware(object): def __init__(self): self.fake = faker.Faker() def process_request(self, request, spider): # 随机生成一个User-Agent useragent = self.fake.user_agent() # 更新请求头的内容 request.headers.update({"useragent": useragent}) return None
工做中你用爬虫,必定最经常使用的就是代理,这里咱们能够自定义代理中间件
def get_proxy(): """获取代理的函数""" response = requests.get('http://134.175.188.27:5010/get/') data = response.json() return data["proxy"] class ProxyDownloaderMiddleware(object): """代理中间件""" def process_request(self, request, spider): request.meta['proxy'] = get_proxy() return None
设置了代理以后,可能会出现代理不可用,通常咱们都是在配置文件中设置重试。
RETRY_ENABLED = True # 是否开启超时重试 RETRY_TIMES = 2 # initial response + 2 retries = 3 requests 重试次数 RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408, 429] # 重试的状态码 DOWNLOAD_TIMEOUT = 1 # 1秒没有请求到数据,主动放弃