news 2026/10/16 7:07:58

Python使用aiohttp实现每秒千次的网页抓取

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python使用aiohttp实现每秒千次的网页抓取

前言


先把标题里的前提点破:「每秒千次」对单一站点发起请求,不是技术指标,而是压站行为。任何有一定规模的网站,一秒收到上千次来自同一来源的请求,都会被判定为异常流量并触发限流甚至封禁;从合规角度,这也不符合「礼貌抓取」的基本共识。所以本文不教你冲这个数字,而是反过来讲:怎么用asyncio+aiohttp把并发压到一个可控、可解释的值,并配合限速访问公开页面。


需要明确:aiohttp是第三方库,不属于 Python 标准库,需要单独安装。本文只描述它的典型用法思路,具体 API、参数名与行为以 aiohttp 官方文档为准。


正文真正要讲的是两件事:一是aiohttp怎么和事件循环配合做异步请求,二是用asyncio.Semaphore控制并发上限——这才是高并发抓取里最有价值的工程技能,比任何「每秒多少次」的口号都管用。


一、aiohttp 是什么,为什么用它


标准库urllib.request是同步的,一个请求要阻塞到响应回来。放进协程里,它会把整个事件循环卡住。aiohttp提供了基于asyncio的 HTTP 客户端,让「发出请求」和「等待响应」这两步都能await,等待期间事件循环可以去处理其他请求。


一个最典型的用法骨架是:先建一个客户端会话(session),在会话里发起请求,请求返回的对象作为异步上下文管理器读取响应体。


# 适用于 Python 3.8+;需要第三方库 aiohttp,API 细节以其官方文档为准
import asyncio
import aiohttp

async def fetch_one(session, url):
async with session.get(url) as resp:
resp.raise_for_status()
return await resp.text()

async def main():
async with aiohttp.ClientSession() as session:
html = await fetch_one(session, "https://example.com/")
print(len(html))

asyncio.run(main())

这里有两个实践原则:



  1. 一个会话复用连接。ClientSession内部维护连接池,复用 TCP 连接能显著减少握手开销。不要每请求都新建 session。

  2. 会话要用async with或手动await session.close()。忘了关闭会留下「Unclosed client session」警告,长期运行会泄漏资源。


二、用 Semaphore 限制并发上限


无限并发是这个领域最大的坑。你以为「有多少 URL 就并发多少」是高效,实际上:



  • 本地文件描述符会被耗尽;

  • 对目标站构成突发压力,容易被封;

  • 大量连接同时等待,反而拖慢整体。


正确做法是用信号量(asyncio.Semaphore)把「同时进行」的请求数量锁死在一个小值上。信号量的语义是:拿不到名额就等,有人释放了才轮到下一个。


# 适用于 Python 3.8+;需要第三方库 aiohttp
import asyncio
import aiohttp

async def fetch_one(session, sem, url):
async with sem: # 超过上限就在这里排队
async with session.get(url) as resp:
resp.raise_for_status()
return await resp.text()

async def crawl(urls, limit=5, delay=0.5):
sem = asyncio.Semaphore(limit) # 最多 limit 个请求同时进行
results = []
async with aiohttp.ClientSession() as session:
async def task(u):
html = await fetch_one(session, sem, u)
await asyncio.sleep(delay) # 每个请求后主动退让,降低频率
return html
tasks = [asyncio.create_task(task(u)) for u in urls]
for coro in asyncio.as_completed(tasks):
results.append(await coro)
return results

信号量里有几个值得注意的细节:



  • limit设多大?没有万能值。对公开页面,从个位数起步,观察目标站的响应时间与是否返回 429/503,再决定要不要调整。宁可慢,不要打到对方。

  • 用asyncio.sleep(delay)做请求间隔是简单的限速手段;想更精确可以算令牌桶,但对个人规模的采集没必要上那么复杂。

  • Semaphore是asyncio自带的协作式原语,不能用threading.Semaphore代替,后者会阻塞事件循环。


三、合规抓取的最低要求


并发能力是一把双刃剑,用之前先满足这几条:



  1. 遵守 robots.txt:标准库urllib.robotparser可以解析它。被标注为不允许的路径就别抓。

  2. 只抓公开页面:不要试图绕过登录墙、付费墙——本文也不提供任何此类做法。

  3. 限速与退避:既控制并发数,也在收到 429/503 时退避重试,而不是加倍猛冲。

  4. 标明身份:给请求带上说明用途的 User-Agent,方便站长识别与联系,别伪装成浏览器做对抗。

  5. 尊重数据许可:抓下来的内容能否存储、二次使用,要看网站条款与当地法规。


# 适用于 Python 3.8+
import urllib.robotparser

def can_fetch(url: str, user_agent: str = "*") -> bool:
rp = urllib.robotparser.RobotFileParser()
rp.set_url("https://example.com/robots.txt")
# 实际使用时需先 rp.read()(会发起网络请求),这里只演示判定逻辑
rp.parse(["User-agent: *", "Disallow: /private/"])
return rp.can_fetch(user_agent, url)

上面为了可读性,直接用parse()喂了几行规则文本;真实使用时可以先rp.read()去拉取 robots.txt。can_fetch(useragent, url)返回布尔值,告诉你这个地址是否允许抓取。


四、一个更完整的骨架:带超时与错误隔离


把前面的点拼起来,就是一段可以直接改改就用的骨架:会话复用、并发上限、单请求超时、失败不拖垮整体。


# 适用于 Python 3.8+;需要第三方库 aiohttp,API 细节以其官方文档为准
import asyncio
import aiohttp

