You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RedisSpider并发设置未达预期性能的问题求助

Scrapy-Redis爬虫并发行为不符合预期的原因

我使用Python3.7与Scrapy-Redis编写了RedisSpider,将CONCURRENT_REQUESTS设置为10,期望爬虫能保持10个并发请求,单个请求完成后立即从Redis获取新请求补充。但实际情况是爬虫会一次性从Redis获取10个请求,必须等这10个全部完成后才会拉取新请求。

爬虫配置

custom_settings = {
    "DOWNLOADER_MIDDLEWARES": {
        "projects.middlewares.MediaFileCheckMiddleware": 300,
    },
    "DOWNLOAD_TIMEOUT": 300,
    "CONCURRENT_REQUESTS": 10,
    "SCHEDULER_PERSIST": True,
    "SCHEDULER_FLUSH_ON_START": False,
    "SCHEDULER_IDLE_BEFORE_CLOSE": 0,
}

预期与实际执行流程

预期的线程执行流程

thread1 --job1--|-----job2-----|-job3-|--job4--|...
thread2 ----job1----|----job2----|----job3----|...
thread3 -job1-|-job2-|----job3----|--job4--|...

每个线程完成当前任务后立即启动新任务,持续维持并发数。

实际的线程执行流程

thread1 --job1--      |-----job2-----|-job3-         |--job4--,...
thread2 ----job1----|----job2----   |----job3----|...
thread3 -job1-         |-job2-           |----job3---- |--job4--,...

所有线程先完成第一批10个任务,才会开始下一批任务。

核心爬虫代码

class FileHandler(RedisSpider):
    name = 'filehandler'
    redis_key = 'news:multimedias'
    file_path = r'/data/temp'
    img_header = Headers(content_type='img')

    size_limit = 50 * 1024 * 1024

    custom_settings = {
        "DOWNLOADER_MIDDLEWARES": {
            "projects.dapoo.middlewares.MediaFileCheckMiddleware": 300,
        },
        "DOWNLOAD_TIMEOUT": 300,
        # "DOWNLOAD_WARNSIZE": 0,
        "CONCURRENT_REQUESTS": 10,
        "SCHEDULER_PERSIST": True,
        "SCHEDULER_FLUSH_ON_START": False,
        "SCHEDULER_IDLE_BEFORE_CLOSE": 0,
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def make_request_from_data(self, data):
        obj = json.loads(data)
        suffix = obj.get('suffix')
        src = obj.get("src")
        err = obj.get('err')
        media_type = obj.get('media_type')

        if err:
            pass  # logging
        else:
            return scrapy.Request(
                src,
                method="GET",
                headers=self.img_header(),
                dont_filter=True,
                meta={'file_name': str(int(datetime.datetime.now().timestamp()))},
                callback=self.parse
            )

    def parse(self, response):
        file_name = response.meta.get('file_name')
        file_path = os.path.join(self.file_path, file_name)
        with open(file_path, 'wb') as f:
            f.write(response.body)

原因分析与解决方案

原因

这是Scrapy-Redis调度器的默认批量拉取机制导致的:

  • Scrapy-Redis的RedisScheduler会在本地维护一个请求队列,当本地队列的请求数低于CONCURRENT_REQUESTS时,会一次性从Redis任务队列拉取足够的请求填满本地队列(最多CONCURRENT_REQUESTS个)。
  • 只有当本地队列的所有请求都处理完毕后,调度器才会再次去Redis拉取新的一批请求,因此出现了“必须等10个全部完成才取新请求”的现象。

解决方案

调整Scrapy-Redis的批量拉取参数,在custom_settings中添加:

"REDIS_START_URLS_BATCH_SIZE": 1

该参数控制每次从Redis拉取请求的数量,设置为1后,每当有一个请求处理完成,调度器会立即从Redis拉取一个新请求补充到本地队列,从而维持稳定的10个并发请求,符合你的预期。

另外,如果你的MediaFileCheckMiddleware存在同步阻塞操作,也可能影响并发效率,建议检查中间件实现是否存在耗时的同步逻辑。

内容的提问来源于stack exchange,提问作者vassiliev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 02:05:45