Spiders Contracts (爬虫契约)
测试爬虫可能特别令人烦恼,虽然没有什么能阻止你编写单元测试,但这项任务很快就会变得繁琐。Scrapy 提供了一种通过契约来测试爬虫的集成方式。
这允许你通过硬编码一个示例 URL 并检查回调如何处理响应的各种约束来测试爬虫的每个回调。每个契约都以 @ 为前缀,并包含在 docstring 中。请看以下示例:
def parse(self, response):
"""
This function parses a sample response. Some contracts are mingled
with this docstring.
@url http://www.example.com/s?field-keywords=selfish+gene
@returns items 1 16
@returns requests 0 0
@scrapes Title Author Year Price
"""
你可以使用以下契约:
class scrapy.contracts.default.UrlContract[source]
这个契约(@url)设置了在检查此爬虫的其他契约条件时使用的示例 URL。此契约是强制性的。当运行检查时,所有缺少此契约的回调都将被忽略:
@url url
class scrapy.contracts.default.CallbackKeywordArgumentsContract[source]
这个契约(@cb_kwargs)为示例请求设置了 cb_kwargs 属性。它必须是一个有效的 JSON 字典。
@cb_kwargs {"arg1": "value1", "arg2": "value2", ...}
class scrapy.contracts.default.MetadataContract[source]
这个契约(@meta)为示例请求设置了 meta 属性。它必须是一个有效的 JSON 字典。
@meta {"arg1": "value1", "arg2": "value2", ...}
class scrapy.contracts.default.ReturnsContract[source]
这个契约(@returns)为爬虫返回的项目和请求设置了下限和上限。上限是可选的:
@returns item(s)|request(s) [min [max]]
class scrapy.contracts.default.ScrapesContract[source]
这个契约(@scrapes)检查回调返回的所有项目是否具有指定的字段:
@scrapes field_1 field_2 ...
使用 check 命令运行契约检查。
Custom Contracts (自定义契约)
如果您发现需要比内置 Scrapy 契约更多的功能,您可以使用 SPIDER_CONTRACTS 设置在项目中创建和加载自己的契约:
SPIDER_CONTRACTS = {
"myproject.contracts.ResponseCheck": 10,
"myproject.contracts.ItemValidate": 10,
}
每个契约都必须继承自 Contract,并且可以覆盖三个方法:
class scrapy.contracts.Contract(method, *args)[source]
参数:
- method (
collections.abc.Callable) – 与契约关联的回调函数 - args (
list) – 传递到 docstring 中的参数列表(以空格分隔)
adjust_request_args(args)[source]
这会接收一个 dict 作为参数,其中包含请求对象的默认参数。默认使用 Request,但这可以通过 request_cls 属性更改。如果链中的多个契约定义了此属性,则使用最后一个。
必须返回相同或修改后的版本。
pre_process(response)
这允许在将收到的响应传递给回调之前,对其进行各种检查。
post_process(output)
这允许处理回调的输出。迭代器在传递给此钩子之前会转换为列表。
如果未满足预期,则从 pre_process 或 post_process 中引发 ContractFail:
class scrapy.exceptions.ContractFail[source]
契约失败时引发的错误
这是一个演示契约,它检查收到的响应中是否存在自定义标头:
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")
Detecting check runs (检测检查运行)
当 scrapy check 运行时,环境变量 SCRAPY_CHECK 被设置为字符串 true。您可以使用 os.environ 在使用 scrapy check 时对您的爬虫或设置执行任何更改:
import os
import scrapy
class ExampleSpider(scrapy.Spider):
name = "example"
def __init__(self):
if os.environ.get("SCRAPY_CHECK"):
pass # 在运行检查时进行一些爬虫调整