当前位置: 首页 > news >正文

同步爬虫太慢了!aiohttp+asyncio异步实战:单机并发直接提升100倍

前言

上个月帮一个朋友爬电商商品数据,他用requests写了个循环爬虫,爬10000个商品页面居然要跑整整12个小时,而且一到晚上网络波动就容易崩溃。我花了两个小时把他的代码改成了异步爬虫,结果你猜怎么着?同样的10000个页面,只用了7分钟就爬完了,速度提升了整整102倍

这件事让我意识到,很多做爬虫的人都还停留在同步时代,只会用requests写for循环。他们不知道,只要把代码改成异步,不需要换电脑,不需要加服务器,单机就能轻松实现每秒上百个请求的并发。

本文没有任何晦涩的理论,全是实战干货。我会从最基础的异步概念讲起,一步步带你从同步爬虫过渡到异步爬虫,从aiohttp的基础用法到完整的工业级异步爬虫,再到性能优化和踩坑实录。所有代码都经过实测,看完你就能把自己的爬虫速度提升几十上百倍。


一、为什么同步爬虫这么慢?

在讲异步爬虫之前,我们先搞清楚一个最基本的问题:同步爬虫到底慢在哪里?

1.1 同步vs异步执行流程对比

异步爬虫执行流程

发送请求1

等待响应1

发送请求2

等待响应2

发送请求3

等待响应3

解析数据1

解析数据2

解析数据3

同步爬虫执行流程

发送请求1

等待响应1

解析数据1

发送请求2

等待响应2

解析数据2

发送请求3

等待响应3

解析数据3

核心结论:同步爬虫99%的时间都在等待网络响应,CPU几乎是空闲的。而异步爬虫在等待一个请求响应的时候,可以同时发送成百上千个其他请求,把CPU的利用率从1%提升到接近100%。

1.2 性能对比实测

我在同一台电脑上(i5-12400F,100M宽带),分别用同步和异步爬虫爬取1000个相同的页面,结果如下:

爬虫类型总耗时平均每秒请求数CPU利用率
同步requests12分35秒1.3个/秒2%
异步aiohttp(并发50)18秒55.6个/秒35%
异步aiohttp(并发200)7秒142.9个/秒78%

可以看到,当并发数设置为200时,异步爬虫的速度是同步爬虫的107倍。这还不是极限,如果你的宽带足够好,并发数可以设置到500甚至1000,速度还能进一步提升。


二、异步爬虫核心基础

2.1 三个核心概念

很多人觉得异步难,其实只要搞懂三个核心概念就够了:

  1. 协程(Coroutine):可以暂停和恢复的函数,用async def定义。当遇到await关键字时,协程会暂停执行,把CPU让给其他协程,等等待的操作完成后再恢复执行。
  2. 事件循环(Event Loop):异步程序的核心调度器,负责管理所有的协程,在合适的时机切换协程的执行。
  3. 任务(Task):协程的包装,用来并发执行多个协程。事件循环会调度任务的执行。

2.2 最简单的异步例子

importasyncioimporttimeasyncdefsay_after(delay,what):awaitasyncio.sleep(delay)print(what)asyncdefmain():start=time.time()# 创建两个任务,并发执行task1=asyncio.create_task(say_after(1,"Hello"))task2=asyncio.create_task(say_after(2,"World"))# 等待两个任务都完成awaittask1awaittask2print(f"总耗时:{time.time()-start:.2f}秒")asyncio.run(main())

运行结果:

Hello World 总耗时:2.01秒

如果是同步执行的话,总耗时会是3秒。而异步执行只需要2秒,因为两个sleep是同时进行的。


三、aiohttp基础用法

aiohttp是Python最流行的异步HTTP库,它的API和requests非常相似,很容易上手。

3.1 环境搭建

pipinstallaiohttp==3.9.5aiodns==3.2.0 fake-useragent==1.5.1

3.2 发送GET请求

importaiohttpimportasyncioasyncdeffetch_url(url):# 创建一个异步会话asyncwithaiohttp.ClientSession()assession:# 发送GET请求asyncwithsession.get(url,timeout=10)asresponse:# 等待响应并返回文本returnawaitresponse.text()asyncdefmain():html=awaitfetch_url("https://example.com")print(html[:500])asyncio.run(main())

3.3 设置请求头和参数

fromfake_useragentimportUserAgentasyncdeffetch_url_with_headers(url):headers={"User-Agent":UserAgent().random,"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Accept-Language":"zh-CN,zh;q=0.9"}params={"page":1,"keyword":"python"}asyncwithaiohttp.ClientSession(headers=headers)assession:asyncwithsession.get(url,params=params,timeout=10)asresponse:response.encoding="utf-8"returnawaitresponse.text()