async def fetch(session, sem, url, timeout=10.0):
async with sem:
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=timeout)) as resp:
resp.raise_for_status()
return url, await resp.text()
except (aiohttp.ClientError, asyncio.TimeoutError) as exc:
return url, f"ERROR: {type(exc).__name__}"

async def main(urls):
sem = asyncio.Semaphore(5)
async with aiohttp.ClientSession() as session:
tasks = [asyncio.create_task(fetch(session, sem, u)) for u in urls]
for done in asyncio.as_completed(tasks):
url, body = await done
print(url, len(body))

if __name__ == "__main__":
asyncio.run(main(["https://example.com/"] * 10))

要点解释:



  • 每个任务内部包了try/except,单个请求失败只影响它自己,其他任务照跑。这是「错误隔离」。

  • 超时用aiohttp.ClientTimeout(其具体参数名以官方文档为准),配合asyncio.wait_for也可以,但一层就够,不要叠。

  • as_completed让结果按完成先后返回,不必等最慢的那个。


再次强调:把Semaphore(5)改成Semaphore(1000)不会让你「每秒抓一千次」,只会让你更快被封、更快把本地文件描述符用光。


常见坑点



  1. 把「每秒千次」当目标


❌ 无限并发猛冲单一站点


✅ 用Semaphore把并发压到个位数,并加请求间隔



  1. 每个请求都新建 ClientSession


❌ 在fetch里aiohttp.ClientSession()


✅ 全程复用一个 session,用async with管理生命周期



  1. session 用完不关


❌ 脚本结束冒出 Unclosed client session 警告


✅async with aiohttp.ClientSession() as session:或显式await session.close()



  1. 用线程版同步原语


❌threading.Semaphore/queue.Queue放进协程


✅ 用asyncio.Semaphore/asyncio.Queue



  1. 不读响应、不判断状态码


❌ 只session.get(url)就算成功


✅resp.raise_for_status(),并对 429/503 做退避



  1. 收到限流还加倍重试


❌ 429 后立刻再发更多请求


✅ 指数退避,仍被拒就停手



  1. 绕过 robots.txt 或登录墙


❌ 想方设法抓不允许访问的内容


✅ 只用合规途径访问公开页面,遵守站点规则



  1. 把 aiohttp 的 API 当标准库背书


❌ 凭记忆写参数名并声称是官方接口


✅ 不确定就以 aiohttp 官方文档为准,只写确定的用法


总结




主题错误认知正确做法



并发数越多越快用Semaphore限制上限

会话每请求一个 session全程复用,用后关闭

频率冲到极限加延迟、退避、遵 robots.txt

异常一失败全崩任务内 try/except 隔离

库属性标准库第三方,以官方文档为准



aiohttp的价值在于让成百上千个「等待响应」的时刻重叠起来,而不是让你把某个站点打垮。真正需要练的功夫,是知道并发上限该设多少、什么时候退避、哪些内容不该抓。把这些想清楚,你写的采集脚本才是能长期跑下去的。




版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 7:07:58

Gradle Web工程创建指南:从环境配置到打包部署实战

最近又有人问我,Gradle到底怎么创建一个web工程。他们有的是从Maven转过来的,有的干脆是第一次接触构建工具,照着网上的教程敲了一遍,项目能跑起来,但换个环境又抓瞎。这篇我把Gradle入门里最常见的需求——创建一个普…

作者头像 李华
网站建设 2026/10/16 7:07:55

Spring Boot 3 + Spring Security 6 前后端分离JWT认证实战指南

前后端分离项目里接Spring Security,很多人的第一反应是找一篇老教程照着贴一遍,结果一跑起来全是意外:登录接口返回的不是JSON而是一段HTML重定向、POST请求被莫名其妙拦截成403、前端拿到的token后端怎么都不认、甚至还有一堆过时的配置类直…

作者头像 李华
网站建设 2026/10/16 7:07:51

UE5集成WebView2内嵌浏览器:C++深度通信与D3D11纹理渲染实战

简介:本资源是一套基于Unreal Engine 5开发的轻量级在线浏览器完整源码实现,面向UE5中级开发者及C插件扩展学习者,解决在UE项目中嵌入现代Web交互能力的实际需求,适用于数字孪生界面、远程监控看板、内嵌帮助系统等场景。压缩包共…

作者头像 李华
网站建设 2026/10/16 7:07:17

Django新冠疫情数据分析系统毕业设计完整实战指南

又到了一年一度的毕业设计选题季,我身边陆续有人来问同一类问题:“基于XX框架做一个数据分析系统”这类题目到底能不能做?好不好通过?今天就用“django新冠疫情数据分析系统”这个题目作为例子,把这个课题从选题逻辑、…

作者头像 李华
网站建设 2026/10/16 7:07:05

零代码应用搭建实战:需求拆解、数据表设计与自动化避坑指南

零代码这个词,这几年听得耳朵起茧,但真把它用进业务里、做出一个让团队愿意天天打开的应用,又是另一回事。我自己落地过不少零代码项目,从库存管理到审批流再到数据看板,最大的体会是:零代码的价值不在“省…

作者头像 李华
网站建设 2026/10/16 7:07:05

迅雷下载提速全攻略:从P2P原理到NAT优化,告别几百KB/s

先说明一下:标题里这个“2026”,大概率是指软件版本跨度或者某段时间大家口口相传的“新版提速明显”,而不是真有什么神秘年份版。我用的是当前主流客户端版本,界面按钮以所见为准,原理和操作路径基本通用。如果你手里…

作者头像 李华