对scrapy进行单元测试 -- 使用betamax

对于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的原理。

转载于:https://www.cnblogs.com/thomaszdxsn/p/duiscrapy-jin-xing-dan-yuan-ce-shi--shi-yongbetama.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Scrapy可以通过在settings.py文件中设置USER_AGENTS列表来实现随机user-agent。在列表中添加多个user-agent,Scrapy会随机选择一个作为请求头中的user-agent。这样可以避免被网站识别为爬虫,提高爬虫的稳定性和成功率。具体设置方法如下: 1. 在settings.py文件中添加USER_AGENTS列表,如下所示: ``` USER_AGENTS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36', 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36', ] ``` 2. 在middlewares.py文件中添加RandomUserAgentMiddleware中间件,如下所示: ``` from scrapy import signals import random class RandomUserAgentMiddleware(object): def __init__(self, agents): self.agents = agents @classmethod def from_crawler(cls, crawler): return cls(crawler.settings.getlist('USER_AGENTS')) def process_request(self, request, spider): request.headers.setdefault('User-Agent', random.choice(self.agents)) ``` 3. 在settings.py文件中启用RandomUserAgentMiddleware中间件,如下所示: ``` DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.RandomUserAgentMiddleware': 543, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, } ``` 这样就完成了随机user-agent的设置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值