crawler(七):Scrapy的Request和Response、Files Pipeline、Images Pipeline
請(qǐng)求和響應(yīng)
Scrapy的Request 和Response對(duì)象用于爬網(wǎng)網(wǎng)站。
通常,Request對(duì)象在爬蟲(chóng)程序中生成并傳遞到系統(tǒng),直到它們到達(dá)下載程序,后者執(zhí)行請(qǐng)求并返回一個(gè)Response對(duì)象,該對(duì)象返回到發(fā)出請(qǐng)求的爬蟲(chóng)程序。
兩個(gè)類(lèi)Request和Response類(lèi)都有一些子類(lèi),它們添加基類(lèi)中不需要的功能。這些在下面的請(qǐng)求子類(lèi)和 響應(yīng)子類(lèi)中描述。
一個(gè)Request對(duì)象表示一個(gè)HTTP請(qǐng)求,它通常是在爬蟲(chóng)生成,并由下載執(zhí)行,從而生成Response。
常用參數(shù):
- url(string) - 此請(qǐng)求的網(wǎng)址
- callback(callable) - 將使用此請(qǐng)求的響應(yīng)(一旦下載)作為其第一個(gè)參數(shù)調(diào)用的函數(shù)。有關(guān)更多信息,請(qǐng)參閱下面的將附加數(shù)據(jù)傳遞給回調(diào)函數(shù)。如果請(qǐng)求沒(méi)有指定回調(diào),parse()將使用spider的 方法。請(qǐng)注意,如果在處理期間引發(fā)異常,則會(huì)調(diào)用errback。
- method(string) - 此請(qǐng)求的HTTP方法。默認(rèn)為’GET’。
- meta(dict) - 屬性的初始值Request.meta。如果給定,在此參數(shù)中傳遞的dict將被淺復(fù)制。
- headers(dict) - 這個(gè)請(qǐng)求的頭。dict值可以是字符串(對(duì)于單值標(biāo)頭)或列表(對(duì)于多值標(biāo)頭)。如果 None作為值傳遞,則不會(huì)發(fā)送HTTP頭。
- body(str或unicode) - 請(qǐng)求體。如果unicode傳遞了a,那么它被編碼為 str使用傳遞的編碼(默認(rèn)為utf-8)。如果 body沒(méi)有給出,則存儲(chǔ)一個(gè)空字符串。不管這個(gè)參數(shù)的類(lèi)型,存儲(chǔ)的最終值將是一個(gè)str(不會(huì)是unicode或None)。
- cookie(dict或list) - 請(qǐng)求cookie。這些可以以兩種形式發(fā)送。
- dont_filter(boolean) - 表示此請(qǐng)求不應(yīng)由調(diào)度程序過(guò)濾。當(dāng)您想要多次執(zhí)行相同的請(qǐng)求時(shí)忽略重復(fù)過(guò)濾器時(shí)使用。小心使用它,或者你會(huì)進(jìn)入爬行循環(huán)。默認(rèn)為False。
- priority(int) - 此請(qǐng)求的優(yōu)先級(jí)(默認(rèn)為0)。調(diào)度器使用優(yōu)先級(jí)來(lái)定義用于處理請(qǐng)求的順序。具有較高優(yōu)先級(jí)值的請(qǐng)求將較早執(zhí)行。允許負(fù)值以指示相對(duì)低優(yōu)先級(jí)。
- encoding(string) - 此請(qǐng)求的編碼(默認(rèn)為’utf-8’)。此編碼將用于對(duì)URL進(jìn)行百分比編碼,并將正文轉(zhuǎn)換為str(如果給定unicode)。
Request中meta參數(shù)的作用是傳遞信息給下一個(gè)函數(shù),使用過(guò)程可以理解成:
把需要傳遞的信息賦值給這個(gè)叫meta的變量, 但meta只接受字典類(lèi)型的賦值,因此 要把待傳遞的信息改成“字典”的形式,即: meta={'key1':value1,'key2':value2}如果想在下一個(gè)函數(shù)中取出value1, 只需得到上一個(gè)函數(shù)的meta['key1']即可, 因?yàn)閙eta是隨著Request產(chǎn)生時(shí)傳遞的, 下一個(gè)函數(shù)得到的Response對(duì)象中就會(huì)有meta, 即response.meta, 取value1則是value1=response.meta['key1'] class example(scrapy.Spider):name='example'allowed_domains=['example.com']start_urls=['http://www.example.com']def parse(self,response):#從start_urls中分析出的一個(gè)網(wǎng)址賦值給urlurl=response.xpath('.......').extract()#ExamleClass是在items.py中定義的,下面會(huì)寫(xiě)出。"""記住item本身是一個(gè)字典"""item=ExampleClass()item['name']=response.xpath('.......').extract()item['htmlurl']=response.xpath('.......').extract()"""通過(guò)meta參數(shù),把item這個(gè)字典,賦值給meta中的'key'鍵(記住meta本身也是一個(gè)字典)。Scrapy.Request請(qǐng)求url后生成一個(gè)"Request對(duì)象",這個(gè)meta字典(含有鍵值'key','key'的值也是一個(gè)字典,即item)會(huì)被“放”在"Request對(duì)象"里一起發(fā)送給parse2()函數(shù) """yield Request(url,meta={'key':item},callback='parse2')def parse2(self,response):item=response.meta['key']"""這個(gè)response已含有上述meta字典,此句將這個(gè)字典賦值給item,完成信息傳遞。這個(gè)item已經(jīng)和parse中的item一樣了"""item['text']=response.xpath('.......').extract()#item共三個(gè)鍵值,到這里全部添加完畢了yield itemmeta是淺復(fù)制,必要時(shí)需要深復(fù)制。
import copy meta={'key':copy.deepcopy('value')}meta是一個(gè)dict,主要是用解析函數(shù)之間傳遞值,一種常見(jiàn)的情況:在parse中給item某些字段提取了值,但是另外一些值需要在parse_item中提取,這時(shí)候需要將parse中的item傳到parse_item方法中處理,顯然無(wú)法直接給parse_item設(shè)置而外參數(shù)。 Request對(duì)象接受一個(gè)meta參數(shù),一個(gè)字典對(duì)象,同時(shí)Response對(duì)象有一個(gè)meta屬性可以取到相應(yīng)request傳過(guò)來(lái)的meta。所以解決上述問(wèn)題可以這樣做:
def parse(self, response):# item = ItemClass()yield Request(url, meta={'item': item}, callback=self.parse_item) def parse(self, response):item = response.meta['item']item['field'] = valueyield itemrequest和response之間如何傳參
有些時(shí)候需要將兩個(gè)頁(yè)面的內(nèi)容合并到一個(gè)item里面,這時(shí)候就需要在yield scrapy.Request的同時(shí),傳遞一些參數(shù)到一下頁(yè)面中。這時(shí)候可以這樣操作。
request=scrapy.Request(houseurl,method='GET',callback=self.showhousedetail)request.meta['biid']=biidyield requestdef showhousedetail(self,response):house=HouseItem()house['bulidingid']=response.meta['biid']Items
爬取的主要目標(biāo)就是從非結(jié)構(gòu)性的數(shù)據(jù)源提取結(jié)構(gòu)性數(shù)據(jù),例如網(wǎng)頁(yè)。 Scrapy spider可以以python的dict來(lái)返回提取的數(shù)據(jù).雖然dict很方便,并且用起來(lái)也熟悉,但是其缺少結(jié)構(gòu)性,容易打錯(cuò)字段的名字或者返回不一致的數(shù)據(jù),尤其在具有多個(gè)spider的大項(xiàng)目中。。
為了定義常用的輸出數(shù)據(jù),Scrapy提供了Item 類(lèi)。 Item 對(duì)象是種簡(jiǎn)單的容器,保存了爬取到得數(shù)據(jù)。 其提供了 類(lèi)似于詞典(dictionary-like)的API以及用于聲明可用字段的簡(jiǎn)單語(yǔ)法。
聲明Item
Item使用簡(jiǎn)單的class定義語(yǔ)法以及 Field 對(duì)象來(lái)聲明。例如:
Item Pipeline
當(dāng)Item在Spider中被收集之后,它將會(huì)被傳遞到Item Pipeline,一些組件會(huì)按照一定的順序執(zhí)行對(duì)Item的處理。
每個(gè)item pipeline組件(有時(shí)稱之為“Item Pipeline”)是實(shí)現(xiàn)了簡(jiǎn)單方法的Python類(lèi)。他們接收到Item并通過(guò)它執(zhí)行一些行為,同時(shí)也決定此Item是否繼續(xù)通過(guò)pipeline,或是被丟棄而不再進(jìn)行處理。
編寫(xiě)你自己的item pipeline
編寫(xiě)你自己的item pipeline很簡(jiǎn)單,每個(gè)item pipiline組件是一個(gè)獨(dú)立的Python類(lèi),同時(shí)必須實(shí)現(xiàn)以下方法:
pipeline區(qū)分傳來(lái)Items
各個(gè)頁(yè)面都會(huì)封裝items并將item傳遞給pipelines來(lái)處理,而pipelines接收的入口只有一個(gè)就是
def process_item(self, item, spider)函數(shù)spider 對(duì)應(yīng)相應(yīng)的爬蟲(chóng),調(diào)用spider.name也可區(qū)分來(lái)自不同爬蟲(chóng)的item
def process_item(self, item, spider):if str(type(item))=="<class 'rishome.items.RishomeItem'>":self.saverishome(item)if str(type(item))=="<class 'rishome.items.BulidingItem'>":self.savebuliding(item)if str(type(item))=="<class 'rishome.items.HouseItem'>":self.savehouse(item)return item # 必須實(shí)現(xiàn)返回spider 對(duì)應(yīng)相應(yīng)的爬蟲(chóng),調(diào)用spider.name也可區(qū)分來(lái)自不同爬蟲(chóng)的item
def process_item(self, item, spider):if spider.name == "XXXX":pass啟用一個(gè)Item Pipeline組件
為了啟用一個(gè)Item Pipeline組件,你必須將它的類(lèi)添加到ITEM_PIPELINES配置,就像下面這個(gè)例子:
ITEM_PIPELINES = {'myproject.pipelines.PricePipeline': 300,'myproject.pipelines.JsonWriterPipeline': 800, }分配給每個(gè)類(lèi)的整型值,確定了他們運(yùn)行的順序,item按數(shù)字從低到高的順序,通過(guò)pipeline,通常將這些數(shù)字定義在0-1000范圍內(nèi)。
下載及處理文件和圖片
Scrapy為下載item中包含的文件(比如在爬取到產(chǎn)品時(shí),同時(shí)也想保存對(duì)應(yīng)的圖片)提供了一個(gè)可重用的item pipelines . 這些pipeline有些共同的方法和結(jié)構(gòu)(我們稱之為media pipeline)。一般來(lái)說(shuō)你會(huì)使用Files Pipeline或者 Images Pipeline.
使用Files Pipeline
當(dāng)使用 FilesPipeline ,典型的工作流程如下所示:
在一個(gè)爬蟲(chóng)里,你抓取一個(gè)項(xiàng)目,把其中圖片的URL放入 file_urls組內(nèi)。
項(xiàng)目從爬蟲(chóng)內(nèi)返回,進(jìn)入項(xiàng)目管道。
當(dāng)項(xiàng)目進(jìn)入 FilesPipeline,file_urls組內(nèi)的URLs將被Scrapy的調(diào)度器和下載器(這意味著調(diào)度器和下載器的中間件可以復(fù)用)安排下載,當(dāng)優(yōu)先級(jí)更高,會(huì)在其他頁(yè)面被抓取前處理。項(xiàng)目會(huì)在這個(gè)特定的管道階段保持“l(fā)ocker”的狀態(tài),直到完成文件的下載(或者由于某些原因未完成下載)。
當(dāng)文件下載完后,另一個(gè)字段(files)將被更新到結(jié)構(gòu)中。這個(gè)組將包含一個(gè)字典列表,其中包括下載文件的信息,比如下載路徑、源抓取地址(從file_urls組獲得)和圖片的校驗(yàn)碼(checksum)。 files列表中的文件順序?qū)⒑驮?file_urls組保持一致。如果某個(gè)圖片下載失敗,將會(huì)記錄下錯(cuò)誤信息,圖片也不會(huì)出現(xiàn)在 files 組中。
import scrapyclass MyItem(scrapy.Item):# ... other item fields ...image_urls = scrapy.Field()images = scrapy.Field()在配置文件 settings.py中配置FILES_STORE,這個(gè)配置用來(lái)設(shè)置文件下載下來(lái)的路徑。
啟動(dòng)pipeline,
對(duì)于 Files Pipeline, 使用:
對(duì)于 Images Pipeline, 使用:
ITEM_PIPELINES = {'scrapy.pipeline.images.ImagesPipeline': 1}接著IMAGES_STORE設(shè)置為一個(gè)有效的文件夾,用來(lái)存儲(chǔ)下載的圖片。 否則管道將保持禁用狀態(tài),即使你在ITEM_PIPELINES 設(shè)置中添加了它。
對(duì)于Files Pipeline, 設(shè)置 FILES_STORE
對(duì)于Images Pipeline, 設(shè)置 IMAGES_STORE
IMAGES_STORE = '/path/to/valid/dir'其中:
- <IMAGES_STORE> 是定義在IMAGES_STORE 設(shè)置里的文件夾
- full是用來(lái)區(qū)分圖片和縮略圖(如果使用的話)的一個(gè)子文件夾。詳情參見(jiàn) 針對(duì)圖片生成縮略圖.
1 傳統(tǒng)的Scrapy框架圖片下載方式
創(chuàng)建項(xiàng)目:
改寫(xiě)settings.py
-
不遵守robots協(xié)議
# Obey robots.txt rules ROBOTSTXT_OBEY = False -
設(shè)置請(qǐng)求頭
# Override the default request headers: DEFAULT_REQUEST_HEADERS = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language': 'en', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36' }
改寫(xiě)items.py
import scrapyclass BmwItem(scrapy.Item):category = scrapy.Field()image_urls = scrapy.Field()images = scrapy.Field()改寫(xiě)bmw5.py
# -*- coding: utf-8 -*- import scrapy from bmw.items import BmwItemclass Bmw5Spider(scrapy.Spider):name = 'bmw5'allowed_domains = ['car.autohome.com.cn']start_urls = ['https://car.autohome.com.cn/pic/series/65.html']def parse(self, response):# SelectorList -> listuiboxs = response.xpath("//div[@class='uibox']")[1:]for uibox in uiboxs:category = uibox.xpath(".//div[@class='uibox-title']/a/text()").get()urls = uibox.xpath(".//ul/li/a/img/@src").getall()# for url in urls:# url = response.urljoin(url)# print(url)urls = list(map(lambda url:response.urljoin(url),urls))item = BmwItem(category=category,image_urls=urls)yield item改寫(xiě)pipelines.py
# -*- coding: utf-8 -*-# Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html import os from urllib import request#導(dǎo)入兩個(gè)庫(kù) from scrapy.pipelines.images import ImagesPipeline from bmw import settingsclass BmwPipeline(object):def __init__(self):self.path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')if not os.path.exists(self.path):os.mkdir(self.path)def process_item(self, item, spider):category = item['category']urls = item['urls']category_path = os.path.join(self.path,category)if not os.path.exists(category_path):os.mkdir(category_path)for url in urls:image_name = url.split('_')[-1]request.urlretrieve(url,os.path.join(category_path,image_name))return item測(cè)試py
from scrapy import cmdline cmdline.execute('scrapy crawl bmw5'.split())Scrapy框架提供了兩個(gè)中間件、下載文件的Files pipeline 和下載圖片的Image pipeline
下載文件的Files pipeline
使用步驟:
定義好一個(gè)item,然后定義兩個(gè)屬性file_urls 和 files , file_urls是用來(lái)存儲(chǔ)需要下載的文件的url鏈接,列表類(lèi)型
當(dāng)文件下載完成后,會(huì)把文件下載的相關(guān)信息存儲(chǔ)到item的files屬性中。例如:下載路徑,下載url 和文件的效驗(yàn)碼
再配置文件settings.py中配置FILES_STORE,指定文件下載路徑
啟動(dòng)pipeline,在ITEM_PIPELINES中設(shè)置scrapy.pipelines.files.FilesPipeline :1
下載圖片的Images Pipeline
使用步驟:
定義好一個(gè)item,然后定義兩個(gè)屬性image_urls和 images,image_urls是用來(lái)存儲(chǔ)需要下載的文件的url鏈接,列表類(lèi)型
當(dāng)文件下載完成后,會(huì)把文件下載的相關(guān)信息存儲(chǔ)到item的images屬性中。例如:下載路徑,下載url 和文件的效驗(yàn)碼
再配置文件settings.py中配置IMAGES_STORE,指定文件下載路徑
啟動(dòng)pipeline,在ITEM_PIPELINES中設(shè)置scrapy.pipelines.images.ImagesPipeline :1
使用Images_pipeline進(jìn)行圖片下載(還是以汽車(chē)之家圖片為例)
改寫(xiě)settings.py
開(kāi)啟自己定義的中間件
配置IMAGES_STORE,指定文件下載路徑
# 圖片下載的路徑,供images pipelines使用 IMAGES_STORE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')改寫(xiě)pipelines.py
import os from urllib import request#導(dǎo)入兩個(gè)庫(kù) from scrapy.pipelines.images import ImagesPipeline from bmw import settingsclass BmwPipeline(object):def __init__(self):self.path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')if not os.path.exists(self.path):os.mkdir(self.path)def process_item(self, item, spider):category = item['category']urls = item['urls']category_path = os.path.join(self.path,category)if not os.path.exists(category_path):os.mkdir(category_path)for url in urls:image_name = url.split('_')[-1]request.urlretrieve(url,os.path.join(category_path,image_name))return item以這種方法下載的圖片,默認(rèn)下載到full文件下,能夠根據(jù)獲取的信息將圖片進(jìn)行分類(lèi)。
優(yōu)化上述的方法
改寫(xiě)pipelines.py
# -*- coding: utf-8 -*-# Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html import os from urllib import request#導(dǎo)入ImagesPipeline from scrapy.pipelines.images import ImagesPipeline#導(dǎo)入settings 引用參數(shù)settings.IMAGES_STORE from bmw import settingsclass BmwPipeline(object):def __init__(self):self.path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')if not os.path.exists(self.path):os.mkdir(self.path)def process_item(self, item, spider):category = item['category']urls = item['urls']category_path = os.path.join(self.path,category)if not os.path.exists(category_path):os.mkdir(category_path)for url in urls:image_name = url.split('_')[-1]request.urlretrieve(url,os.path.join(category_path,image_name))return item#發(fā)送下載圖片的請(qǐng)求 '''def get_media_requests(self, item, info):return [Request(x) for x in item.get(self.images_urls_field, [])] '''# 更改路徑,自定義類(lèi) 繼承ImagesPipeline class BMWImagesPipeline(ImagesPipeline):def get_media_requests(self, item, info):# 這個(gè)方法是在發(fā)送下載請(qǐng)求之前調(diào)用。# 其實(shí)這個(gè)方法本身就是去發(fā)送下載請(qǐng)求的#調(diào)用父類(lèi)的get_media_requests() 方法request_objs = super(BMWImagesPipeline, self).get_media_requests(item,info)for request_obj in request_objs:request_obj.item = itemreturn request_objs#重寫(xiě)file_path 方法def file_path(self, request, response=None, info=None):# 這個(gè)方法是在圖片將要被存儲(chǔ)的時(shí)候調(diào)用,來(lái)獲取這個(gè)圖片存儲(chǔ)的路徑path = super(BMWImagesPipeline, self).file_path(request,response,info)# path = 'full/%s.jpg' 默認(rèn)存儲(chǔ)的文件路徑category = request.item.get('category') #得到分類(lèi)images_store = settings.IMAGES_STORE #存儲(chǔ)的文件路徑category_path = os.path.join(images_store,category) #分類(lèi)存儲(chǔ)的文件路徑#文件夾是否存在,不存在建立相應(yīng)分類(lèi)的文件夾if not os.path.exists(category_path):os.mkdir(category_path)#得到文件的名字image_name = path.replace("full/","")#文件的絕對(duì)路徑image_path = os.path.join(category_path,image_name)return image_path改寫(xiě)settings.py
ITEM_PIPELINES = {# 'bmw.pipelines.BmwPipeline': 300,#不分組情況下使用的pipeline#'scrapy.pipelines.images.ImagesPipeline': 1#分組情況下使用自己定義的pipeline'bmw.pipelines.BMWImagesPipeline': 1 }在自定義ImagePipeline代碼中,作為重要的是要重載get_media_requests(self, item, info)和item_completed(self, results, item, info)這兩個(gè)函數(shù)。
def file_path(self, request, response=None, info=None):image_guid = hashlib.sha1(to_bytes(request.url)).hexdigest()return 'full/%s.jpg' % (image_guid)def get_media_requests(self, item, info):return [Request(x) for x in item.get(self.images_urls_field, [])]#Request返回中 何以傳入其它的參數(shù),例如參數(shù)含有meta,def item_completed(self, results, item, info):if isinstance(item, dict) or self.images_result_field in item.fields:item[self.images_result_field] = [x for ok, x in results if ok]return item- get_media_requests()。它的第一個(gè)參數(shù)item是爬取生成的Item對(duì)象。我們將它的url字段取出來(lái),然后直接生成Request對(duì)象。此Request加入到調(diào)度隊(duì)列,等待被調(diào)度,執(zhí)行下載。
- file_path()。它的第一個(gè)參數(shù)request就是當(dāng)前下載對(duì)應(yīng)的Request對(duì)象。這個(gè)方法用來(lái)返回保存的文件名,直接將圖片鏈接的最后一部分當(dāng)作文件名即可。它利用split()函數(shù)分割鏈接并提取最后一部分,返回結(jié)果。這樣此圖片下載之后保存的名稱就是該函數(shù)返回的文件名。
- item_completed(),它是當(dāng)單個(gè)Item完成下載時(shí)的處理方法。因?yàn)椴⒉皇敲繌垐D片都會(huì)下載成功,所以我們需要分析下載結(jié)果并剔除下載失敗的圖片。如果某張圖片下載失敗,那么我們就不需保存此Item到數(shù)據(jù)庫(kù)。該方法的第一個(gè)參數(shù)results就是該Item對(duì)應(yīng)的下載結(jié)果,它是一個(gè)列表形式,列表每一個(gè)元素是一個(gè)元組,其中包含了下載成功或失敗的信息。這里我們遍歷下載結(jié)果找出所有成功的下載列表。如果列表為空,那么該Item對(duì)應(yīng)的圖片下載失敗,隨即拋出異常DropItem,該Item忽略。否則返回該Item,說(shuō)明此Item有效。
get_media_requests用于解析item中image_urls中指定的url進(jìn)行爬取,可以通過(guò)get_media_requests為每個(gè)url生成一個(gè)Request。如:
class ImagePipeline(ImagesPipeline):def get_media_requests(self, item, info):for image_url in item['image_urls']:yield scrapy.Request(image_url, meta={"image_name": item['image_name']})def file_path(self, request, response=None, info=None):file_name = request.meta['image_name'].strip().replace('\r\n\t\t', r'') + ".jpg"file_name=file_name.replace('/','_')return file_name總結(jié)
以上是生活随笔為你收集整理的crawler(七):Scrapy的Request和Response、Files Pipeline、Images Pipeline的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 决策理论与方法——决策概念与分类
- 下一篇: HTML(一、语法规范,二、结构标签,三