本章將介紹scrapy框架里面的spider中間件规肴,更多內(nèi)容請(qǐng)參考: >本章將介紹Request與Response,更多內(nèi)容請(qǐng)參考:Python學(xué)習(xí)指南
scrapy框架數(shù)據(jù)流
Scrapy中的數(shù)據(jù)流由執(zhí)行引擎控制缺猛,其過(guò)程如下:
- 引擎從Spiders中獲取到的最初的要爬取的請(qǐng)求(Requests)珍特。
- 引擎安排請(qǐng)求(Requests)到調(diào)度器中,并向調(diào)度器請(qǐng)求下一個(gè)要爬取的請(qǐng)求(Requests)晓褪。
- 調(diào)度器返回下一個(gè)要爬取的請(qǐng)求(Request)給請(qǐng)求堵漱。
- 引擎從上步中得到的請(qǐng)求(Requests)通過(guò)下載器中間件(Downloader Middlewares)發(fā)送給下載器(Downloader),這個(gè)過(guò)程中下載器中間件(Downloader Middlerwares)中的
process_request()
函數(shù)就會(huì)被調(diào)用。 - 一旦頁(yè)面下載完畢涣仿,下載器生成一個(gè)該頁(yè)面的Response勤庐,并將其通過(guò)下載中間件(Downloader Middlewares)中的
process_response()
函數(shù),最后返回給引擎 - 引擎從下載器中得到上步中的Response并通過(guò)Spider中間件(Spider Middewares)發(fā)送給Spider處理好港,這個(gè)過(guò)程中Spider中間件(Spider Middlewares)中的
process_spider_input()
函數(shù)會(huì)被調(diào)用到愉镰。 - Spider處理Response并通過(guò)Spider中間件(Spider Middlewares)返回爬取到的Item及(跟進(jìn)的)新的Request給引擎,這個(gè)過(guò)程中Spider中間件(Spider Middlewares)的
process_spider_output()
函數(shù)會(huì)被調(diào)用到钧汹。 - 引擎將上步中Spider處理的及其爬取到的Item給Item管道(Piplline),將Spider處理的Requests發(fā)送給調(diào)度器丈探,并向調(diào)度器請(qǐng)求可能存在的下一個(gè)要爬取的請(qǐng)求(Requests)
- (從第二步)重復(fù)知道調(diào)度器中沒(méi)有更多的請(qǐng)求(Requests)。
Spider中間件(Spider Middlewares)
Spider中間件是介入到Scrapy中的spider處理機(jī)制的鉤子框架拔莱,可以插入自定義功能來(lái)處理發(fā)送給Spiders的response,以及spider產(chǎn)生的item和request碗降。
1.激活Spider中間件(Spider Middlewares)
要啟用Spider中間件(Spider Middlewares),可以將其加入到SPIDER_MIDDLEWARES
設(shè)置中塘秦。該設(shè)置是一個(gè)字典讼渊,鍵為中間件的路徑,值為中間件的順序(order)尊剔。
樣例:
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware' : 543,
}
SPIDER_MIDDLEWARES
設(shè)置會(huì)與Scrapy定義的SPIDER_MIDDLEWARES_BASE
設(shè)置合并(但不是覆蓋)爪幻,而后根據(jù)順序(order)進(jìn)行排序,最后得到啟用中間件的有序列表须误;第一個(gè)中間件是最靠近引擎的挨稿,最后一個(gè)中間就愛(ài)你是最靠近spider的。
關(guān)于如何分配中間的順序請(qǐng)查看SPIDER_MIDDLEWARES_BASE
設(shè)置京痢,而后根據(jù)您想要放置中間件的位置選擇一個(gè)值奶甘。由于每個(gè)中間件執(zhí)行不同的動(dòng)作,您的中間件可能會(huì)依賴于之前(或者之后)執(zhí)行的中間件历造,因此順序是最重要的甩十。
如果您想禁止內(nèi)置的(在SPIDER_MIDDLEWARES_BASE
中設(shè)置并默認(rèn)啟用的)中間件船庇,您必須在項(xiàng)目的SPIDER_MIDDLEWARES
設(shè)置中定義該中間件吭产,并將其賦值為None侣监,例如,如果您想要關(guān)閉off-site中間件:
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware': 543,
'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': None,
}
最后臣淤,請(qǐng)注意橄霉,有些中間件需要通過(guò)特定的設(shè)置來(lái)啟用。更多內(nèi)容請(qǐng)查看相關(guān)中間件文檔邑蒋。
2. 編寫自己的spider中間件
編寫中間件十分簡(jiǎn)單姓蜂,每個(gè)中間件組件是一個(gè)定義了以下一個(gè)或多個(gè)方法的Python類:
class scrapy.contrib.spidermiddleware.SpiderMiddleware
process_spider_input(response, spider)
當(dāng)response通過(guò)spider中間件時(shí),該方法被調(diào)用医吊,處理該response钱慢。
process_spider_input()
應(yīng)該返回一個(gè)None或者拋出一個(gè)異常(exception)。
- 如果其返回None卿堂,Scrapy將會(huì)繼續(xù)處理該response束莫,調(diào)用所有其他中間件直到spider處理該response。
- 如果其拋出一個(gè)異常(exception),Scrapy將不會(huì)調(diào)用任何其他中間件的process_spider_input()方法草描,并調(diào)用request的errback览绿。errback的輸出將會(huì)以另一個(gè)方向被輸入到中間鏈中,使用
process_spider_output()
方法來(lái)處理穗慕,當(dāng)其拋出異常時(shí)則帶調(diào)用process_spider_exception()
饿敲。
參數(shù):
response(Response對(duì)象) - 被處理的response
spider(Spider對(duì)象) - 該response對(duì)應(yīng)的spider
process_spider_out(response, result, spider)
當(dāng)Spider處理response返回result時(shí),該方法被調(diào)用逛绵。
process_spider_output()
必須返回包含Request或Item對(duì)象的可迭代對(duì)象(iterable)怀各。
參數(shù):
response(Response對(duì)象) - 生成該輸出的response
result(包含Reques或Item對(duì)象的可迭代對(duì)象(iterable)) - spider返回的result
spider(Spider對(duì)象) - 其結(jié)果被處理的spider
process_spider_exception(response, exception, spider)
當(dāng)spider或(其它spider中間件的)process_spider_input()拋出異常時(shí),該方法被調(diào)用
process_spider_exception()必須要么返回None术浪,要么返回一個(gè)包含Response或Item對(duì)象的可迭代對(duì)象(iterable)渠啤。
通過(guò)其返回None,Scrapy將繼續(xù)處理該異常添吗,調(diào)用中間件鏈中的其它中間件的process_spider_exception()
如果其返回一個(gè)可迭代對(duì)象沥曹,則中間件鏈的process_spider_output()
方法被調(diào)用,其他的process_spider_exception()
將不會(huì)被調(diào)用碟联。
response(Response對(duì)象) - 異常被拋出時(shí)被處理的response
exception(Exception對(duì)象) - 被拋出的異常
spider(Spider對(duì)象) - 拋出異常的spider
process_start_requests(start_requests, spider)
該方法以spider啟動(dòng)的request為參數(shù)被調(diào)用妓美,執(zhí)行的過(guò)程類似于process_spider_output()
,只不過(guò)其沒(méi)有相關(guān)聯(lián)的response并且必須返回request(不是item)鲤孵。
其接受一個(gè)可迭代的對(duì)象(start_requests參數(shù))且必須返回一個(gè)包含Request對(duì)象的可迭代對(duì)象壶栋。
當(dāng)在您的spider中間件實(shí)現(xiàn)該方法時(shí),您必須返回一個(gè)可迭代對(duì)象(類似于參數(shù)start_requests)且不要遍歷所有的start_requests普监。該迭代器會(huì)很大(甚至是無(wú)限)贵试,進(jìn)而導(dǎo)致內(nèi)存溢出琉兜。Scrapy引擎再其具有能力處理start_requests時(shí)將會(huì)拉起request,因此start_requests迭代器會(huì)變得無(wú)限,而由其它參數(shù)來(lái)停止spider(例如時(shí)間限制或者item/page計(jì)數(shù))毙玻。
參數(shù):
start_requests(b包含Request的可迭代對(duì)象) - start requests
spider(Spider對(duì)象) - start request所屬的spider
案例:下載妹子圖圖片
編寫spider實(shí)現(xiàn)代碼
1. spider文件:
##file:MeizituSpider.py
#-*- coding:utf-8 -*-
import scrapy
from scrapy.spiders import Request
import logging
import re
from cnblogSpider.items import SaveGirlImageItem
logger = logging.getLogger(__name__)
class MeiziTuSpider(scrapy.Spider):
name = "meizitu"
allowed_domains = ['meizitu.com']
user_header = {
"Referer": "http://www.meizitu.com/tag/nvshen_460_1.html",
"Upgrade-Insecure-Requests" : "1",
"User-Agent" : "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0"
}
def start_requests(self):
logging.debug("###### 妹子圖Spider開始啟動(dòng).....%s"%self)
return [Request(url="http://www.meizitu.com/tag/nvshen_460_1.html", callback=self.parse, headers = self.user_header)]
@staticmethod
def __remove_html_tags(str):
return re.sub(r'<[^>]+>', '', str)
def parse(self, response):
# print(response.body)
for picdiv in response.css('div[class="pic"]'):
image_urls = picdiv.css('a[target="_blank"] img::attr(src)').extract_first()
image_split = image_urls.split("/")
image_name = image_split[-3]+ image_split[-2]+ image_split[-1]
yield SaveGirlImageItem({
'name' : MeiziTuSpider.__remove_html_tags(picdiv.css('a[target="_blank"] img::attr(alt)').extract()[0]),#獲取這組相片的名稱
'url' : picdiv.css('a[target="_blank"] img::attr(src)').extract_first(), #獲取這組照片的鏈接
'image_urls' : [picdiv.css('a[target="_blank"] img::attr(src)').extract_first()],
'images' : image_name
})
next_page = response.xpath(u'//div[@class="navigation"]//li/a[contains(.,"下一頁(yè)")]/@href').extract_first()
if next_page is not None:
requesturl = "http://www.meizitu.com" + next_page
yield Request(requesturl, callback = self.parse, headers=self.user_header)
2. 中間件代碼:
##file:middlewares.py
import logging
###下面是妹子圖案例的spider中間件
logger = logging.getLogger(__name__)
##start_requests函數(shù)調(diào)用這個(gè)spider中間件
class ModifyStartRequest(object):
def process_start_requests(self, start_requests, spider):
logging.info("#### 22222222 #####strat_requests %s, spider %s ####"%(start_requests, spider))
last_request = []
for one_request in start_requests:
logging.info("#### one_request %s, spider %s ####"%(one_request, spider))
last_request.append(one_request)
logging.info("#### 2222222 ####last_request %s, spider %s ####"%(last_request, spider))
return last_request
#file:spiderMiddleware.py
import logging
logger = logging.getLogger(__name__)
###
class SpiderInputMiddleware(object):
def process_spider_input(self, response, spider):
logging.info("#### 3333 response %s, spider %s ####"%(response, spider))
return
class SpiderOutputMiddleware(object):
def process_spider_output(self, response, result, spider):
logging.info("#### 4444 response %s, spider %s ####" %(response, spider))
return result
3. item文件:
#file:items.py
class SaveGirlImageItem(scrapy.Item):
name = scrapy.Field()
url = scrapy.Field()
image_urls = scrapy.Field()
images = scrapy.Field()
4. settings設(shè)置:
#file:settings.py
LOG_LEVEL = "INFO"
#禁用Cookie
COOKIES_ENABLED = False
#spider中間件
SPIDER_MIDDLEWARES = {
# 'cnblogSpider.middlewares.CnblogspiderSpiderMiddleware': 543,
'cnblogSpider.middlewares.ModifyStartRequest' : 643,
'cnblogSpider.spiderMiddleware.SpiderInputMiddleware' : 743,
'cnblogSpider.spiderMiddleware.SpiderOutputMiddleware': 843
}
#管道中間件
ITEM_PIPELINES = {
'cnblogSpider.pipelines.MeizituPipelineJson' :10,
'scrapy.pipelines.images.ImagesPipeline' : 1
}
#使用圖片管道文件下載圖片
IMAGES_STORE="/home/chenqi/python/python_code/python_Spider/chapter04/cnblogs/cnblogSpider/cnblogSpider/images"
IMAGES_URLS_FIELD = "image_urls"
IMAGES_RESULT_FIELD="images"