新版功能。
注解
這是一個(gè)新引入(Scrapy 0.15)的特性,在后續(xù)的功能/API 更新中可能有所改變,查看 release notes 來(lái)了解更新。
測(cè)試 spider 是一件挺煩人的事情,尤其是只能編寫單元測(cè)試(unit test)沒有其他辦法時(shí),就更惱人了。 Scrapy 通過合同(contract)的方式來(lái)提供了測(cè)試 spider 的集成方法。
您可以硬編碼(hardcode)一個(gè)樣例(sample)url, 設(shè)置多個(gè)條件來(lái)測(cè)試回調(diào)函數(shù)處理 repsponse 的結(jié)果,來(lái)測(cè)試 spider 的回調(diào)函數(shù)。 每個(gè) contract 包含在文檔字符串(docstring)里,以@
開頭。 查看下面的例子:
def parse(self, response):
""" This function parses a sample response. Some contracts are mingled
with this docstring.
@url http://www.amazon.com/s?field-keywords=selfish+gene
@returns items 1 16
@returns requests 0 0
@scrapes Title Author Year Price
"""
該回調(diào)函數(shù)使用了三個(gè)內(nèi)置的 contract 來(lái)測(cè)試:
該 constract(@url)設(shè)置了用于檢查 spider 的其他 constract 狀態(tài)的樣例 url。該 contract 是必須的,所有缺失該 contract 的回調(diào)函數(shù)在測(cè)試時(shí)將會(huì)被忽略:
@url url
該 contract(@returns)設(shè)置 spider 返回的 items 和 requests 的上界和下界。上界是可選的:
@returns item(s)|request(s) [min [max]]
該 contract(@scrapes)檢查回調(diào)函數(shù)返回的所有 item 是否有特定的 fields:
@scrapes field_1 field_2 ...
使用 check
命令來(lái)運(yùn)行 contract 檢查。
如果您想要比內(nèi)置 scrapy contract 更為強(qiáng)大的功能,可以在您的項(xiàng)目里創(chuàng)建并設(shè)置您自己的 contract,并使用 SPIDER_CONTRACTS
設(shè)置來(lái)加載:
SPIDER_CONTRACTS = {
'myproject.contracts.ResponseCheck': 10,
'myproject.contracts.ItemValidate': 10,
}
每個(gè) contract 必須繼承 scrapy.contracts.Contract
并覆蓋下列三個(gè)方法:
參數(shù):
接收一個(gè)字典(dict)
作為參數(shù)。該參數(shù)包含了所有 Request
對(duì)象 參數(shù)的默認(rèn)值。該方法必須返回相同或修改過的字典。
該函數(shù)在 sample request 接收到 response 后,傳送給回調(diào)函數(shù)前被調(diào)用,運(yùn)行測(cè)試。
該函數(shù)處理回調(diào)函數(shù)的輸出。迭代器(Iterators)在傳輸給該函數(shù)前會(huì)被列表化(listified)。
該樣例 contract 在 response 接收時(shí)檢查了是否有自定義 header。 在失敗時(shí) Raise scrapy.exceptions.ContractFaild
來(lái)展現(xiàn)錯(cuò)誤:
from scrapy.contracts import Contract
from scrapy.exceptions import ContractFail
class HasHeaderContract(Contract):
""" Demo contract which checks the presence of a custom header
@has_header X-CustomHeader
"""
name = 'has_header'
def pre_process(self, response):
for header in self.args:
if header not in response.headers:
raise ContractFail('X-CustomHeader not present')