crawler(七):Scrapy的Request和Response、Files Pipeline、Images Pipeline
請求和響應
Scrapy的Request 和Response對象用于爬網網站。
通常,Request對象在爬蟲程序中生成并傳遞到系統,直到它們到達下載程序,后者執行請求并返回一個Response對象,該對象返回到發出請求的爬蟲程序。
兩個類Request和Response類都有一些子類,它們添加基類中不需要的功能。這些在下面的請求子類和 響應子類中描述。
一個Request對象表示一個HTTP請求,它通常是在爬蟲生成,并由下載執行,從而生成Response。
常用參數:
- url(string) - 此請求的網址
- callback(callable) - 將使用此請求的響應(一旦下載)作為其第一個參數調用的函數。有關更多信息,請參閱下面的將附加數據傳遞給回調函數。如果請求沒有指定回調,parse()將使用spider的 方法。請注意,如果在處理期間引發異常,則會調用errback。
- method(string) - 此請求的HTTP方法。默認為’GET’。
- meta(dict) - 屬性的初始值Request.meta。如果給定,在此參數中傳遞的dict將被淺復制。
- headers(dict) - 這個請求的頭。dict值可以是字符串(對于單值標頭)或列表(對于多值標頭)。如果 None作為值傳遞,則不會發送HTTP頭。
- body(str或unicode) - 請求體。如果unicode傳遞了a,那么它被編碼為 str使用傳遞的編碼(默認為utf-8)。如果 body沒有給出,則存儲一個空字符串。不管這個參數的類型,存儲的最終值將是一個str(不會是unicode或None)。
- cookie(dict或list) - 請求cookie。這些可以以兩種形式發送。
- dont_filter(boolean) - 表示此請求不應由調度程序過濾。當您想要多次執行相同的請求時忽略重復過濾器時使用。小心使用它,或者你會進入爬行循環。默認為False。
- priority(int) - 此請求的優先級(默認為0)。調度器使用優先級來定義用于處理請求的順序。具有較高優先級值的請求將較早執行。允許負值以指示相對低優先級。
- encoding(string) - 此請求的編碼(默認為’utf-8’)。此編碼將用于對URL進行百分比編碼,并將正文轉換為str(如果給定unicode)。
Request中meta參數的作用是傳遞信息給下一個函數,使用過程可以理解成:
把需要傳遞的信息賦值給這個叫meta的變量, 但meta只接受字典類型的賦值,因此 要把待傳遞的信息改成“字典”的形式,即: meta={'key1':value1,'key2':value2}如果想在下一個函數中取出value1, 只需得到上一個函數的meta['key1']即可, 因為meta是隨著Request產生時傳遞的, 下一個函數得到的Response對象中就會有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中分析出的一個網址賦值給urlurl=response.xpath('.......').extract()#ExamleClass是在items.py中定義的,下面會寫出。"""記住item本身是一個字典"""item=ExampleClass()item['name']=response.xpath('.......').extract()item['htmlurl']=response.xpath('.......').extract()"""通過meta參數,把item這個字典,賦值給meta中的'key'鍵(記住meta本身也是一個字典)。Scrapy.Request請求url后生成一個"Request對象",這個meta字典(含有鍵值'key','key'的值也是一個字典,即item)會被“放”在"Request對象"里一起發送給parse2()函數 """yield Request(url,meta={'key':item},callback='parse2')def parse2(self,response):item=response.meta['key']"""這個response已含有上述meta字典,此句將這個字典賦值給item,完成信息傳遞。這個item已經和parse中的item一樣了"""item['text']=response.xpath('.......').extract()#item共三個鍵值,到這里全部添加完畢了yield itemmeta是淺復制,必要時需要深復制。
import copy meta={'key':copy.deepcopy('value')}meta是一個dict,主要是用解析函數之間傳遞值,一種常見的情況:在parse中給item某些字段提取了值,但是另外一些值需要在parse_item中提取,這時候需要將parse中的item傳到parse_item方法中處理,顯然無法直接給parse_item設置而外參數。 Request對象接受一個meta參數,一個字典對象,同時Response對象有一個meta屬性可以取到相應request傳過來的meta。所以解決上述問題可以這樣做:
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之間如何傳參
有些時候需要將兩個頁面的內容合并到一個item里面,這時候就需要在yield scrapy.Request的同時,傳遞一些參數到一下頁面中。這時候可以這樣操作。
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
爬取的主要目標就是從非結構性的數據源提取結構性數據,例如網頁。 Scrapy spider可以以python的dict來返回提取的數據.雖然dict很方便,并且用起來也熟悉,但是其缺少結構性,容易打錯字段的名字或者返回不一致的數據,尤其在具有多個spider的大項目中。。
為了定義常用的輸出數據,Scrapy提供了Item 類。 Item 對象是種簡單的容器,保存了爬取到得數據。 其提供了 類似于詞典(dictionary-like)的API以及用于聲明可用字段的簡單語法。
聲明Item
Item使用簡單的class定義語法以及 Field 對象來聲明。例如:
Item Pipeline
當Item在Spider中被收集之后,它將會被傳遞到Item Pipeline,一些組件會按照一定的順序執行對Item的處理。
每個item pipeline組件(有時稱之為“Item Pipeline”)是實現了簡單方法的Python類。他們接收到Item并通過它執行一些行為,同時也決定此Item是否繼續通過pipeline,或是被丟棄而不再進行處理。
編寫你自己的item pipeline
編寫你自己的item pipeline很簡單,每個item pipiline組件是一個獨立的Python類,同時必須實現以下方法:
pipeline區分傳來Items
各個頁面都會封裝items并將item傳遞給pipelines來處理,而pipelines接收的入口只有一個就是
def process_item(self, item, spider)函數spider 對應相應的爬蟲,調用spider.name也可區分來自不同爬蟲的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 # 必須實現返回spider 對應相應的爬蟲,調用spider.name也可區分來自不同爬蟲的item
def process_item(self, item, spider):if spider.name == "XXXX":pass啟用一個Item Pipeline組件
為了啟用一個Item Pipeline組件,你必須將它的類添加到ITEM_PIPELINES配置,就像下面這個例子:
ITEM_PIPELINES = {'myproject.pipelines.PricePipeline': 300,'myproject.pipelines.JsonWriterPipeline': 800, }分配給每個類的整型值,確定了他們運行的順序,item按數字從低到高的順序,通過pipeline,通常將這些數字定義在0-1000范圍內。
下載及處理文件和圖片
Scrapy為下載item中包含的文件(比如在爬取到產品時,同時也想保存對應的圖片)提供了一個可重用的item pipelines . 這些pipeline有些共同的方法和結構(我們稱之為media pipeline)。一般來說你會使用Files Pipeline或者 Images Pipeline.
使用Files Pipeline
當使用 FilesPipeline ,典型的工作流程如下所示:
在一個爬蟲里,你抓取一個項目,把其中圖片的URL放入 file_urls組內。
項目從爬蟲內返回,進入項目管道。
當項目進入 FilesPipeline,file_urls組內的URLs將被Scrapy的調度器和下載器(這意味著調度器和下載器的中間件可以復用)安排下載,當優先級更高,會在其他頁面被抓取前處理。項目會在這個特定的管道階段保持“locker”的狀態,直到完成文件的下載(或者由于某些原因未完成下載)。
當文件下載完后,另一個字段(files)將被更新到結構中。這個組將包含一個字典列表,其中包括下載文件的信息,比如下載路徑、源抓取地址(從file_urls組獲得)和圖片的校驗碼(checksum)。 files列表中的文件順序將和源 file_urls組保持一致。如果某個圖片下載失敗,將會記錄下錯誤信息,圖片也不會出現在 files 組中。
import scrapyclass MyItem(scrapy.Item):# ... other item fields ...image_urls = scrapy.Field()images = scrapy.Field()在配置文件 settings.py中配置FILES_STORE,這個配置用來設置文件下載下來的路徑。
啟動pipeline,
對于 Files Pipeline, 使用:
對于 Images Pipeline, 使用:
ITEM_PIPELINES = {'scrapy.pipeline.images.ImagesPipeline': 1}接著IMAGES_STORE設置為一個有效的文件夾,用來存儲下載的圖片。 否則管道將保持禁用狀態,即使你在ITEM_PIPELINES 設置中添加了它。
對于Files Pipeline, 設置 FILES_STORE
對于Images Pipeline, 設置 IMAGES_STORE
IMAGES_STORE = '/path/to/valid/dir'其中:
- <IMAGES_STORE> 是定義在IMAGES_STORE 設置里的文件夾
- full是用來區分圖片和縮略圖(如果使用的話)的一個子文件夾。詳情參見 針對圖片生成縮略圖.
1 傳統的Scrapy框架圖片下載方式
創建項目:
改寫settings.py
-
不遵守robots協議
# Obey robots.txt rules ROBOTSTXT_OBEY = False -
設置請求頭
# 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' }
改寫items.py
import scrapyclass BmwItem(scrapy.Item):category = scrapy.Field()image_urls = scrapy.Field()images = scrapy.Field()改寫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改寫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#導入兩個庫 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測試py
from scrapy import cmdline cmdline.execute('scrapy crawl bmw5'.split())Scrapy框架提供了兩個中間件、下載文件的Files pipeline 和下載圖片的Image pipeline
下載文件的Files pipeline
使用步驟:
定義好一個item,然后定義兩個屬性file_urls 和 files , file_urls是用來存儲需要下載的文件的url鏈接,列表類型
當文件下載完成后,會把文件下載的相關信息存儲到item的files屬性中。例如:下載路徑,下載url 和文件的效驗碼
再配置文件settings.py中配置FILES_STORE,指定文件下載路徑
啟動pipeline,在ITEM_PIPELINES中設置scrapy.pipelines.files.FilesPipeline :1
下載圖片的Images Pipeline
使用步驟:
定義好一個item,然后定義兩個屬性image_urls和 images,image_urls是用來存儲需要下載的文件的url鏈接,列表類型
當文件下載完成后,會把文件下載的相關信息存儲到item的images屬性中。例如:下載路徑,下載url 和文件的效驗碼
再配置文件settings.py中配置IMAGES_STORE,指定文件下載路徑
啟動pipeline,在ITEM_PIPELINES中設置scrapy.pipelines.images.ImagesPipeline :1
使用Images_pipeline進行圖片下載(還是以汽車之家圖片為例)
改寫settings.py
開啟自己定義的中間件
配置IMAGES_STORE,指定文件下載路徑
# 圖片下載的路徑,供images pipelines使用 IMAGES_STORE = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'images')改寫pipelines.py
import os from urllib import request#導入兩個庫 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以這種方法下載的圖片,默認下載到full文件下,能夠根據獲取的信息將圖片進行分類。
優化上述的方法
改寫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#導入ImagesPipeline from scrapy.pipelines.images import ImagesPipeline#導入settings 引用參數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#發送下載圖片的請求 '''def get_media_requests(self, item, info):return [Request(x) for x in item.get(self.images_urls_field, [])] '''# 更改路徑,自定義類 繼承ImagesPipeline class BMWImagesPipeline(ImagesPipeline):def get_media_requests(self, item, info):# 這個方法是在發送下載請求之前調用。# 其實這個方法本身就是去發送下載請求的#調用父類的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#重寫file_path 方法def file_path(self, request, response=None, info=None):# 這個方法是在圖片將要被存儲的時候調用,來獲取這個圖片存儲的路徑path = super(BMWImagesPipeline, self).file_path(request,response,info)# path = 'full/%s.jpg' 默認存儲的文件路徑category = request.item.get('category') #得到分類images_store = settings.IMAGES_STORE #存儲的文件路徑category_path = os.path.join(images_store,category) #分類存儲的文件路徑#文件夾是否存在,不存在建立相應分類的文件夾if not os.path.exists(category_path):os.mkdir(category_path)#得到文件的名字image_name = path.replace("full/","")#文件的絕對路徑image_path = os.path.join(category_path,image_name)return image_path改寫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)這兩個函數。
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返回中 何以傳入其它的參數,例如參數含有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()。它的第一個參數item是爬取生成的Item對象。我們將它的url字段取出來,然后直接生成Request對象。此Request加入到調度隊列,等待被調度,執行下載。
- file_path()。它的第一個參數request就是當前下載對應的Request對象。這個方法用來返回保存的文件名,直接將圖片鏈接的最后一部分當作文件名即可。它利用split()函數分割鏈接并提取最后一部分,返回結果。這樣此圖片下載之后保存的名稱就是該函數返回的文件名。
- item_completed(),它是當單個Item完成下載時的處理方法。因為并不是每張圖片都會下載成功,所以我們需要分析下載結果并剔除下載失敗的圖片。如果某張圖片下載失敗,那么我們就不需保存此Item到數據庫。該方法的第一個參數results就是該Item對應的下載結果,它是一個列表形式,列表每一個元素是一個元組,其中包含了下載成功或失敗的信息。這里我們遍歷下載結果找出所有成功的下載列表。如果列表為空,那么該Item對應的圖片下載失敗,隨即拋出異常DropItem,該Item忽略。否則返回該Item,說明此Item有效。
get_media_requests用于解析item中image_urls中指定的url進行爬取,可以通過get_media_requests為每個url生成一個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總結
以上是生活随笔為你收集整理的crawler(七):Scrapy的Request和Response、Files Pipeline、Images Pipeline的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 决策理论与方法——决策概念与分类
- 下一篇: HTML(一、语法规范,二、结构标签,三