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

多线程爬虫虽然比单线程快几倍,但线程本身有开销,而且受限于 Python 的 GIL 锁。异步爬虫是更高阶的方案——单线程处理上千个并发请求,比多线程快 5~10 倍。

一、异步 vs 多线程 vs 单线程

对比单线程多线程(5线程)异步(aiohttp)
爬取10页(250条)~10秒~2.8秒~1秒
爬取100页(2500条)~100秒~30秒~5秒
CPU占用
代码复杂度简单中等稍高
适合场景小规模中等规模大规模

二、异步爬虫原理

普通爬虫发请求后要等待服务器响应,这段时间 CPU 是空闲的。异步爬虫在等待的过程中切换去发送其他请求,等响应回来了再回来处理。

普通请求:发送 → 等待 → 处理 → 发送 → 等待 → 处理 ...
异步请求:发送 → 发送 → 发送 → 处理 → 处理 → 处理 ...
           (同时发多个请求,谁回来了处理谁)

三、环境准备

pip install aiohttp aiodns

四、基本用法

import asyncio
import aiohttp

async def fetch(session, url):
    """异步请求单页"""
    async with session.get(url) as response:
        return await response.text()

async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, "https://example.com")
        print(len(html))

# 运行
asyncio.run(main())

五、实战:异步爬取豆瓣 Top250

import asyncio
import aiohttp
from lxml import etree

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}

async def fetch(session, url):
    """异步请求"""
    try:
        async with session.get(url, headers=HEADERS, timeout=10) as resp:
            return await resp.text()
    except Exception as e:
        print(f"请求失败: {url[:30]}... {e}")
        return None

async def scrape_page(session, page):
    """异步爬取单页"""
    url = f"https://movie.douban.com/top250?start={page * 25}"
    html = await fetch(session, url)
    if not html:
        return []

    tree = etree.HTML(html)
    items = tree.xpath('//ol[@class="grid_view"]/li')
    
    movies = []
    for item in items:
        rank = item.xpath('.//em/text()')[0]
        title = item.xpath('.//span[@class="title"]/text()')[0]
        rating = item.xpath('.//span[@class="rating_num"]/text()')[0]
        people_text = item.xpath('.//span[contains(text(), "人评价")]/text()')
        people = people_text[0].strip() if people_text else "0人评价"

        movies.append({
            "rank": int(rank),
            "title": title,
            "rating": float(rating),
            "people": people
        })
    
    print(f"第{page+1}页完成,{len(movies)}条")
    return movies

async def crawl_all():
    """异步爬取全部10页"""
    async with aiohttp.ClientSession() as session:
        # 创建10个爬取任务
        tasks = [scrape_page(session, page) for page in range(10)]
        
        # 并发执行所有任务
        results = await asyncio.gather(*tasks)
    
    # 合并结果并排序
    all_movies = []
    for movies in results:
        all_movies.extend(movies)
    all_movies.sort(key=lambda x: x["rank"])
    return all_movies

运行

import time
start = time.time()
movies = asyncio.run(crawl_all())
print(f"\n共{len(movies)}部电影,耗时{time.time()-start:.1f}秒")

输出效果:

第1页完成,25条
第3页完成,25条
第5页完成,25条
第2页完成,25条
...
共250部电影,耗时1.2秒

六、异步爬虫 + CSV 保存

爬取后自动保存到文件:

import csv

async def crawl_and_save():
    movies = await crawl_all()
    
    with open("douban_top250.csv", "w", newline="", encoding="utf-8-sig") as f:
        writer = csv.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 是同步库,会阻塞事件循环
async def fetch(url):
    return requests.get(url)

# 正确:使用 aiohttp
async def fetch(session, url):
    async with session.get(url) as resp:
        return await resp.text()

2. 控制并发数

同时发太多请求会被封,用 asyncio.Semaphore 限制:

sem = asyncio.Semaphore(5)  # 最多5个并发

async def fetch(session, url):
    async with sem:  # 排队等待
        async with session.get(url) as resp:
            return await resp.text()

3. 超时设置

异步请求一定要设置超时,避免某个请求卡死:

timeout = aiohttp.ClientTimeout(total=10)
async with session.get(url, timeout=timeout) as resp:
    ...

4. 异步不能直接跟 matplotlib 混用

matplotlib 是同步的,先爬取完数据,再同步画图。

总结

异步爬虫是处理大规模数据采集的最佳方案。虽然学习曲线比 requests 陡一些,但带来的性能提升非常值得。建议爬取 100 页以下用多线程,100 页以上用异步。

爬虫系列完结! 从 requests 入门 → XPath → BS4 → Selenium → 反爬 → 多线程 → 异步,一套完整的爬虫技能链已经覆盖。后续将分享更多实战项目和 Java 开发相关文章。


如果对你有帮助,欢迎点赞、评论、关注【张老师技术栈】,持续分享 Java/Python/爬虫 实战干货。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值