Python协程爬虫:高并发爬取
告别龟速采集:Python协程爬虫实战与高并发避坑指南
写爬虫时,面对几万个URL,用传统的同步库挨个请求,看着控制台慢吞吞地滚动,是不是特别抓狂?有人会说上多线程或者多进程。多线程确实能提速,但线程一多,内存占用飙升,上下文切换的开销也大。这时候,协程(Coroutine) 就该登场了。
打个比方,多线程像是雇了100个人去餐厅吃饭,每个人点完菜就干等着;而协程就像你一个人同时盯着5个灶台煮面,水开了就捞面,没开就去忙别的。在单线程内通过任务切换,协程能把I/O等待的时间利用到极致,轻松实现高并发。
在Python里玩转协程爬虫,最经典的搭配是内置的asyncio配合第三方的aiohttp。咱们不扯底层源码,直接聊聊怎么落地。
构建异步请求核心
抛弃同步的requests,换上aiohttp。你需要定义一个异步函数,用async def声明,内部使用await等待网络响应。这里的核心逻辑是让出控制权,当网络I/O等待时,程序不会傻等,而是去事件循环里执行其他挂起的任务。
import aiohttp
async def fetch_url(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
任务打包与并发控制
把成百上千个请求封装成Task,扔给asyncio.gather()统一调度。重点来了,千万别一次性把几万个任务全塞进去。这不仅会撑爆本地内存,还可能瞬间触发目标网站的反爬封禁机制。
这时候需要引入信号量(Semaphore) 来控制并发,这是高并发爬虫的核心技巧。通过设定一个阈值,就像给并发上了个限流阀,既保证了爬取速度,又保护了本地资源。
import asyncio
async def main():
urls = ["http://example.com"] * 1000
# 限制同时进行的请求数最多为100
semaphore = asyncio.Semaphore(100)
async def bounded_fetch(url):
async with semaphore:
return await fetch_url(url)
tasks = [bounded_fetch(url) for url in urls]
await asyncio.gather(*tasks)
避开协程实战中的“暗坑”
代码跑通只是第一步,高并发场景下还有几个极易踩坑的地方,稍不注意就会让效率大打折扣。
警惕“伪异步”拖死全局。在协程代码里,绝对不能混入time.sleep()或者同步的requests.get()。这些同步阻塞操作会直接卡死整个事件循环,让协程退化成单线程串行。必须严格使用asyncio.sleep()和异步专属库。
做好异常捕获机制。高并发下,网络波动、目标服务器超时是常态。如果某个请求报错没有捕获,可能会导致整个协程任务崩溃,前面的努力全白费。在异步函数内部务必加上try-except块,遇到异常记录日志后跳过,保证其他任务继续平稳运行。
注意Session的复用。在上面的基础代码中,每次请求都创建了一个ClientSession,这在高并发下非常消耗资源。正确的做法是在外部创建一个全局Session,传递给所有的异步请求函数,用完后统一关闭,这样能大幅提升连接效率。
认清场景,用对工具
协程爬虫并不是万能的,它专为I/O密集型任务(如网络请求、数据库读写)而生。如果你爬取数据后,需要进行极其复杂的CPU计算(如大规模图像识别、复杂正则匹配),协程的优势就体现不出来了,这种场景还是得老老实实交给多进程。
理清业务场景,用对并发模型,你的爬虫效率绝对能迎来质的飞跃。下次再遇到海量数据采集需求,不妨让协程帮你把速度拉满。


还没有评论,来说两句吧...