如何使用scrapy合同?

时间:2014-09-10 11:26:31

标签: python unit-testing python-2.7 web-scraping scrapy

Scrapy Contracts问题

我开始研究scrapy框架。也实施了一些蜘蛛  提取,但我不能为蜘蛛写一个单元测试用例,因为合同  scrapy提供的包文档没有正确的编写程序  测试用例。请帮帮我。

2 个答案:

答案 0 :(得分:11)

是的,Spiders Contracts远非清晰明确。

我不是编写蜘蛛合同的专家(实际上只在web-scraping tutorial newcoder.io处理suggested here时写了一次。但每当我需要为Scrapy蜘蛛编写测试时,我更倾向于遵循方法Scrapy Unit Testing - 从本地html文件创建一个虚假的响应。如果这仍然是一个单元测试程序,这是有争议的,但这为您提供了更多的灵活性和稳健性。

请注意,您仍然可以编写合同,但很快就会感到需要扩展它们并编写自定义合同。这几乎没问题。

相关链接:

答案 1 :(得分:2)

草签合同

测试蜘蛛

测试蜘蛛的两个最基本的问题可能是:

  1. 我的代码更改是否会破坏蜘蛛?
  2. 蜘蛛网是否会因为我要抓取的页面改变而断裂?

合同

Scrapy提供了一种测试蜘蛛的方法:合同。

合同看起来有些不可思议。他们生活在多行文档字符串中。合同“语法”为:@contract_name <arg>。您可以创建自己的合同,这很简洁。

要使用合同,请在合同名称前加上@。合同的名称由给定合同子类上的.name属性指定。这些合同子类可以是内置的,也可以是您创建的自定义子类。

最后,上述文档字符串必须存在于您的Spider的回调中。这是parse回调中存在的一些基本合约的示例;默认回调。

def parse(self, response):
  """This function gathers the author and the quote text.

  @url http://quotes.toscrape.com/
  @returns items 1 8
  @returns requests 0 0
  @scrapes author quote_text
  """

您可以通过scrapy check运行此合同;或者,使用scrapy check -l列出您的合同。

签订更深入的合同

以上合同已使用三个内置合同进行了测试:

  • scrapy.contracts.default.UrlContract
  • scrapy.contracts.default.ReturnsContract
  • scrapy.contracts.default.ScrapesContract

UrlContract是强制性的,并不是真正的合同,因为它不用于验证。 @url协定用于设置通过scrapy check测试蜘蛛时蜘蛛将爬网的URL。在这种情况下,我们指定http://quotes.toscrape.com/。但是我们可以指定http://127.0.0.1:8080/home-11-05-2019-1720.html,这是我用quotes.toscrape.com命令保存的scrapy view http://quotes.toscrape.com/的本地版本。

ReturnsContract用于检查您正在测试的回调的输出。如您所见,该合同被两次调用,具有不同的参数。但是,您不能只在其中放置任何arg。引擎盖下有一个预期的args字典:

objects = {
  'request': Request,
  'requests': Request,
  'item': (BaseItem, dict),
  'items': (BaseItem, dict),
}

我们的合同认为我们的蜘蛛@returns items 1 16116是上下限。上限是可选的;如果未指定?,则将其设置为无穷大。

try:
    self.max_bound = int(self.args[2])
except IndexError:
    self.max_bound = float('inf')

但是,@returns可以帮助您了解蜘蛛是否返回预期的物品或请求量。

最后,@scrapes合同是最后一个内置合同。用于检查已刮除项目中字段的存在。它只是遍历回调的输出字典,并构造缺少的属性的列表:

class ScrapesContract(Contract):
    """ Contract to check presence of fields in scraped items
        @scrapes page_name page_body
    """

    name = 'scrapes'

    def post_process(self, output):
        for x in output:
            if isinstance(x, (BaseItem, dict)):
                missing = [arg for arg in self.args if arg not in x]
                if missing:
                    raise ContractFail(
                        "Missing fields: %s" % ", ".join(missing))

正在运行的合同

运行:scrapy check

如果一切顺利,您会看到:

...
----------------------------------------------------------------------
Ran 3 contracts in 0.140s

OK

如果发生爆炸,您会看到:

F..
======================================================================
FAIL: [example] parse (@returns post-hook)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/adnauseum/.virtualenvs/scrapy_testing-CfFR3tdG/lib/python3.7/site-packages/scrapy/contracts/__init__.py", line 151, in wrapper
    self.post_process(output)
  File "/Users/adnauseum/.virtualenvs/scrapy_testing-CfFR3tdG/lib/python3.7/site-packages/scrapy/contracts/default.py", line 90, in post_process
    (occurrences, self.obj_name, expected))
scrapy.exceptions.ContractFail: Returned 10 items, expected 0

----------------------------------------------------------------------

自定义合同

假设您要签订@has_header X-CustomHeader合同。这将确保您的蜘蛛检查X-CustomHeader是否存在。 Scrapy合同只是具有三个可覆盖方法的类:adjust_request_argspre_processpost_process。从那里,只要没有达到期望,您就需要从ContractFailpre_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' # add the command name to the registry

  def pre_process(self, response):
    for header in self.args:
      if header not in response.headers:
        raise ContractFail(f"{header} not present")

合同为何有用?

看来合同可以帮助您了解两件事:

  1. 您所做的代码更改没有破坏

    • 似乎最好对正在抓取的页面的本地副本运行蜘蛛,并使用合同来验证的代码更改没有破坏任何内容。在这种情况下,您要控制要抓取的页面,并且知道页面没有更改。因此,如果合同失败,您就会知道这是您的代码更改。
    • 在这种方法中,将这些HTML固定装置命名为带有某种时间戳记以保持记录可能会很有用。即Site-Page-07-14-2019.html。您可以通过运行scrapy view <url>保存这些页面。 Scrapy将在浏览器中打开此页面,但还将保存您需要的所有内容的HMTL文件。
  2. 您要抓取的页面没有更改(以影响您的方式)

    • 然后,您还可以将蜘蛛与实物相撞,并让合同告诉您您要抓取的东西已经改变。

尽管合同很有用,但您可能必须做更多的工作才能确保您的蜘蛛。例如,不能保证您要抓取的项目数量始终保持恒定。在这种情况下,您可以考虑对模拟服务器进行爬网并针对收集的项目运行测试。似乎缺少文档和最佳实践。

最后,由Spidermon的Scrapinghub制作了一个项目,该项目可用于监视运行中的蜘蛛:https://spidermon.readthedocs.io/en/latest/getting-started.html

您可以根据模型定义验证抓取的物品,并获得蜘蛛的统计信息(当前抓取的num个物品,不符合验证的num个物品等)。