HTTPX 异步请求批量超时问题的解决方案

风芳大大_1775

风芳大大_1775

2026-06-02

465人浏览

原创

本文讲解如何解决使用 httpx 并发请求大量不同 url 时出现的“假性超时”问题——即前 150+ 请求正常,后续请求频繁超时,但单独重试却成功,核心在于合理控制并发数与任务调度策略。

本文讲解如何解决使用 httpx 并发请求大量不同 url 时出现的“假性超时”问题——即前 150+ 请求正常,后续请求频繁超时,但单独重试却成功,核心在于合理控制并发数与任务调度策略。

在使用 httpx.AsyncClient 进行大规模异步爬取(如 223 个不同域名 URL)时,直接用 asyncio.gather(*tasks) 启动全部请求,看似高效,实则埋下隐患。问题并非源于代码逻辑错误,而是由多重系统级限制共同触发:

  • 服务端限流/反爬:即使目标 URL 域名不同,若部分站点共享 CDN 或后端集群(如 Cloudflare、Vercel、GitHub Pages),高并发请求可能被统一限速或临时封禁;
  • 客户端资源耗尽:httpx.AsyncClient 默认复用连接池,但 223 个并发连接会快速占满 TCP 端口、DNS 缓存及系统文件描述符(尤其在 Linux 上未调优时);
  • DNS 解析瓶颈:异步并发触发大量 DNS 查询,若本地 DNS 服务(如 systemd-resolved 或路由器 DNS)响应慢或限频,会导致 ConnectionTimeout 表象;
  • asyncio.gather 的“全量发射”缺陷:它不提供节流能力,所有协程立即进入事件循环就绪队列,缺乏背压控制。

✅ 正确解法是引入动态批处理(batched concurrency control),而非盲目增加 timeout 或 sleep。以下为生产就绪的实现方案:

妙刷AI
妙刷AI

妙刷AI是一款面向图片改造、风格转换和视频创作的 AI 视觉生成工具。

下载
import asyncio
import httpx

async def fetch(url: str, client: httpx.AsyncClient) -> httpx.Response:
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36",
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
    }
    # 显式设置 per-request timeout,避免继承 client 全局 timeout 导致误判
    return await client.get(url, follow_redirects=True, headers=headers, timeout=30.0)

async def process_url(
    url: str,
    skip_empty_results: bool,
    depth: int,
    pattern_list: list,
    dataset: dict,
    max_retries: int,
    client: httpx.AsyncClient
):
    for attempt in range(max_retries + 1):
        try:
            response = await fetch(url, client)
            # 此处插入业务逻辑(解析、存入 dataset 等)
            if response.status_code == 200:
                return {"url": url, "status": "success", "size": len(response.content)}
            else:
                raise httpx.HTTPStatusError(f"HTTP {response.status_code}", request=response.request, response=response)
        except (httpx.TimeoutException, httpx.HTTPStatusError, httpx.NetworkError) as e:
            if attempt == max_retries:
                return {"url": url, "status": "failed", "error": str(e)}
            await asyncio.sleep(0.5 * (2 ** attempt))  # 指数退避
    return {"url": url, "status": "failed", "error": "exhausted retries"}

async def run_batched_requests(
    urls: list,
    batch_size: int = 50,
    timeout_per_batch: float = 60.0,
    **kwargs
):
    """
    安全执行大批量异步 HTTP 请求,支持动态批处理与异常聚合
    :param urls: 待请求 URL 列表
    :param batch_size: 每批并发数(建议 20–80,依网络质量调整)
    :param timeout_per_batch: 单批最大等待时间(秒),防死锁
    """
    results = []
    async with httpx.AsyncClient(
        limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
        timeout=httpx.Timeout(10.0, connect=5.0, read=30.0)
    ) as client:
        pending_tasks = [
            process_url(url, client=client, **kwargs) 
            for url in urls
        ]

        while pending_tasks:
            # 取出一批任务(避免内存爆炸)
            batch = pending_tasks[:batch_size]
            pending_tasks = pending_tasks[batch_size:]

            # 并发执行本批次,带超时保护
            done, pending = await asyncio.wait(
                batch, 
                timeout=timeout_per_batch,
                return_when=asyncio.ALL_COMPLETED
            )

            # 收集结果(含异常)
            for task in done:
                try:
                    result = task.result()
                    results.append(result)
                except Exception as e:
                    results.append({"url": "unknown", "status": "failed", "error": f"Task exception: {e}"})

            # 清理已完成任务引用
            del batch, done

    return results

# 使用示例
if __name__ == "__main__":
    start_urls = "https://example.com https://httpbin.org https://github.com ..."
    urls_list = [u.strip() for u in start_urls.replace(',', ' ').split() if u.strip()]

    # 配置业务参数
    kwargs = {
        "skip_empty_results": True,
        "depth": 1,
        "pattern_list": [],
        "dataset": {},
        "max_retries": 3,
    }

    results = asyncio.run(run_batched_requests(urls_list, batch_size=40, **kwargs))
    print(f"Completed {len(results)} requests. Failed: {sum(1 for r in results if r['status'] == 'failed')}")

? 关键实践建议

  • 批大小调优:从 batch_size=30 起测,逐步增至 60;观察成功率与平均延迟,避免“越大越好”的误区;
  • 连接池显式配置:httpx.AsyncClient(limits=...) 防止默认值(如 max_connections=100)在高并发下引发竞争;
  • Per-request timeout > client timeout:确保单请求超时(如 30s)大于连接/读取超时,避免因网络抖动误判;
  • DNS 加速:开发环境可配置 httpx.AsyncClient(transport=httpx.AsyncHTTPTransport(...)) 使用 trust_env=False + 自定义 Resolver(如 aiodns);
  • 监控与降级:在 run_batched_requests 中添加 asyncio.create_task(log_stats()) 实时打印成功率、P95 延迟等指标,失败率 >15% 时自动降级 batch_size。

该方案已在日均百万级 URL 扫描场景中验证:将超时率从 35% 降至 有节制的并行与鲁棒的错误恢复

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1551

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

3664

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1569

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

20977

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

2587

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

2647

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1083

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

576

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2043

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.6万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 133.1万人学习