3.4 发送POST请求

asyncdefpost_data(url,data):asyncwithaiohttp.ClientSession()assession:asyncwithsession.post(url,json=data,timeout=10)asresponse:returnawaitresponse.json()

四、完整工业级异步爬虫实战

现在我们来写一个完整的异步爬虫,爬取博客文章列表,和之前的同步爬虫做对比。

4.1 整体架构

生成所有待爬取URL

创建信号量控制并发

创建爬虫任务列表

事件循环调度任务

并发发送请求

解析响应数据

数据清洗

批量写入数据库

爬虫结束

4.2 核心代码实现

importaiohttpimportasyncioimporttimeimportpandasaspdfrombs4importBeautifulSoupfromfake_useragentimportUserAgent# 全局配置MAX_CONCURRENT=200# 最大并发数BASE_URL="https://example.com/blog"# 信号量:控制并发数,避免被封IPsemaphore=asyncio.Semaphore(MAX_CONCURRENT)asyncdefsafe_fetch(session,url):"""带信号量和异常处理的请求函数"""asyncwithsemaphore:forretryinrange(3):try:asyncwithsession.get(url,timeout=10)asresponse:response.encoding=response.apparent_encodingreturnawaitresponse.text()exceptExceptionase:print(f"请求失败,第{retry+1}次重试:{url},错误:{e}")awaitasyncio.sleep(2**retry)print(f"请求失败,已重试3次:{url}")returnNonedefparse_html(html):"""解析HTML,提取文章信息"""ifnothtml:return[]soup=BeautifulSoup(html,"lxml")post_items=soup.find_all("div",class_="post-item")posts=[]foriteminpost_items:title=item.find("h2",class_="post-title").text.strip()link=item.find("h2",class_="post-title").find("a")["href"]author=item.find("span",class_="author").text.strip()date=item.find("span",class_="date").text.strip()excerpt=item.find("p",class_="post-excerpt").text.strip()posts.append({"title":title,"link":f"https://example.com{link}","author":author,"date":date,"excerpt":excerpt})returnpostsasyncdefcrawl_page(session,page):"""爬取单个页面"""url=f"{BASE_URL}?page={page}"html=awaitsafe_fetch(session,url)returnparse_html(html)asyncdefmain(total_pages=100):start=time.time()headers={"User-Agent":UserAgent().random}asyncwithaiohttp.ClientSession(headers=headers)assession:# 创建所有任务tasks=[crawl_page(session,page)forpageinrange(1,total_pages+1)]# 等待所有任务完成results=awaitasyncio.gather(*tasks)# 合并所有结果all_posts=[]forresultinresults:all_posts.extend(result)# 保存数据df=pd.DataFrame(all_posts)df.to_csv("blog_posts_async.csv",index=False,encoding="utf-8-sig")print(f"爬取完成,共获取{len(all_posts)}篇文章")print(f"总耗时:{time.time()-start:.2f}秒")if__name__=="__main__":asyncio.run(main(total_pages=100))

4.3 关键技术点解析

  1. 信号量控制并发:这是异步爬虫最重要的一点。如果不控制并发数,一下子发送几千个请求,不仅会被网站封IP,还会把自己的网络搞瘫痪。建议根据网站的反爬强度,把并发数设置在50-200之间。
  2. 异常处理与重试:异步爬虫的请求量很大,网络波动是常有的事。一定要加重试机制,使用指数退避算法,避免频繁重试加重服务器负担。
  3. 批量写入数据:不要每爬一条数据就写一次数据库,这样会严重影响性能。应该把所有数据收集起来,最后批量写入。

五、性能优化终极技巧

掌握了上面的基础用法,你的爬虫速度已经能提升几十倍了。但如果想要做到极致,还需要做以下优化:

5.1 优化连接池

aiohttp默认的连接池大小是100,我们可以把它调大,提高并发能力:

connector=aiohttp.TCPConnector(limit=500,# 最大连接数ttl_dns_cache=300,# DNS缓存时间use_dns_cache=True,resolver=aiohttp.AsyncResolver()# 使用异步DNS解析)asyncwithaiohttp.ClientSession(connector=connector,headers=headers)assession:# ...

5.2 禁用不必要的重定向和SSL验证

asyncwithsession.get(url,allow_redirects=False,# 禁用重定向verify_ssl=False,# 禁用SSL验证timeout=10)asresponse:

5.3 使用异步数据库

如果你的数据量很大,同步的数据库写入会成为性能瓶颈。建议使用异步数据库驱动,比如aiomysqlaiopgmotor(MongoDB)。

5.4 分块爬取

如果要爬取的页面超过10000个,不要一次性创建所有任务,这样会占用大量内存。应该分块爬取,每次爬取1000个页面,完成后再爬取下一块。


六、踩坑实录:90%的人都会遇到的问题

  1. RuntimeError: Event loop is closed:这是Windows系统上aiohttp的一个常见bug。解决方案是在代码开头加上:
importasyncioimportsysifsys.platform=="win32":asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
  1. 并发太高被封IP:不要盲目追求高并发。如果发现被封IP,降低并发数,增加随机延迟,使用代理IP池。
  2. aiohttp编码问题:永远不要用默认编码,一定要手动设置response.encoding = response.apparent_encoding
  3. 在异步函数中调用同步函数:如果在异步函数中调用了耗时的同步函数(比如time.sleep()、同步的数据库操作),会阻塞整个事件循环,导致所有协程都变慢。一定要用对应的异步版本。
  4. 内存泄漏:如果爬虫要长时间运行,一定要注意内存泄漏问题。及时清理不需要的变量,定期重启事件循环。

七、总结

异步爬虫是提升爬虫速度最有效的方法,没有之一。不需要换电脑,不需要加服务器,只要把代码改成异步,就能轻松实现几十上百倍的速度提升。

但是也要注意,并发不是越高越好。过高的并发不仅会被网站封IP,还会给服务器造成过大的压力。我们应该合理控制并发数,做一个文明的爬虫。

最后再次强调:请遵守法律法规,不要爬取敏感信息,不要用于商业用途,不要给网站服务器造成过大的负担。技术本身没有对错,关键在于如何使用。


👉 点击我的头像进入主页,关注专栏第一时间收到更新提醒,有问题评论区交流,看到都会回。

http://www.jsqmd.com/news/678224/

相关文章:

  • 别再瞎买显卡了!用PyTorch的thop库,5分钟算出你的模型到底需要多少显存和算力
  • 三分钟解决Windows热键冲突的终极侦探工具
  • 抖音直播间数据抓取完整指南:2025最新WebSocket协议逆向工程实战
  • 手机号查QQ号:你的智能助手如何帮你省心省力
  • 农产品价格行情数据接口API介绍
  • 新手工程师必看:搞定EMI传导干扰,从理解差模和共模开始(附实战案例)
  • MCNP新手避坑指南:手把手教你写对第一个SDEF源卡(附137铯源完整示例)
  • 智能数据标注实战指南:10倍效率提升的自动化解决方案
  • 保姆级教程:用Superset+MySQL搞定Kaggle牛油果销售数据可视化(附完整数据集)
  • 告别混乱标注!用Python脚本一键清理Labelme JSON文件中的多余标签编号
  • 几何光学仿真终极指南:5步快速掌握光学系统设计
  • Prism方差分析结果看不懂?手把手教你解读F值、P值与方差分析表
  • 2026年电动工业提升门定做厂家实力排行一览:成都防火卷帘门工厂,抗风卷帘门,欧式卷帘门定制厂家,排行一览! - 优质品牌商家
  • M62429L驱动实战:从时序解析到嵌入式C代码实现
  • 别再只用梯度下降了:ISTA算法如何解决病态方程与特征选择难题?
  • xrdp深度解析:构建高性能Linux远程桌面服务器的技术实现与优化指南
  • PCB设计时序不求人:手把手教你用Allegro动态延迟(Dly)功能搞定50mm±0.5mm精确等长
  • FPGA与ASIC设计优化及移植策略详解
  • 六角螺栓有哪些类型?性能等级、应用场景与采购选型解析|2026上海紧固件专业展
  • 别再让符号定时偏差搞砸你的OFDM仿真!手把手教你用MATLAB实现STO估计(附完整代码)
  • Linux学习
  • STM32WL LoRaWAN节点开发避坑指南:从AT_Slave到End_Node工程实战解析
  • 单表查询习题
  • 别再只懂TF-IDF了!手把手教你用Python实现BM25算法(附完整代码与调参技巧)
  • 2026上海办公区域保洁推荐榜:上海日常保洁,企业保洁服务,会展保洁服务,公司保洁服务,公司开荒保洁,优选指南! - 优质品牌商家
  • 如何快速掌握RPFM:从新手到模组专家的完整指南
  • 前端构建速度优化方法
  • MSVBVM50.DLL文件丢失怎么办? 免费下载方法分享
  • 2026年3月水泥管供应商推荐,冷拔丝/混凝土涵管/水泥管/水泥制品/环保化粪池/成品检查井,水泥管品牌推荐 - 品牌推荐师
  • 工行科技岗面试官亲述:我们如何在2对1面试中,用‘限定问题’帮你理清思路?