对于scrapy的单元测试,官方文档并没有提到,只是说有一个Contract功能。但是相信我,这个东西真的不好用,甚至scrapy的作者在一个issue中都说到希望删去这个功能。

那么scrapy应该怎么测试呢?

首先我们要明白我们真正想测试的是什么:

  • 我们不是要测试爬虫是否能访问站点!这个应该在你编写爬虫的时候就做到;如果你的代码在运行突然不可以访问站点了,也应该使用sentry这种日志监控系统。
  • 我们要测试parse(), parse_xx()方法是否如预期返回想要的item和request
  • 我们要测试parse()返回的item中字段类型是否正确。尤其是你用了scrapy的processor系统之后

使用betamax进行单元测试

关于betamax的介绍,可以看我的这篇博客

我们实际要做的不仅是单元测试1,还是集成测试2。我们不想每次都重复进行真实的请求,我们不想使用啰嗦的mock

爬虫代码

下面是我们的爬虫代码,这是爬取一个ip代理网站,获取最新发布的ip:

# src/spider.py
import scrapy
from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, MapCompose, Join class IPItem(scrapy.Item):
ip = scrapy.Field(
input_processor=MapCompose(str, str.strip),
output_processor=TakeFirst()
)
port = scrapy.Field(
input_processor=MapCompose(str, str.strip),
output_processor=TakeFirst()
)
protocol = scrapy.Field(
input_processor=MapCompose(str, str.strip, str.lower),
output_processor=TakeFirst()
)
remark = scrapy.Field(
input_processor=MapCompose(str, str.strip),
output_processor=Join(separator=', ')
)
source = scrapy.Field(
input_processor=MapCompose(str, str.strip),
output_processor=TakeFirst()
) class IpData5uSpider(scrapy.Spider):
name = 'ip-data5u'
allowed_domains = ['data5u.com']
start_urls = [
'http://www.data5u.com/free/index.shtml',
'http://www.data5u.com/free/gngn/index.shtml',
]
custom_settings = {
'USER_AGENT': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
'DOWNLOAD_DELAY': 1
} def parse(self, response):
for row in response.css('div.wlist ul.l2'):
loader = ItemLoader(item=IPItem(), selector=row)
loader.add_value('source', 'data5u')
loader.add_css('ip', 'span:nth-child(1) li::text')
loader.add_css('port', 'span:nth-child(2) li::text')
loader.add_css('protocol', 'span:nth-child(4) li::text')
loader.add_css('remark', 'span:nth-child(5) li::text')
loader.add_css('remark', 'span:nth-child(5) li::text')
yield loader.load_item()

测试代码

我们使用pytest编写项目的单元测试,首先我们编写一些fixture函数:

# tests/conftest.py
import pathlib
import pytest
from scrapy.http import HtmlResponse, Request import betamax
from betamax.fixtures.pytest import _betamax_recorder # betamax配置,设置betamax录像带的存储位置
cassette_dir = pathlib.Path(__file__).parent / 'fixture' / 'cassettes'
cassette_dir.mkdir(parents=True, exist_ok=True)
with betamax.Betamax.configure() as config:
config.cassette_library_dir = cassette_dir.resolve()
config.preserve_exact_body_bytes = True @pytest.fixture
def betamax_recorder(request):
"""修改默认的betamax pytest fixtures
让它默认可用接口pytest.mark.parametrize装饰器,并且生产不同的录像带.
有些地方可能会用到
"""
return _betamax_recorder(request, parametrized=True) @pytest.fixture
def resource_get(betamax_session):
"""这是一个pytest fixture
返回一个http请求方法,相当于: with Betamax(session) as vcr:
vcr.use_use_cassette('这里是测试函数的qualname')
resp = session.get(url, *args, **kwargs)
# 将requests的Response,封装成scrapy的HtmlResponse
return HtmlResponse(body=resp.content)
"""
def get(url, *args, **kwargs):
request = kwargs.pop('request', None)
resp = betamax_session.get(url, *args, **kwargs)
selector = HtmlResponse(body=resp.content, url=url, request=request)
return selector return get

然后是测试函数:

# tests/test_spider/test_ip_spider.py
from src.spider import IpData5uSpider, IPItem def test_proxy_data5u_spider(resource_get):
spider = IpData5uSpider()
headers = {
'user-agent': spider.custom_settings['USER_AGENT']
} for urlr in spider.start_urls:
selector = resource_get(url, headers=headers, request=req) result = spider.parse(selector)
for item in result:
if isinstance(item, IPItem):
assert isinstance(item['port'], str)
assert item['ip']
assert item['protocol'] in ('http', 'https')
elif isinstance(item, Request):
assert item.url.startswith(req.url)
else:
raise ValueError('yield 输出了意料外的item')

然后我们运行它:

>>> pytest
...
Results (2.12s):
1 passed

我们可以看到fixture目录出现新的文件,类似xxx.tests.test_spiders.test_ip_spider.test_proxy_data5u_spider.json这样的文件名.

再运行一次:

>>> pytest
...
Results (0.56s):
1 passed

测试运行速度明显变快,这是因为这一次使用的是保存在fixture的文件,用它来代替进行真正的http request操作。

另外我们可以看一下fixture中json文件的内容:

{"http_interactions": [{"request": {"body": {"encoding": "utf-8", "base64_string": ""}, "headers": {"user-agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "Connection": ["keep-alive"]}, "method": "GET", "uri": "http://www.data5u.com/free/index.shtml"}, "response": {"body": {"encoding": "UTF-8", "base64_string": "H4sIAAAAAAx..."}]}

可以看到这里保存了一个response的全部信息,通过这个response再构造一个request.Response也不是难事吧。这就是betamax的原理。

对scrapy进行单元测试 -- 使用betamax的更多相关文章

  1. Learning Scrapy笔记(三)- Scrapy基础

    摘要:本文介绍了Scrapy的基础爬取流程,也是最重要的部分 Scrapy的爬取流程 Scrapy的爬取流程可以概括为一个方程式:UR2IM,其含义如下图所示 URL:Scrapy的运行就从那个你想要 ...

  2. python爬虫scrapy项目详解(关注、持续更新)

    python爬虫scrapy项目(一) 爬取目标:腾讯招聘网站(起始url:https://hr.tencent.com/position.php?keywords=&tid=0&st ...

  3. scrapy将爬取到的数据存入elasticsearch

    pip安装 elasticsearch-dsl的包, 是elasticsearch提供给python 的接口 if __name__ == "__main__": 这个用来调试,还 ...

  4. scrapy爬取cnblogs文章列表

    scrapy爬取cnblogs文章 目标任务 安装爬虫 创建爬虫 编写 items.py 编写 spiders/cnblogs.py 编写 pipelines.py 编写 settings.py 运行 ...

  5. Scrapy框架爬虫初探——中关村在线手机参数数据爬取

    关于Scrapy如何安装部署的文章已经相当多了,但是网上实战的例子还不是很多,近来正好在学习该爬虫框架,就简单写了个Spider Demo来实践.作为硬件数码控,我选择了经常光顾的中关村在线的手机页面 ...

  6. Intellij idea添加单元测试工具

    1.idea 版本是14.0.0 ,默认带有Junit,但是不能自动生成单元测试,需要下载JunitGererator2.0插件 2.Settings -Plugins,下载 JunitGenerat ...

  7. Python的单元测试(二)

    title: Python的单元测试(二) date: 2015-03-04 19:08:20 categories: Python tags: [Python,单元测试] --- 在Python的单 ...

  8. Python的单元测试(一)

    title: Python的单元测试(一) author: 青南 date: 2015-02-27 22:50:47 categories: Python tags: [Python,单元测试] -- ...

  9. scrapy爬虫docker部署

    spider_docker 接我上篇博客,为爬虫引用创建container,包括的模块:scrapy, mongo, celery, rabbitmq,连接https://github.com/Liu ...

随机推荐

  1. BZOJ 2219 数论之神 (CRT推论+BSGS+原根指标)

    看了Po神的题解一下子就懂了A了! 不过Po神的代码出锅了-solve中"d-temp"并没有什么用QwQQwQQwQ-应该把模数除以p^temp次方才行. 来自BZOJ讨论板的h ...

  2. HDU-3613-Best Reward(Manacher, 前缀和)

    链接: https://vjudge.net/problem/HDU-3613 题意: After an uphill battle, General Li won a great victory. ...

  3. Linux下使用telnet测试端口号是否开放

    telnet 127.0.0.1 80调用后,若提示bash: telnet: command not found,那么进行以下步骤: 1.检查telnet是否已经安装,或者有部分未安装: rpm - ...

  4. 对称加密算法DES、3DES和AES 原理总结(转载)

    1.对称加密算法 1.1 定义 对称加密算法是应用较早的加密算法,技术成熟.在对称加密算法中,数据发信方将明文(原始数据)和加密密钥(mi yue)一起经过特殊加密算法处理后,使其变成复杂的加密密文发 ...

  5. 使用canvas画布生成二维码

    1. 基本用法 <canvas>标签只有两个属性-----width和height CSS: <canvas class="qrcode" width=" ...

  6. Vue:选中商品规格改变字体和边框颜色(默认选中第一种规格)

    效果图: CSS: <div class="label"> <p>标签类别</p> <ul> <li v-for=" ...

  7. HGOI 20190828 题解

    Problem A 数学题 设数论函数$f(x)$表示$x(x ∉ Prime)$的次大因数, 给出$l,r$求出$\sum\limits_{i=l,i ∉ Prime} ^r f(i)$ . 对于$ ...

  8. 灵魂拷问:Java如何获取数组和字符串的长度?length还是length()?

    限时 1 秒钟给出答案,来来来,听我口令:"Java 如何获取数组和字符串的长度?length 还是 length()?" 在逛 programcreek 的时候,我发现了上面这个 ...

  9. BZOJ 4814 Luogu P3699 [CQOI2017]小Q的草稿 (计算几何、扫描线、set)

    题目链接 (BZOJ) http://lydsy.com/JudgeOnline/problem.php?id=4814 (Luogu) https://www.luogu.org/problem/P ...

  10. Topics(主题模式)

    引言 topic exchange和direct exchange类似,都是通过routing key和binding key进行匹配,不同的是topic exchange可以为routing key ...