异步爬虫实践:使用Aiohttp加速抓取多个星座运势 原创

2025-10-09 16:59

一、同步与异步:为何效率天差地别?

在开始代码之前,理解其背后的理念至关重要。

Python的 asyncio 库提供了构建异步程序的底层基础设施。而 Aiohttp 则是基于 asyncio 的,专门用于处理HTTP请求的库,它使得编写异步爬虫变得异常简单和高效。

二、项目实战:构建异步星座运势爬虫

我们的目标是并发地抓取一个假设的星座网站上的12个星座运势。

1. 环境准备

首先,确保安装了必要的库。aiohttp 是核心,asyncio 是Python的内置库。我们还会使用 beautifulsoup4 来解析HTML。

2. 目标分析

假设我们的目标URL结构为:https://example-astrology.com/aquarius.html。每个星座一个页面,我们可以构建一个URL列表。

3. 代码实现

以下是完整的、带有详细注释的代码实现。

import aiohttp
import asyncio
from bs4 import BeautifulSoup
import time
import logging

# 配置日志,方便观察异步执行过程
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# 目标星座列表和基础URL(此为示例,请替换为真实可用的URL)
ZODIAC_SIGNS = [
    'aries', 'taurus', 'gemini', 'cancer', 'leo', 'virgo',
    'libra', 'scorpio', 'sagittarius', 'capricorn', 'aquarius', 'pisces'
]
BASE_URL = "https://example-astrology.com/{sign}.html"

# 定义异步函数:获取并解析单个星座的页面
async def fetch_zodiac_forecast(session, sign):
    """
    使用传入的aiohttp session异步抓取单个星座的运势。
    
    Args:
        session: aiohttp.ClientSession 实例,用于发起请求。
        sign: 星座名称字符串。
    
    Returns:
        dict: 包含星座名称和运势文本的字典。
    """
    url = BASE_URL.format(sign=sign)
    try:
        logger.info(f"正在发起请求: {sign}")
        # 发起异步GET请求,with语句确保响应被正确关闭
        async with session.get(url) as response:
            # 确保请求成功,否则抛出异常
            response.raise_for_status()
            # 异步读取响应内容
            html = await response.text()
        
        # 使用BeautifulSoup同步解析HTML(注意:这里是同步操作)
        # 由于解析通常很快,对整体性能影响不大。如果解析极复杂,可考虑异步方式。
        soup = BeautifulSoup(html, 'html.parser')
        
        # 假设运势文本在一个 <div class='forecast'> 标签内
        # 请根据实际网页结构修改此选择器
        forecast_element = soup.find('div', class_='forecast')
        forecast_text = forecast_element.get_text(strip=True) if forecast_element else "运势未找到"
        
        logger.info(f"成功抓取: {sign}")
        return {
            'sign': sign,
            'forecast': forecast_text
        }
    
    except aiohttp.ClientError as e:
        logger.error(f"请求星座 {sign} 时发生错误: {e}")
        return {
            'sign': sign,
            'forecast': f"抓取失败: {str(e)}"
        }
    except Exception as e:
        logger.error(f"解析星座 {sign} 时发生未知错误: {e}")
        return {
            'sign': sign,
            'forecast': f"解析失败: {str(e)}"
        }

# 定义主异步函数:创建任务并并发执行
async def main():
    """
    主函数,创建会话和所有抓取任务,并并发执行。
    """
    # 创建一个TCPConnector,可以限制总连接数和每台主机的连接数,避免对服务器造成过大压力
    connector = aiohttp.TCPConnector(limit=10) # 限制同时连接数为10
    
    # 创建一个ClientSession,所有请求共享这个session的连接池和其他资源
    async with aiohttp.ClientSession(connector=connector) as session:
        # 为每个星座创建一个异步任务(Task)
        tasks = []
        for sign in ZODIAC_SIGNS:
            # 创建task对象,但此时尚未执行
            task = asyncio.create_task(fetch_zodiac_forecast(session, sign))
            tasks.append(task)
        
        logger.info(f"已创建 {len(tasks)} 个异步任务,开始并发执行...")
        
        # 使用 asyncio.gather 并发运行所有任务,并等待它们全部完成
        # gather会返回一个结果列表,顺序与tasks列表一致
        results = await asyncio.gather(*tasks)
        
        # 所有任务完成后,打印结果
        logger.info("所有任务已完成!以下是抓取结果:")
        print("\\n" + "="*50)
        for result in results:
            print(f"【{result['sign'].upper()}】")
            print(f"运势:{result['forecast']}")
            print("-" * 30)

# 程序入口点
if __name__ == "__main__":
    start_time = time.time() # 记录开始时间
    
    # 获取或创建事件循环并运行主程序
    # asyncio.run() 是Python 3.7+的推荐方式,它负责管理事件循环
    asyncio.run(main())
    
    end_time = time.time() # 记录结束时间
    elapsed_time = end_time - start_time
    print(f"\\n>>> 总耗时: {elapsed_time:.2f} 秒 <<<")

三、代码核心解析与最佳实践

  1. async with aiohttp.ClientSession()

  1. asyncio.create_task()

  1. await asyncio.gather(*tasks)

  1. 错误处理:

  1. 性能对比:

四、总结与拓展

通过本次实践,我们成功构建了一个高效、健壮的异步星座运势爬虫。Aiohttp asyncio 的结合,将I/O密集型任务的性能提升了一个数量级。

在实际应用中,你还可以考虑以下拓展方向:



阅读 22 / 评论 0

 相关视频教程更多课程