Контракты пауков
Пауки-тестеры могут стать особенно утомительными, и, хотя ничто не мешает вам писать модульные тесты, задача быстро становится громоздкой. Scrapy предлагает интегрированный способ тестирования ваших пауков посредством контрактов.
Это позволяет вам тестировать каждый обратный вызов вашего паука, жёстко кодируя образец URL и проверяя различные ограничения того, как обратный вызов обрабатывает ответ. У каждого контракта есть префикс @
и включается в строку документации. См. следующий пример:
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
"""
Данный обратный вызов протестирован с использованием трёх встроенных контрактов:
- class scrapy.contracts.default.UrlContract
Данный контракт (
@url
) устанавливает образец URL, используемый при проверке других условий контракта для этого паука. Данный контракт является обязательным. Все обратные вызовы, в которых отсутствует данный контракт, игнорируются при запуске проверок:@url url
- class scrapy.contracts.default.CallbackKeywordArgumentsContract
Данный контракт (
@cb_kwargs
) устанавливает атрибутcb_kwargs
для образца запроса. Это должен быть действующий словарь JSON.@cb_kwargs {"arg1": "value1", "arg2": "value2", ...}
- class scrapy.contracts.default.ReturnsContract
Данный контракт (
@returns
) устанавливает нижнюю и верхнюю границы для элементов и запросов, возвращаемых пауком. Верхняя граница не обязательна:@returns item(s)|request(s) [min [max]]
- class scrapy.contracts.default.ScrapesContract
Данный контракт (
@scrapes
) проверяет, что все элементы, возвращаемые обратным вызовом, имеют указанные поля:@scrapes field_1 field_2 ...
Используйте команду check
для запуска проверок контракта.
Пользовательские контракты
Если вы обнаружите, что вам нужно больше мощности, чем встроенные контракты Scrapy, вы можете создать и загрузить свои собственные контракты в проект, используя параметр SPIDER_CONTRACTS
:
SPIDER_CONTRACTS = {
'myproject.contracts.ResponseCheck': 10,
'myproject.contracts.ItemValidate': 10,
}
Каждый контракт должен наследовать от Contract
и может переопределять три метода:
- class scrapy.contracts.Contract(method, *args)
- Параметры
method (collections.abc.Callable) – функция обратного вызова, с которой связан контракт
args (list) – список аргументов, переданных в строку документации (разделенные пробелами)
- adjust_request_args(args)
Он получает
dict
в качестве аргумента, содержащего аргументы по умолчанию для объекта запроса. По умолчанию используетсяRequest
, но его можно изменить с помощью атрибутаrequest_cls
. Если для нескольких контрактов в цепочке определён данный атрибут, используется последний из них.Должен возвращать ту же или модифицированную версию.
- pre_process(response)
Позволяет выполнять различные проверки ответа, полученного от образца запроса, до его передачи в обратный вызов.
- post_process(output)
Позволяет обрабатывать вывод обратного вызова. Итераторы преобразуются в списки перед тем, как быть переданы в этот хук.
Вызывается ContractFail
из pre_process
или post_process
, если ожидания не оправдаются:
Вот демонстрационный контракт, который проверяет наличие настраиваемого заголовка в полученном ответе:
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')
Обнаружение контрольных прогонов
Когда 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 # Do some scraper adjustments when a check is running