scrapy组件首先我们来看一下scrapy官网提供的新结构图。原理很容易理解。这些是一个通用的爬虫框架应该具备的一些基本组件。上一篇博客讲到项目流水线(也就是图中的ITEMPIPELINES),可以看到中间的引擎(ENGINE)将item传递给项目流水线,也就是让项目流水线处理捕获的内容。另外,图中所谓的组件只是抽象出来的东西,更容易让人理解。其实这些都是python类实例化的东西。下载中间件在引擎和下载器中间,就像引擎和下载器有一条管道,管道上有两个路障是下载中间件,只有请求(REQUESTS)和响应(RESPONSE),所以这两个路障很明显的作用就是拦截请求和响应(至于拦截之后你想干??什么,你想干什么就干什么,比如改变请求或响应内容,改变请求头或响应头,或者做没什么,记录下来就让它过去吧)。蜘蛛中间件位于蜘蛛和引擎之间。这里所说的spider就是我们写在spider目录下的spider类。写过蜘蛛的应该都知道,响应一般是在蜘蛛中处理的(默认解析的是parse方法),返回item,或者返回(准确点应该是迭代)请求(scrapy.Request)。图中也很明显,请求(REQUESTS)、响应(RESPONSE)和项目在蜘蛛和引擎之间传递。响应是由下载器从网站上下载下来并传递给引擎由蜘蛛处理,而项目一般由蜘蛛从响应内容中解析出来并传递给引擎由项目管道处理,而请求一般是spider处理过的start_urls中定义的URL和spider解析出的新URL准备好传递给引擎,由调度器统一管理。上面一堆废话说:中间件的作用就是拦截两个组件传递的内容。看完这些组件的功能和使用方法,你就会发现为什么这么麻烦了。可以看到我在使用requests库的时候只需要请求和得到响应就可以直接处理了。引擎和调度器觉得没有存在的意义。这个需要慢慢理解,或者自己写一个简单的通用爬虫框架就知道了。Middleware写中间件启用DOWNLOADER_MIDDLEWARES={'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware':None,'newspider.middlewares.UAMiddleware':500#newspider是scrapy项目名,也就是settings.py中BOT_NAME的值,UAMiddleware如何choosethevalueofthedefinedmiddlewarename}dictionary,500的值可以改成其他值吗?先看看官方是怎么说的:scrapy会将DOWNLOADER_MIDDLEWARES设置和DOWNLOADER_MIDDLEWARES_BASE设置合并,然后按照value的顺序排序,得到最终排序的启用中间件列表:value最小的中间件离引擎更近,中间件与最大的下载器拉近距离。也就是说,每个中间件的process_request()方法会以升序调用,每个中间件的process_response()方法会按降序调用。要确定分配给中间件的顺序,请查看DOWNLOADER_MIDDLEWARES_BASE设置并根据要插入中间件的位置选择一个值。顺序很重要,因为每个中间件都做不同的事情,并且您的中间件可能取决于应用的某些先前(或后续)中间件。如果你想禁用内置中间件(DOWNLOADER_MIDDLEWARES_BASE默认定义在启用的中间件中),你必须在你的项目中将DOWNLOADER_MIDDLEWARES设置为None。上面有一句话很重要:每个中间件的process_request()方法会按升序调用,每个中间件的process_response()方法会按降序调用。这个应该说清楚怎么取值,但是一般中间件没有一定的先后顺序,只要不冲突就行。比如:你写了一个中间件newspider.middlewares.UAMiddleware,但是你在启用的时候并没有禁用内置的UserAgentMiddleware,那怎么让你的中间件生效呢?代码如下:DOWNLOADER_MIDDLEWARES={#'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware':None,'newspider.middlewares.UAMiddleware':501#newspider是scrapy项目的名字,也就是设置中BOT_NAME的值。py,而UAMiddleware是定义中间组件的名称}因为UserAgentMiddleware默认值是500,因为修改请求头在process_request中,process_request是增量调用的,所以会先调用内置的UserAgentMiddleware,并且调用你定义的UAMiddleware时会覆盖内置的UAMiddleware。但是因为很多人都是以字典访问的形式修改请求头(request.headers["User-Agent"]=""),即使你把值设置为499,结果还是你设置的User-Agent.而不是默认的内置。其实看内置代码就可以理解:源码中使用了request.headers.setdefault(b'User-Agent',self.user_agent),key不存在时设置setdefault,不存在如果存在则设置。内置内置内置下载下载器DefaultHeadersMiddleware':400,'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware':500,'scrapy.downloadermiddlewares.retry.RetryMiddleware':550,'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware':560,'scrapy.downloadermiddlewares.redirectMiddleware'Refresh:580,'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware':590,'scrapy.downloadermiddlewares.redirect.RedirectMiddleware':600,'scrapy.downloadermiddlewares.cookies.CookiesMiddleware':700,'scrapy.downloadermiddlewares.httpproxy.Http:Proxy750Middleware','scrapy.downloadermiddlewares.stats.DownloaderStats':850,'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware':900,}写下载中间件process_request(request,spider):所有请求都会调用这个方法process_response(request,response,spider):这里的参数比上面的response多,它必须是process_exception(request,exception,spider)用来处理response:handleexceptionfrom_crawler(cls,crawler):从settings.py获取配置假设我们需要一个自动管理cookie的中间件,应该怎么写呢?首先,管理cookie一般是用服务器返回的Set-cookie更新保存的cookie伪代码如下:fromscrapy.exceptionsimportNotConfiguredclassCookieMiddleware(object):def__init__(self):#其实cookies不能用字典保存,因为cookies可以包含相同的key#这里只是简单演示,正常你可以使用scrapy.http.cookies.CookieJarself.cookies={}defprocess_request(request,spider):self.addcookies(request,self.cookies)defprocess_response(request,response,spider):newcookie=response.headers.getlist('Set-Cookie')self.updatecookies(self.cookies,newcookie)returnresponsedefprocess_exception(request,exception,spider):passdefaddcookies(self,request,cookies):'''添加cookie到请求'''passdefupdatecookie(self,cookies,cookie):'''更新cookie字典(object)'''pass@classmethoddeffrom_crawler(cls,crawler):ifnotcrawler.settings.get('是否启用cookies'):#如果这个方法在__init__之前抛出异常,类中的所有方法都不会被执行raiseNotConfigured其实scrapy内置了cookies管理的中间件,可以参考源码:https://docs.scrapy.org/en/la...想要一个随机的User-Agent也很简单中间件,只需要在process_request方法中更改请求头即可。其实process_request和process_response这两个方法是有返回值的。上面的例子只修改了请求头和响应头,所以不涉及返回值。如果我们需要对请求和响应作为一个整体进行更改,我们需要重构请求和响应。这里最典型的例子就是selenium中间件。Spider示例代码:importscrapyfromseleniumimportwebdriverclassSeleniumSpider(scrapy.Spider):name='seleniumspider'start_urls=['https://example.com']#实例化浏览器对象,因为浏览器对象只需要实例化一次,所以在中间件中没有实例化bro=webdriver.Chrome(executable_path=r'E:\spiderman\day13\wynews\wynews\wynews\chromedriver.exe')defparse(self,response):'''andnormal同样,howtoparsehowtoparse'''passselenium中间件示例代码(只是一个简单的演示,为了更好的理解如何编写中间件):fromselenium.common.exceptionsimportTimeoutExceptionfromscrapy.httpimportHtmlResponseclassSeleniumMiddleware(object):def__init__(self,timeout=None,options=None):passdefprocess_reqeust(self,request,spider):尝试:url=request.urlspider.bro.get(url)returnHtmlResponse(url=url,body=spider.bro.page_source,request=request,encoding='utf-8',status=200)除了TimeoutException:返回HtmlResponse(url=url,status=500,request=request)@classmethoddeffrom_crawler(cls,crawler):pass代码中,HtmlResponse类就是我们新建的response,所以之前spider处理的response就是这个response。我在这里有一个问题?为什么response要在process_reqeust方法中返回,不可以在process_response中吗?其实也是可以的。不同的是,如果process_reqeust返回,其他中间件中的process_reqeust和process_exception方法会被忽略,只会执行其他中间件的process_response方法。试想一下,如果你还有一个cookie管理中间件,process_reqeust的操作和SeleniumMiddleware是矛盾的,所以我们不得不在process_reqeust中返回response,让其他中间件的process_reqeust方法失效。返回值process_reqeust方法的返回值为None:如果返回None,则一切正常。传递请求对象:如果返回请求对象,scrapy将停止调用下一个中间件并重新处理请求对象(即引擎重新管理)响应对象:...,Scrapy将不再调用process_request()或其他中间件的process_exception()方法,会直接调用其他中间件的process_response方法IgnoreRequest异常:依次调用所有中间件的process_exception方法。必须返回一个对象,不能是None)response对象:process_response方法传递给下一个中间件request对象:scrapy会停止调用下一个中间件,重新处理request对象(即由引擎重新管理)IgnoreRequest异常:依次调用所有中间件的process_exception方法。process_exception方法的返回值为None:继续传递request对象:scrapy会停止调用下一个中间件的process_exception方法,重新处理request对象(即由引擎重新管理)response对象:停止传递process_exception在下一个中间件开始调用所有process_response方法。蜘蛛中间件process_spider_input(response,spider):所有请求都会调用这个方法process_spider_output(response,result,spider):蜘蛛解析response后,调用这个方法,result是解析的结果(一个可迭代对象),可能是项目或请求对象。process_spider_exception(response,exception,spider):进程异常process_start_requests(start_requests,spider):同process_spider_output,只是处理spider中start_requests方法返回的from_cr的结果awler(cls,crawler):...它也拦截请求和响应,那和下载中间件有什么区别呢?因为下载中间件连接了引擎和下载器,如果修改请求,只会影响下载器返回的结果。如果修改响应,会影响蜘蛛处理。蜘蛛中间件连接到引擎和蜘蛛。如果请求被修改,会影响到整个scrapy请求,因为所有的scrapy请求都来自于spider,当然也包括scheduler和downloader。如果你修改响应,它只会影响蜘蛛的解析,因为响应是由引擎传递给蜘蛛的。上面说的有点乱,我们来梳理一下这两个的功能:蜘蛛中间件:记录深度,丢弃非200状态码响应,丢弃非指定域名请求等下载中间件:修改请求头,修改响应,管理cookies,丢弃非指定域名请求等200状态码响应,丢弃非指定域名请求等丢弃非指定域名请求一般使用spider中间件。如果使用下载中间件,会导致引擎和调度器的无效任务较多。我认为它应该用于丢弃非200状态代码响应。中间件,不过scrapy内置使用的是spider中间件,可能是我理解不够。Scrapy已经提供了很多内置的中间件,只需要启用它即可。另外,蜘蛛中间件一般用于操作蜘蛛返回的请求,而下载中间件用于操作向互联网发起请求和返回的响应。更多示例,可以查看一些内置中间件的源码。蜘蛛中间件一般不需要自己写,使用几个内置的中间件就可以了
