Python异步编程实战:让代码跑得比AI还快
大家好,我是船长。
上周有个读者问我:"船长,我爬虫跑10000个URL要3小时,有没有办法加速?"
我看了下他的代码——用的是同步requests。10000个URL,每个假设耗时1秒,再怎么优化也很难突破这个天花板。
换成异步之后,同样的任务,8分钟跑完。
这就是今天要讲的东西:Python异步编程。
一、为什么异步能快这么多?
先理解一个概念:同步 vs 异步。
同步代码,就像排队买奶茶。你站在柜台前,等服务员做好一杯,下一个人才能点单。10000个人,就要等10000次。
异步代码,就像自助点单机。你点完单就去玩手机,叫你了再来拿。10000个人同时点单,最后一起来拿。
关键点在于:等待IO的时候,CPU是空闲的。网络请求、文件读写、数据库查询——这些操作99%的时间都在等。
异步编程就是让你在等待的时候"顺便"干别的事。
二、asyncio基础:Hello World
先看最简单例子:
import asyncio
async def say_hello():
print("Hello")
await asyncio.sleep(1) # 模拟IO等待
print("World")
# 运行
asyncio.run(say_hello())
几点说明:
-
async def定义异步函数 -
await暂停当前协程,让出CPU -
asyncio.run()入口函数
三、实战场景1:并发HTTP请求
这是最常见的需求。安装依赖:
pip install aiohttp
完整代码:
import asyncio
import aiohttp
import time
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main(urls):
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
results = await asyncio.gather(*tasks)
return results
# 测试
urls = [f"https://httpbin.org/delay/1" for _ in range(100)]
start = time.time()
asyncio.run(main(urls))
print(f"100个请求耗时: {time.time() - start:.2f}秒")
输出:100个请求耗时: 1.23秒
如果是同步requests,同样的代码要100秒。提升80倍。
四、实战场景2:批量文件读写
import asyncio
import aiofiles
async def read_file(path):
async with aiofiles.open(path, mode='r') as f:
return await f.read()
async def process_files(file_paths):
tasks = [read_file(path) for path in file_paths]
contents = await asyncio.gather(*tasks)
return contents
# 使用
import time
file_list = [f"data_{i}.txt" for i in range(1000)]
start = time.time()
contents = asyncio.run(process_files(file_list))
print(f"读取1000个文件耗时: {time.time() - start:.2f}秒")
aiofiles让文件IO也能异步。
五、实战场景3:数据库批量插入
import asyncio
import asyncpg
async def batch_insert(records):
conn = await asyncpg.connect(
host='localhost',
port=5432,
user='user',
password='password',
database='dbname'
)
# 批量插入,比逐条插入快10倍+
await conn.executemany(
"INSERT INTO users(id, name) VALUES($1, $2)",
records
)
await conn.close()
# 使用
records = [(i, f"user_{i}") for i in range(10000)]
asyncio.run(batch_insert(records))
asyncpg是PostgreSQL的异步驱动,比同步psycopg2快很多。
六、实战场景4:异步爬虫(完整示例)
import asyncio
import aiohttp
import aiofiles
from bs4 import BeautifulSoup
async def crawl_page(session, url, semaphore):
async with semaphore: # 限制并发数,避免被封
try:
async with session.get(url) as response:
html = await response.text()
soup = BeautifulSoup(html, 'html.parser')
title = soup.find('title').text
# 异步写入文件
async with aiofiles.open(f"output/{url.split('/')[-1]}.txt", 'w') as f:
await f.write(title)
return title
except Exception as e:
print(f"Error: {url} - {e}")
return None
async def main(start_url, max_pages=100):
# 1. 先获取所有链接
async with aiohttp.ClientSession() as session:
# 省略爬取链接的逻辑
urls = [f"https://example.com/page/{i}" for i in range(max_pages)]
# 2. 并发抓取(限制同时50个请求)
semaphore = asyncio.Semaphore(50)
async with aiohttp.ClientSession() as session:
tasks = [crawl_page(session, url, semaphore) for url in urls]
results = await asyncio.gather(*tasks)
return results
# 运行
asyncio.run(main("https://example.com"))
几个要点:
-
Semaphore限制并发数,太高会被封IP -
异常处理必须有,网络请求随时会失败
-
gather收集所有结果
七、注意事项
1. 不要混用同步和异步
requests是同步库,不能用在async函数里。要用aiohttp。
2. 异步不等于多线程
asyncio是单线程,只是切换执行权。如果CPU密集型任务(如加密、压缩),要用multiprocessing。
3. 调试比同步代码难
print可能不会按顺序输出。用logging代替。
总结
异步编程的核心场景:
-
网络请求(爬虫、API调用)
-
文件IO(批量读写)
-
数据库操作(批量插入查询)
-
消息队列消费
提升效果:IO密集型任务,10-100倍性能提升。
代码复杂度:比同步稍高,但值得。
完整代码已上传到GitHub,需要的同学公众号后台回复"异步"获取链接。
有问题欢迎评论区聊聊。
【船长Talk】专注数据分析 + 职场真相 + 投资洞察