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

Django中使用grequests批量调用API时应用意外退出求助

Django中使用grequests批量异步调用API后应用无征兆退出的解决方案

问题背景

在Django项目中使用grequests实现100个API的批量异步调用,代码在Jupyter Notebook中测试完全正常,但通过python manage.py runserver启动开发服务器后,执行完一批(10个)API调用后应用会无征兆退出。已知SSL证书验证警告(InsecureRequestWarning)并非导致退出的原因。

问题代码

class BatchCall:
    def __init__(self, urls, BO_urls):
        self.urls = urls
        self.BO_urls = BO_urls

    def exception(self, request, exception):
        print("Problem: {}: {}".format(request.url, exception))

    def asyncCall_release(self):
        return grequests.map(
            (grequests.get(u, auth=HTTPBasicAuth(username, password), verify=False) for u in self.urls),
            exception_handler=self.exception, size=10)

    def asyncCall_BO(self):
        return grequests.map(
            (grequests.get(u, auth=HTTPBasicAuth(username, password), verify=False) for u in self.BO_urls),
            exception_handler=self.exception, size=10)

test = BatchCall(urls, BO_urls)
results_release = test.asyncCall_release()
results_BO = test.asyncCall_BO()

原因分析

grequests基于gevent实现异步,而Django开发服务器(runserver)的默认线程模型、自动重载机制与gevent的协程调度存在冲突:

  • gevent通过猴子补丁修改标准库实现协程,但Django开发服务器启动时未被patch,导致协程和线程的事件循环冲突,触发进程退出
  • runserver的自动重载功能会重启进程,干扰gevent的协程上下文

解决方案

1. 提前添加gevent猴子补丁

在项目的manage.py最顶部添加gevent的猴子补丁,确保所有标准库被patch,需放在所有其他import之前:

from gevent import monkey
monkey.patch_all()

# 以下是原有manage.py的代码
import os
import sys

def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(...) from exc
    execute_from_command_line(sys.argv)

if __name__ == '__main__':
    main()

2. 禁用开发服务器自动重载

启动服务器时添加--noreload参数,避免自动重载机制破坏协程上下文:

python manage.py runserver --noreload

3. 调整并发数

适当降低grequests.map的size参数(比如改为5),减少短时间内的协程数量,避免资源竞争:

return grequests.map(
    (grequests.get(u, auth=HTTPBasicAuth(username, password), verify=False) for u in self.urls),
    exception_handler=self.exception, size=5)

4. 改用Django原生异步方案(推荐)

如果上述方法仍存在兼容性问题,推荐使用Django 3.1+原生异步支持结合aiohttp实现,更贴合Django生态:

import asyncio
import aiohttp
from django.http import HttpResponse
from django.views import View
from asgiref.sync import async_to_sync
from aiohttp import BasicAuth

class BatchCall:
    def __init__(self, urls, bo_urls, username, password):
        self.urls = urls
        self.bo_urls = bo_urls
        self.auth = BasicAuth(username, password)

    async def fetch(self, session, url):
        try:
            async with session.get(url, auth=self.auth, ssl=False) as response:
                return await response.json()  # 根据需求返回对应格式
        except Exception as e:
            print(f"Problem: {url}: {e}")
            return None

    async def async_call_release(self):
        async with aiohttp.ClientSession() as session:
            tasks = [self.fetch(session, url) for url in self.urls]
            return await asyncio.gather(*tasks)

    async def async_call_bo(self):
        async with aiohttp.ClientSession() as session:
            tasks = [self.fetch(session, url) for url in self.bo_urls]
            return await asyncio.gather(*tasks)

# 在视图中使用
class BatchAPIView(View):
    def get(self, request):
        urls = ["url1", "url2"]  # 替换为你的URL列表
        bo_urls = ["bo_url1", "bo_url2"]
        batch_call = BatchCall(urls, bo_urls, "your_username", "your_password")
        
        # 同步调用异步方法
        results_release = async_to_sync(batch_call.async_call_release)()
        results_bo = async_to_sync(batch_call.async_call_bo)()
        
        return HttpResponse(f"Release Results: {results_release}<br>BO Results: {results_bo}")

验证方法

应用猴子补丁后,使用python manage.py runserver --noreload启动服务器,执行批量API调用,检查是否能完整执行所有100个请求且不退出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 12:18:28