Python异步编程实战:让代码跑得比AI还快

0 阅读4分钟

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】专注数据分析 + 职场真相 + 投资洞察