当前位置:  首页>> 技术小册>> Python3网络爬虫开发实战(上)

6.3 aiohttp 异步爬取实战

在网络爬虫的开发过程中,性能优化始终是一个重要的议题。随着网络数据的日益庞大和复杂,传统的同步请求方式在处理大规模数据抓取时显得力不从心。aiohttp 作为 Python 中的一个强大异步 HTTP 客户端/服务端框架,为构建高效的网络爬虫提供了有力的支持。本章将深入介绍如何使用 aiohttp 进行异步爬取实战,包括基础概念、安装配置、请求发送、数据解析以及错误处理等关键步骤。

6.3.1 aiohttp 简介

aiohttp 是一个基于 asyncio 的异步 HTTP 客户端/服务器框架,支持客户端和服务器两端的异步操作。它提供了丰富的功能和良好的性能,非常适合用于构建需要处理大量并发请求的网络爬虫。与传统同步 HTTP 客户端(如 requests)相比,aiohttp 能够利用 Python 的 asyncio 库实现非阻塞的 I/O 操作,从而显著提高爬取效率。

6.3.2 安装 aiohttp

在开始使用 aiohttp 之前,首先需要确保它已经安装在你的 Python 环境中。可以通过 pip 命令轻松安装:

  1. pip install aiohttp

此外,如果你打算使用异步的 HTTP 服务器,可能还需要安装 aiohttp 的服务端组件,但这在大多数爬虫项目中不是必需的。

6.3.3 异步爬取基础

异步爬取的核心在于利用 asyncio 库来并发执行多个 HTTP 请求。aiohttp 提供了 ClientSession 类,用于管理多个请求和响应的会话。以下是一个基本的异步爬取示例:

  1. import aiohttp
  2. import asyncio
  3. async def fetch(session, url):
  4. async with session.get(url) as response:
  5. return await response.text()
  6. async def main():
  7. async with aiohttp.ClientSession() as session:
  8. html1 = await fetch(session, 'http://example.com/page1')
  9. html2 = await fetch(session, 'http://example.com/page2')
  10. # 这里可以添加更多异步请求
  11. # 处理响应数据
  12. print(html1)
  13. print(html2)
  14. # Python 3.7+
  15. asyncio.run(main())

在上述代码中,fetch 函数是一个异步函数,它接收一个 aiohttp.ClientSession 实例和一个 URL,然后发送 GET 请求并返回响应的文本内容。main 函数则负责创建会话并并发地调用 fetch 函数来获取两个页面的内容。

6.3.4 并发控制

虽然 aiohttp 允许你并发地发送多个请求,但无限制地增加并发数可能会导致目标网站因压力过大而采取反爬措施,如限制 IP 访问频率、验证码验证等。因此,合理控制并发数是异步爬取中的一个重要策略。

aiohttp 并没有直接提供限制并发的内置机制,但你可以使用 asyncio.Semaphore 来控制同时进行的任务数:

  1. import aiohttp
  2. import asyncio
  3. async def fetch(session, url, semaphore):
  4. async with semaphore:
  5. async with session.get(url) as response:
  6. return await response.text()
  7. async def main():
  8. concurrency = 10 # 并发数
  9. semaphore = asyncio.Semaphore(concurrency)
  10. urls = ['http://example.com/page{}'.format(i) for i in range(1, 21)]
  11. tasks = []
  12. async with aiohttp.ClientSession() as session:
  13. for url in urls:
  14. task = asyncio.create_task(fetch(session, url, semaphore))
  15. tasks.append(task)
  16. # 等待所有任务完成
  17. results = await asyncio.gather(*tasks)
  18. for result in results:
  19. print(result[:100]) # 示例:仅打印前100个字符
  20. asyncio.run(main())

在这个示例中,我们使用 asyncio.Semaphore 来限制同时进行的 HTTP 请求数量,从而避免对目标网站造成过大的压力。

6.3.5 数据解析

在获取到网页的 HTML 内容后,接下来需要进行数据解析以提取所需的信息。虽然 aiohttp 主要负责 HTTP 请求的发送和接收,但你可以结合其他库(如 BeautifulSouplxml 或正则表达式)来进行数据解析。

例如,使用 BeautifulSoup 解析 HTML 内容:

  1. from bs4 import BeautifulSoup
  2. async def parse_html(html):
  3. soup = BeautifulSoup(html, 'html.parser')
  4. # 假设我们要提取所有标题
  5. titles = [title.get_text() for title in soup.find_all('h1')]
  6. return titles
  7. # 在 main 函数中调用 parse_html
  8. # ...
  9. titles = await asyncio.gather(*[parse_html(html) for html in results])
  10. for title_list in titles:
  11. for title in title_list:
  12. print(title)

6.3.6 错误处理

在网络爬虫中,错误处理是一个不可或缺的环节。由于网络状况的不稳定性和目标网站结构的频繁变动,请求可能会失败或返回非预期的结果。因此,在编写爬虫时,应该添加适当的错误处理逻辑来增强代码的健壮性。

aiohttp 提供了丰富的异常类型,你可以通过捕获这些异常来处理各种可能的错误情况:

  1. async def fetch_with_error_handling(session, url):
  2. try:
  3. async with session.get(url) as response:
  4. response.raise_for_status() # 抛出异常如果响应状态码不是 200-299
  5. return await response.text()
  6. except aiohttp.ClientError as e:
  7. print(f"Error for {url}: {e}")
  8. return None
  9. # 在 main 函数中调用 fetch_with_error_handling
  10. # ...

6.3.7 总结

通过本章的学习,你应该已经掌握了如何使用 aiohttp 进行异步网络爬取的基本技能。从安装配置、请求发送、数据解析到错误处理,每一步都是实现高效爬取的关键。在实际项目中,你可能还需要根据目标网站的具体情况和爬取需求,对爬虫进行进一步的优化和调整。记住,合理控制并发数、妥善处理异常、遵守网站的爬虫协议,是构建可靠、高效网络爬虫的重要原则。


该分类下的相关小册推荐: