异步爬虫 aiohttp 实战——比多线程快10倍的爬虫方案

异步爬虫 aiohttp 实战——比多线程快10倍的爬虫方案 多线程爬虫虽然比单线程快几倍但线程本身有开销而且受限于 Python 的 GIL 锁。异步爬虫是更高阶的方案——单线程处理上千个并发请求比多线程快 5~10 倍。一、异步 vs 多线程 vs 单线程对比单线程多线程(5线程)异步(aiohttp)爬取10页(250条)~10秒~2.8秒~1秒爬取100页(2500条)~100秒~30秒~5秒CPU占用低中低代码复杂度简单中等稍高适合场景小规模中等规模大规模二、异步爬虫原理普通爬虫发请求后要等待服务器响应这段时间 CPU 是空闲的。异步爬虫在等待的过程中切换去发送其他请求等响应回来了再回来处理。普通请求发送 → 等待 → 处理 → 发送 → 等待 → 处理 ... 异步请求发送 → 发送 → 发送 → 处理 → 处理 → 处理 ... 同时发多个请求谁回来了处理谁三、环境准备pipinstallaiohttp aiodns四、基本用法importasyncioimportaiohttpasyncdeffetch(session,url):异步请求单页asyncwithsession.get(url)asresponse:returnawaitresponse.text()asyncdefmain():asyncwithaiohttp.ClientSession()assession:htmlawaitfetch(session,https://example.com)print(len(html))# 运行asyncio.run(main())五、实战异步爬取豆瓣 Top250importasyncioimportaiohttpfromlxmlimportetree HEADERS{User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36}asyncdeffetch(session,url):异步请求try:asyncwithsession.get(url,headersHEADERS,timeout10)asresp:returnawaitresp.text()exceptExceptionase:print(f请求失败:{url[:30]}...{e})returnNoneasyncdefscrape_page(session,page):异步爬取单页urlfhttps://movie.douban.com/top250?start{page*25}htmlawaitfetch(session,url)ifnothtml:return[]treeetree.HTML(html)itemstree.xpath(//ol[classgrid_view]/li)movies[]foriteminitems:rankitem.xpath(.//em/text())[0]titleitem.xpath(.//span[classtitle]/text())[0]ratingitem.xpath(.//span[classrating_num]/text())[0]people_textitem.xpath(.//span[contains(text(), 人评价)]/text())peoplepeople_text[0].strip()ifpeople_textelse0人评价movies.append({rank:int(rank),title:title,rating:float(rating),people:people})print(f第{page1}页完成{len(movies)}条)returnmoviesasyncdefcrawl_all():异步爬取全部10页asyncwithaiohttp.ClientSession()assession:# 创建10个爬取任务tasks[scrape_page(session,page)forpageinrange(10)]# 并发执行所有任务resultsawaitasyncio.gather(*tasks)# 合并结果并排序all_movies[]formoviesinresults:all_movies.extend(movies)all_movies.sort(keylambdax:x[rank])returnall_movies运行importtime starttime.time()moviesasyncio.run(crawl_all())print(f\n共{len(movies)}部电影耗时{time.time()-start:.1f}秒)输出效果第1页完成25条 第3页完成25条 第5页完成25条 第2页完成25条 ... 共250部电影耗时1.2秒六、异步爬虫 CSV 保存爬取后自动保存到文件importcsvasyncdefcrawl_and_save():moviesawaitcrawl_all()withopen(douban_top250.csv,w,newline,encodingutf-8-sig)asf:writercsv.DictWriter(f,fieldnames[rank,title,rating,people])writer.writeheader()writer.writerows(movies)print(f已保存到 douban_top250.csv共{len(movies)}条)asyncio.run(crawl_and_save())七、性能对比测试方式250条耗时2500条耗时代码量单线程~10秒~100秒40行多线程(5线程)~2.8秒~30秒60行异步 aiohttp~1.2秒~5秒70行数据量越大异步优势越明显。八、踩坑提醒1. 异步函数中不能使用 requests# 错误requests 是同步库会阻塞事件循环asyncdeffetch(url):returnrequests.get(url)# 正确使用 aiohttpasyncdeffetch(session,url):asyncwithsession.get(url)asresp:returnawaitresp.text()2. 控制并发数同时发太多请求会被封用asyncio.Semaphore限制semasyncio.Semaphore(5)# 最多5个并发asyncdeffetch(session,url):asyncwithsem:# 排队等待asyncwithsession.get(url)asresp:returnawaitresp.text()3. 超时设置异步请求一定要设置超时避免某个请求卡死timeoutaiohttp.ClientTimeout(total10)asyncwithsession.get(url,timeouttimeout)asresp:...4. 异步不能直接跟 matplotlib 混用matplotlib 是同步的先爬取完数据再同步画图。总结异步爬虫是处理大规模数据采集的最佳方案。虽然学习曲线比 requests 陡一些但带来的性能提升非常值得。建议爬取 100 页以下用多线程100 页以上用异步。爬虫系列完结从 requests 入门 → XPath → BS4 → Selenium → 反爬 → 多线程 → 异步一套完整的爬虫技能链已经覆盖。后续将分享更多实战项目和 Java 开发相关文章。如果对你有帮助欢迎点赞、评论、关注【张老师技术栈】持续分享 Java/Python/爬虫 实战干货。