Python 异步编程入门:用 asyncio 让你的爬虫速度飞起

在网络爬虫开发中,很多人一开始用 requests 库写同步代码,遇到需要抓取几百个页面时就傻眼了——程序卡得像乌龟爬。为什么?因为同步 I/O 操作会阻塞整个线程,导致 CPU 大部分时间都在等待网络响应。

这时候,异步编程就成了救星。Python 内置的 asyncio 模块配合 aiohttp,能让你的爬虫并发处理几十甚至上百个请求,速度提升几倍到几十倍。今天我们就来一步步拆解如何用 asyncio 写一个高效的异步爬虫。

为什么需要异步?

传统同步爬虫的流程是:发送请求 → 等待响应 → 解析数据 → 下一个请求。这种“串行”方式在网络延迟高的时候效率极低。而异步编程的核心思想是“非阻塞”:当一个请求在等待服务器响应时,程序可以立即切换去处理其他请求,充分利用等待时间。

举个例子,假设你要爬取 100 个网页,每个请求平均耗时 1 秒。同步写法最少需要 100 秒,而合理使用异步并发后,可能只需 5-10 秒就能完成。

基础概念速览

  • 协程 (Coroutine):用 async def 定义的函数,可以被暂停和恢复。
  • 事件循环 (Event Loop):asyncio 的核心,负责调度所有协程的执行。
  • Task:把协程包装成任务,让它在事件循环中并发运行。
  • Await:遇到 await 关键字时,当前协程会暂停,交给事件循环去处理其他任务。

实战:异步爬虫完整示例

首先安装依赖:

pip install aiohttp aiofiles

下面是一个简单的异步爬虫模板,能并发抓取多个页面并保存为 Markdown 文件:

import asyncio
import aiohttp
from bs4 import BeautifulSoup
import aiofiles

async def fetch(url, session):
    async with session.get(url) as response:
        return await response.text()

async def save_to_file(filename, content):
    async with aiofiles.open(filename, 'w', encoding='utf-8') as f:
        await f.write(content)

async def crawl(url):
    async with aiohttp.ClientSession() as session:
        html = await fetch(url, session)
        soup = BeautifulSoup(html, 'html.parser')
        title = soup.title.string if soup.title else '无标题'

        # 简单处理正文(实际项目中可根据需要提取更多内容)
        content = f'# {title}\n\n{html[:500]}...'

        filename = f'{title.replace(" ", "_")}.md'
        await save_to_file(filename, content)
        print(f'已保存:{filename}')

async def main():
    urls = [
        'https://example.com/page1',
        'https://example.com/page2',
        'https://example.com/page3',
        # ... 添加更多 URL
    ]
    tasks = [crawl(url) for url in urls]
    await asyncio.gather(*tasks)

if __name__ == '__main__':
    asyncio.run(main())

进阶技巧

  1. 并发控制:使用 asyncio.Semaphore 限制同时进行的请求数量,避免被目标网站封禁。
    
    sem = asyncio.Semaphore(10)  # 最多同时 10 个请求

async def fetch(url, session):
async with sem:
async with session.get(url) as resp:
return await resp.text()



2. **超时与重试**:为每个请求设置超时,并实现简单的重试机制。
3. **代理与 Headers**:在 ClientSession 中统一设置 User-Agent 和代理池,模拟不同用户行为。
4. **结合其他库**:可以和 Playwright 的异步版本配合,实现更复杂的 JavaScript 渲染页面爬取。

### 注意事项

- 遵守 robots.txt 和网站服务条款,合理控制爬取频率。
- 异步代码调试相对复杂,建议使用 logging 模块记录详细日志。
- Python 3.7+ 推荐使用 asyncio.run() 启动事件循环,更简洁安全。

掌握了 asyncio 之后,你会发现很多 I/O 密集型任务(爬虫、API 调用、文件批量处理)都能获得质的飞跃。建议大家从一个小项目开始练习,比如写一个异步下载图片或批量翻译文本的工具,很快就能感受到异步编程的魅力。

异步编程的学习曲线虽然稍陡,但一旦入门,你会爱上这种“并发思维”。欢迎在评论区分享你用 asyncio 做过的有趣项目,或者遇到的坑,我们一起交流进步!

(本文约 680 字,欢迎点赞收藏,转发给需要的朋友)