ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python异步下载实战:asyncio与aiohttp高效并发方案

2026/9/21 15:12:45 拓冰建站 浏览量
Python异步下载实战:asyncio与aiohttp高效并发方案 1. 异步下载的核心价值与场景需求在当今互联网环境下文件下载是几乎每个开发者都会遇到的基础需求。但传统同步下载方式在面对大量文件或大体积文件时往往会遇到性能瓶颈。我曾在实际项目中遇到过需要同时下载数百个日志文件的情况使用requests库串行下载耗时长达40分钟而改用异步方案后仅需3分钟——这就是异步下载的威力。异步下载特别适合以下场景需要同时下载多个独立文件如图片、文档、媒体资源文件分布在不同的服务器或CDN节点上网络延迟较高但带宽充足的情况需要避免因单个下载失败导致整个任务中断2. 技术选型asyncio aiohttp 组合解析2.1 asyncio 的事件循环机制Python内置的asyncio库通过事件循环(event loop)实现异步IO。与多线程不同它采用单线程内协作式多任务通过await将控制权交还事件循环在IO等待期间可以执行其他任务。这种机制特别适合网络请求这类IO密集型操作。关键优势没有线程切换开销避免多线程的锁竞争问题代码结构更清晰相比回调地狱2.2 aiohttp 的异步HTTP客户端aiohttp是基于asyncio的HTTP客户端/服务端框架其核心特性包括完整的HTTP协议支持连接池管理超时控制支持WebSocket自动处理响应解码与requests的对比特性requestsaiohttp异步支持❌✅连接池✅✅性能一般优秀API复杂度简单中等3. 实现高并发下载的完整方案3.1 基础下载器实现import aiohttp import asyncio import os async def download_file(url, save_path, session): async with session.get(url) as response: with open(save_path, wb) as f: while True: chunk await response.content.read(1024) if not chunk: break f.write(chunk) return save_path3.2 并发控制与错误处理async def batch_download(url_list, save_dir, max_concurrent10): connector aiohttp.TCPConnector(limitmax_concurrent) timeout aiohttp.ClientTimeout(total3600) async with aiohttp.ClientSession(connectorconnector, timeouttimeout) as session: tasks [] for idx, url in enumerate(url_list): save_path os.path.join(save_dir, ffile_{idx}) task asyncio.create_task( download_file(url, save_path, session) ) tasks.append(task) results await asyncio.gather(*tasks, return_exceptionsTrue) success 0 for result in results: if not isinstance(result, Exception): success 1 print(f下载完成: {success}/{len(url_list)})3.3 性能优化技巧连接池调优TCPConnector(limit_per_host5)限制单个主机连接数复用Session对象避免重复建立SSL连接缓冲区设置async with session.get(url, read_bufsize32768) as response:进度显示async def download_with_progress(url, save_path, session): async with session.get(url) as response: total int(response.headers.get(content-length, 0)) with open(save_path, wb) as f: downloaded 0 async for chunk in response.content.iter_chunked(8192): f.write(chunk) downloaded len(chunk) print(f\r{downloaded/total:.1%}, end)4. 实战中的坑与解决方案4.1 常见问题排查SSL证书错误connector aiohttp.TCPConnector(sslFalse) # 不推荐生产环境使用连接泄露确保所有response对象都被正确关闭使用async with管理资源内存暴涨避免将大文件完全读入内存使用流式下载(chunked)4.2 高级技巧断点续传实现headers {Range: fbytes{os.path.getsize(save_path)}-} if os.path.exists(save_path) else None async with session.get(url, headersheaders) as response:代理支持async with aiohttp.ClientSession(proxyhttp://proxy.example.com) as session:速率限制async def limited_download(semaphore, url, save_path, session): async with semaphore: return await download_file(url, save_path, session) sem asyncio.Semaphore(10) # 并发数限制5. 性能对比测试使用100个1MB文件的下载测试结果方式耗时(s)CPU占用内存(MB)同步(requests)58.315%50异步(aiohttp)6.735%80多线程(10线程)9.260%120测试环境Python 3.8, 100Mbps网络在实际项目中我发现当并发数超过50时需要特别注意操作系统的文件描述符限制(ulimit -n)目标服务器的反爬机制本地磁盘IO可能成为瓶颈对于持续运行的下载服务建议添加自动重试机制下载结果持久化记录实时监控告警最后分享一个实用技巧使用aiohttp.ClientTimeout设置分层超时timeout aiohttp.ClientTimeout( total3600, # 总超时 connect30, # 连接超时 sock_connect15, # socket连接超时 sock_read60 # socket读取超时 )