在处理多个远程文件下载任务时,如果采用传统的同步方式(如 requests.get() 逐个下载),程序会因等待网络 I/O 而长时间阻塞,效率极低。
本文将教你如何使用 Python 的异步编程模型(asyncio)配合 aiohttp 库 ,实现真正高效的异步并发下载,大幅提升下载速度,尤其适用于批量下载图片、视频、压缩包等场景。
一、为什么选择 aiohttp + asyncio?
- ✅
aiohttp是基于asyncio的高性能异步 HTTP 客户端/服务器框架; - ✅ 支持异步请求、自动连接池、重定向、超时控制;
- ✅ 语法简洁,性能远超多线程或同步方案;
- ✅ 适合 I/O 密集型任务(如网络下载)。
💡 注意:Python 的"异步" ≠ "多线程",它是通过事件循环(Event Loop)在单线程中并发处理多个 I/O 操作,避免阻塞。
二、安装依赖
首先安装 aiohttp(若未安装):
bash
pip install aiohttp
推荐同时安装
aiofiles(用于异步写入文件):
bashpip install aiofiles
三、核心代码实现
方法一:将内容全部加载到内存后保存(适合中小文件)
python
import asyncio
import aiohttp
import os
async def download_file(session, url, filename):
"""异步下载单个文件"""
try:
async with session.get(url) as response:
if response.status == 200:
content = await response.read()
with open(filename, 'wb') as f:
f.write(content)
print(f"✅ 下载成功: {filename}")
else:
print(f"❌ 下载失败 ({response.status}): {url}")
except Exception as e:
print(f"⚠️ 下载出错: {url} | 错误: {e}")
async def download_all(urls, folder="downloads"):
"""并发下载多个文件"""
os.makedirs(folder, exist_ok=True)
# 创建 aiohttp 客户端会话(支持连接复用)
async with aiohttp.ClientSession() as session:
tasks = []
for i, url in enumerate(urls):
# 生成文件名(可根据需要自定义)
ext = os.path.splitext(url.split('?')[0])[-1] or '.bin'
filename = os.path.join(folder, f"file_{i+1}{ext}")
task = asyncio.create_task(download_file(session, url, filename))
tasks.append(task)
# 并发执行所有下载任务
await asyncio.gather(*tasks)
# 使用示例
if __name__ == "__main__":
urls = [
"https://example.com/file1.zip",
"https://example.com/file2.jpg",
"https://example.com/file3.pdf",
]
print("🚀 开始异步下载...")
asyncio.run(download_all(urls))
print("🎉 所有文件下载完成!")
方法二:流式下载(适合大文件,节省内存)
对于大文件(如视频、ISO 镜像),建议使用流式写入,避免一次性加载整个文件到内存:
python
import aiofiles # 需要额外安装
async def download_file_stream(session, url, filename):
try:
async with session.get(url) as response:
if response.status == 200:
async with aiofiles.open(filename, 'wb') as f:
async for chunk in response.content.iter_chunked(8192):
await f.write(chunk)
print(f"✅ 流式下载成功: {filename}")
else:
print(f"❌ 下载失败 ({response.status}): {url}")
except Exception as e:
print(f"⚠️ 流式下载出错: {url} | 错误: {e}")
# 在 download_all 中替换 download_file 为 download_file_stream 即可
四、性能优势对比
| 方式 | 3 个 50MB 文件耗时(估算) |
|---|---|
| 同步下载(requests) | ~45 秒 |
| 多线程(ThreadPoolExecutor) | ~20 秒 |
| 异步下载(aiohttp + asyncio) | ~15--18 秒 |
📌 实际提升取决于网络带宽和服务器并发能力。异步方案在高延迟或大量小文件场景下优势更明显。
五、进阶优化建议
-
限制并发数量
若 URL 数量极大(如 1000+),可使用
asyncio.Semaphore控制并发数,避免打爆目标服务器或本地资源:pythonsemaphore = asyncio.Semaphore(10) # 最多 10 个并发 async def download_file_limited(session, url, filename): async with semaphore: await download_file(session, url, filename) -
添加 User-Agent 和超时
pythontimeout = aiohttp.ClientTimeout(total=30) async with aiohttp.ClientSession( timeout=timeout, headers={"User-Agent": "Mozilla/5.0 (Python aiohttp)"} ) as session: -
自动从 URL 提取文件名
pythonfrom urllib.parse import urlparse import os def get_filename_from_url(url): path = urlparse(url).path name = os.path.basename(path) return name if name else "downloaded_file"
六、总结
- ✅ 使用
asyncio + aiohttp是 Python 实现高并发下载的最佳实践; - ✅ 异步下载能显著提升效率,尤其适合批量、I/O 密集型任务;
- ✅ 对于大文件,推荐使用流式下载 + aiofiles 避免内存溢出;
- ✅ 生产环境中务必加入错误处理、超时控制、并发限制。
掌握这套异步下载方案,你就能轻松应对各种批量资源采集、自动化备份、数据同步等需求!