aiohttp(基于asyncio)批量请求效率低下问题求助
批量获取域名跳转URL的性能瓶颈排查与优化方案
嘿,我来帮你拆解下这个问题——从每秒100次直接掉到5秒1次,还远达不到教程里的10万次/分钟,大概率是你的脚本在并发控制、资源管理或者请求配置上踩了坑。咱们一步步来排查和优化:
一、先揪出最常见的坑:并发控制没做好
教程里的10万次/分钟是高并发优化后的极限性能,如果你的脚本是串行请求或者并发数没调好,肯定达不到预期,甚至越跑越慢:
- 要是你用的是
requests这种同步库串行发请求:每秒100次都算不错了,因为同步请求是阻塞的——必须等前一个请求拿到响应,才能发下一个,完全没利用好机器的网络能力; - 要是用了
aiohttp这类异步库,但没控制并发量:一下子发起上万个请求,系统的文件句柄、网络连接池直接被占满,操作系统开始限制新连接,请求就会排队,越积越多,最后慢到离谱。
对应的优化方案:
用异步框架+信号量严格控制并发数,比如先从500-1000的并发量开始试,根据你的机器性能调整。给你个极简示例:
import aiohttp import asyncio async def get_final_url(session, domain): try: # 允许自动跳转,设置超时避免拖慢整体 async with session.get(f"http://{domain}", allow_redirects=True, timeout=10) as resp: return (domain, str(resp.url)) except Exception as e: return (domain, f"Error: {str(e)}") async def main(domain_list): # 信号量控制并发数,这里设800,你可以根据机器情况增减 semaphore = asyncio.Semaphore(800) # 复用一个ClientSession,关键!不然每次请求都新建连接 async with aiohttp.ClientSession() as session: tasks = [] for domain in domain_list: # 用信号量包装任务,限制并发 async def wrapped_task(d): async with semaphore: return await get_final_url(session, d) tasks.append(asyncio.create_task(wrapped_task(domain))) # 批量执行任务 results = await asyncio.gather(*tasks) return results if __name__ == "__main__": # 替换成你的域名列表 domains = ["example.com", "test.com", "github.com"] final_results = asyncio.run(main(domains)) for domain, url in final_results: print(f"{domain} -> {url}")
二、别忽略:HTTP连接池必须复用
每次请求都新建连接,会消耗大量的TCP握手、TLS握手时间,这也是导致脚本越跑越慢的关键:
- 同步库
requests要复用同一个requests.Session()实例,而不是每次请求都新建; - 异步库
aiohttp.ClientSession本身就是复用连接池的,但要确保整个脚本只用一个Session,别在循环里反复创建。
三、警惕被限流:服务器会“拉黑”高频请求
短时间内发大量请求,很多域名的服务器会触发限流机制(返回429错误),甚至直接拒绝连接。如果你的脚本没处理这种情况,就会卡在等待超时上,整体速度暴跌:
优化点:
- 给请求加合理的超时时间:比如10秒,避免一个慢请求拖垮整个任务队列;
- 加指数退避重试:第一次失败等1秒,第二次等2秒,最多重试3次,别死磕一个失败的请求;
- 加随机延迟:给每个请求加个几毫秒的随机延迟,模拟人类请求的随机性,降低被限流的概率。
四、检查系统资源是否被耗尽
你的机器CPU、内存、网络带宽可能撑不住高并发请求:
- 看CPU使用率:如果跑满了,说明并发数太高,或者脚本有不必要的CPU密集型逻辑;
- 看网络带宽:如果上传/下载带宽跑满了,请求自然会排队;
- 调大文件句柄数:Linux下默认文件句柄数只有1024,高并发下不够用,可以用
ulimit -n 65535临时调高(永久修改需要改系统配置)。
五、DNS解析可能拖后腿
批量请求大量不同域名时,DNS解析会成为隐形瓶颈——每次请求都要先解析域名到IP,解析慢了整个请求就慢了:
优化方案:
- 用更快的DNS服务器:比如把系统DNS改成1.1.1.1或者8.8.8.8;
- 提前批量解析所有域名的IP:然后直接请求IP,加上
Host头,跳过每次请求的DNS解析步骤。示例代码片段:
import socket # 提前批量解析域名 def pre_resolve_domains(domains): domain_ip_map = {} for domain in domains: try: ip = socket.gethostbyname(domain) domain_ip_map[domain] = ip except socket.gaierror: domain_ip_map[domain] = None return domain_ip_map # 请求时直接用IP,加Host头 async def get_final_url_by_ip(session, domain, ip): if not ip: return (domain, "DNS解析失败") try: async with session.get( f"http://{ip}", headers={"Host": domain}, allow_redirects=True, timeout=10 ) as resp: return (domain, str(resp.url)) except Exception as e: return (domain, f"Error: {str(e)}")
最后总结
先从并发控制+连接池复用入手,这两个是最容易快速提升性能的;然后排查是否被限流,调整重试和延迟策略;最后检查系统资源和DNS解析。一步步调整,应该能接近教程的性能水平。
内容的提问来源于stack exchange,提问作者saladzic
相关产品推荐
相关产品推荐

