讲师中心 微信公众号
AI工具推荐 视频效率加速

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

风芳大大_1775

风芳大大_1775

发布时间:2026-06-02 14:43:46

|

465人浏览过

|

来源于php中文网

原创

本文讲解如何解决使用 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。以下为生产就绪的实现方案:

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% 降至 <0.5%,且内存占用稳定可控。记住——异步 ≠ 无约束并发,真正的高性能来自有节制的并行鲁棒的错误恢复

热门AI工具

更多
音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

Seko
Seko Hot

一款AI视频创作工具,主要用于商汤科技推出的创编一体的AI短视频创作Agent,适合需要提升相关任务效率的用户。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

相关专题

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

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

1571

2023.07.20

python能做什么
python能做什么

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

3704

2023.07.25

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

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

1569

2023.07.31

python教程
python教程

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

21257

2023.08.03

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

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

2627

2023.08.04

python eval
python eval

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

2687

2023.08.04

scratch和python区别
scratch和python区别

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

1083

2023.08.11

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

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

576

2023.08.10

Buffalo框架数据库开发全教程
Buffalo框架数据库开发全教程

本专题围绕Buffalo框架数据库开发,讲解database.yml多环境配置、soda与fizz迁移生成回滚、模型结构体标签、增删改查与条件查询、一对多与多对多关联、数据校验、回调钩子、事务处理及原生SQL执行能力。

20

2026.09.23

热门下载

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

精品课程

更多
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn