Scrapy的默认设置中,有几个关于并发请求及下载延迟的参数,这几个参数的设置,既影响请求效率,同时关乎对方服务器的压力大小。

相关的几个参数:

  • CONCURRENT_REQUESTS
    默认值:16
    Scrapy下载器发送的最大并发请求数。

  • CONCURRENT_REQUESTS_PER_DOMAIN
    默认值:8
    针对某一个域名的最大并发请求数。

  • CONCURRENT_REQUESTS_PER_IP
    默认值:0
    针对某一ip的最大并发请求数。如果此项非0的话,它的优先级高于CONCURRENT_REQUESTS_PER_DOMAIN。也就是说此时的并发数量限制是基于ip的,而非域名。
    同时这个设置也会影响到DOWNLOAD_DELAY,如果CONCURRENT_REQUESTS_PER_IP非0,下载延迟会基于ip,而非域名。

  • DOWNLOAD_DELAY
    默认值:0
    每个请求间的下载延迟,单位为秒。同时受RANDOMIZE_DOWNLOAD_DELAY参数(默认开启)影响,下载延迟并不会是设定的固定值,而是在 0.5*DOWNLOAD_DELAY~1.5*DOWNLOAD_DELAY区间内。
    默认是针对同一域名的每个请求设置DELAY,如果CONCURRENT_REQUESTS_PER_DOMAIN非0,下载延迟会基于ip,而非域名。

这里通过实验的方式,验证这几个参数的表现,以及同时设置某几个参数的影响。为了能直观准确的看到请求时间差,全程将RANDOMIZE_DOWNLOAD_DELAY设置为False,也就是固定下载延迟,而非在区间内波动。

准备工作

使用flask写一个简易的服务端程序,同时在两台机器上运行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import time
from flask import Flask, make_response

app = Flask(__name__)

@app.route('/')
def index():
    time.sleep(10)
    response = make_response('Hello World!')

    return response


if __name__ == '__main__':
    app.run(host='0.0.0.0', port="9999", debug=True, threaded=True)

创建一个scrapy项目,其中spider脚本如下,在请求队列中均匀分布两个ip的请求:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import scrapy
from itertools import chain

class TestSpider(scrapy.Spider):
    name = 'test'
    urls1 = ['http://0.0.0.0:9999/' for i in range(20)]
    urls2 = ['http://123.206.38.21:9999/' for j in range(20)]
    start_urls = list(chain.from_iterable(zip(urls1, urls2)))

    def parse(self, response):
        print(response.body)

实验验证

根据上文,CONCURRENT_REQUESTS_PER_DOMAINCONCURRENT_REQUESTS_PER_IP两个参数是覆盖关系,所以这里只以其中任何一个作为参考即可,这里选择CONCURRENT_REQUESTS_PER_DOMAIN

只单独设置并发数CONCURRENT_REQUESTS或者CONCURRENT_REQUESTS_PER_DOMAIN

1
CONCURRENT_REQUESTS = 10

或者

1
CONCURRENT_REQUESTS_PER_DOMAIN = 2

结果显而易见,CONCURRENT_REQUESTS限制了总并发数,CONCURRENT_REQUESTS_PER_DOMAIN分别对单个域名的并发数起到了限制。

同时设置CONCURRENT_REQUESTSCONCURRENT_REQUESTS_PER_DOMAIN

1
2
CONCURRENT_REQUESTS = 6  
CONCURRENT_REQUESTS_PER_DOMAIN = 4

在同时设置CONCURRENT_REQUESTSCONCURRENT_REQUESTS_PER_DOMAIN时,两者不存在覆盖关系,而是共同对并发数起到了限制,两者的交集决定了最大并发数。

不开启并发,同时将spider中的请求队列改为只向一个地址发送请求,测试下载DELAY

1
2
CONCURRENT_REQUESTS = 1
DOWNLOAD_DELAY = 5

响应间隔为10秒,貌似设置的DELAY没起作用的样子。
其实不然,这里存在一个误区,由于平时正常访问网站几乎是秒回响应,DELAY时间远大于从请求到返回响应的这段时间,所以看不出端倪。
而这里服务器的处理时间为10秒,大于DELAY时间。假设将请求到响应的完整流程看做一个事务,DOWNLOAD_DELAY并没有作用到这个完整事务上,而是作用到了每个请求上,在每个请求发送之后,DELAY即开始计数,计数到设置的DELAY时间点,发送下一个请求。
这里的DELAY时间为5秒,虽然在响应未返回就计数完成,可以发送请求,但由于并发数为1,所以只能等待收到响应后再发送下一次的请求。

设置大于服务器处理时间的DELAY,测试下载DELAY

1
2
CONCURRENT_REQUESTS = 1
DOWNLOAD_DELAY = 13

证实了上面的结论,在21:48:05发送第一个请求后,即开始计数,设置的DELAY为13秒,因此到21:48:18发送下一个请求,21:48:28收到响应,以此类推…

开启并发,请求队列变成对两个地址的请求,测试下载DELAY

1
2
CONCURRENT_REQUESTS = 10
DOWNLOAD_DELAY = 5

在第一个响应之后,每隔5秒收到一个响应。验证了上一步的结论,在发送请求后即开始DELAY计数,到达设定的时间后就会发送下一个请求。
两个地址的响应是同时返回的,也就是说DOWNLOAD_DELAY 针对的是单个域名的请求,而不是将所有请求一视同仁的DELAY。

总结

  1. 在同时设置CONCURRENT_REQUESTSCONCURRENT_REQUESTS_PER_DOMAIN时,两者不存在覆盖关系,而是共同对并发数起到了限制,两者的交集决定了最大并发数。
  2. DOWNLOAD_DELAY只作用于单个地址下的请求,进行DELAY;下载间隔在发送请求后即开始计数,到达设定值发送下一次请求,无关响应。