aiohttp的详细使用和异步爬虫实战

文章目录

    • 1. 基本概念
    • 2. 安装 aiohttp
    • 3. 一个简单的异步爬虫
    • 4. 示例代码及解析
    • 5. 并发控制
    • 6. 错误处理
    • 7. 数据解析与存储
    • 8. 高级用法
      • 8.1 限速
      • 8.2 处理 JavaScript 渲染的页面
      • 8.3 异步文件写入
      • 8.4 POST 请求
      • 8.5 使用代理
      • 8.6 超时设置
    • 9. 总结

aiohttp 是一个基于 asyncio 的异步 HTTP 客户端/服务器框架,非常适合用于编写高效的异步爬虫。下面我们将详细讲解如何使用 aiohttp 编写异步爬虫。

1. 基本概念

异步编程:异步编程是一种编程范式,允许程序在等待某些操作(如 I/O 操作)完成时,继续执行其他任务。与同步编程不同,异步编程不会阻塞程序的执行,从而提高了程序的效率和响应速度。

协程(Coroutine):Python 中通过 async def 定义的函数称为协程,它们需要在事件循环中运行,并且可以暂停和恢复执行。

任务(Task):任务是对协程的封装,用于调度和执行协程。asyncio.create_task() 用于创建任务。

事件循环:这是异步程序的核心,负责管理所有异步任务的调度和执行。

并发与并行:并发是指同时处理多个任务,而并行是指同时执行多个任务。异步编程通常用于实现并发。

aiohttp :是一个基于 asyncio 的异步 HTTP 客户端/服务器框架,具有以下优点:

  • 高效:利用异步 I/O,能够处理大量并发请求。
  • 易用:API 设计简洁,易于上手。
  • 功能强大:支持 WebSocket、Session 管理、代理等功能。

一个基本的 aiohttp 异步爬虫通常包括以下几个部分:

  • 异步函数:用于发送 HTTP 请求并处理响应。
  • 事件循环:用于调度异步任务。

2. 安装 aiohttp

首先,确保你已经安装了 aiohttp 库。如果没有安装,可以使用以下命令进行安装:pip install aiohttp

3. 一个简单的异步爬虫

以下是一个使用 aiohttp 进行简单网页抓取的例子:

import aiohttp
import asyncio

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

async def main():
    urls = ['http://example.com', 'http://example.org', 'http://example.net']
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        pages = await asyncio.gather(*tasks)
        print(pages)

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

这段代码展示了如何定义一个异步函数 fetch 来获取网页内容,并使用 ClientSession 创建一个会话来发起多个请求。asyncio.gather 允许同时并发地运行多个协程。

4. 示例代码及解析

下面是另一个一个简单的 aiohttp 异步爬虫示例,用于抓取多个网页的内容。

import aiohttp
import asyncio

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

if __name__ == "__main__":
    urls = [
        'https://www.example.com',
        'https://www.example.org',
        'https://www.example.net',
    ]
    
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(main(urls))
    
    for result in results:
        print(result[:200])  # 打印每个页面的前200个字符

fetch 函数:这是一个异步函数,用于发送 HTTP GET 请求并返回响应的文本内容。async with session.get(url) 用于发送请求,await response.text() 用于获取响应的文本内容。

main 函数:这是主函数,负责创建 aiohttp.ClientSession 对象,并调度多个 fetch 任务。asyncio.gather 用于并发执行多个异步任务,并等待它们全部完成。

事件循环:asyncio.get_event_loop() 获取当前的事件循环,loop.run_until_complete(main(urls)) 用于运行 main 函数直到完成。

5. 并发控制

在实际应用中,可能需要控制并发请求的数量,以避免对服务器造成过大压力。可以使用 asyncio.Semaphore 来实现并发控制。

import aiohttp
import asyncio

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

async def main(urls, concurrency=5):
    semaphore = asyncio.Semaphore(concurrency)
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url, semaphore) for url in urls]
        results = await asyncio.gather(*tasks)
        return results

if __name__ == "__main__":
    urls = [
        'https://www.example.com',
        'https://www.example.org',
        'https://www.example.net',
        # 更多URL...
    ]
    
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(main(urls, concurrency=5))
    
    for result in results:
        print(result[:200])  # 打印每个页面的前200个字符

6. 错误处理

在实际应用中,网络请求可能会失败,因此需要添加错误处理机制。例如,可以捕获 aiohttp.ClientError 处理连接错误等。

import aiohttp
import asyncio

async def fetch(session, url, semaphore):
    async with semaphore:
        try:
            async with session.get(url) as response:
                response.raise_for_status()  # 如果响应状态码不是200,抛出异常
                return await response.text()
        except aiohttp.ClientError as e:
            print(f"Request failed for {url}: {e}")
            return None

async def main(urls, concurrency=5):
    semaphore = asyncio.Semaphore(concurrency)
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url, semaphore) for url in urls]
        results = await asyncio.gather(*tasks)
        return results

if __name__ == "__main__":
    urls = [
        'https://www.example.com',
        'https://www.example.org',
        'https://www.example.net',
        # 更多URL...
    ]
    
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(main(urls, concurrency=5))
    
    for result in results:
        if result:
            print(result[:200])  # 打印每个页面的前200个字符

7. 数据解析与存储

使用 BeautifulSoup 解析 HTML,并将数据存储到文件或数据库中。

from bs4 import BeautifulSoup
import aiohttp
import asyncio

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

async def parse(html):
    soup = BeautifulSoup(html, 'html.parser')
    return soup.title.string

async def main(urls, concurrency=5):
    semaphore = asyncio.Semaphore(concurrency)
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url, semaphore) for url in urls]
        htmls = await asyncio.gather(*tasks)
        titles = [parse(html) for html in htmls]
        return titles

if __name__ == "__main__":
    urls = [
        'https://www.example.com',
        'https://www.example.org',
        'https://www.example.net',
    ]
    
    titles = asyncio.run(main(urls, concurrency=5))
    for title in titles:
        print(title)

8. 高级用法

8.1 限速

为了避免对目标网站造成过大的负担,你可能希望限制请求的速度。可以通过 asyncio.sleep() 或者第三方库如 aiothrottle 实现。

import aiohttp
import asyncio

async def fetch(session, url, semaphore):
    async with semaphore:
        async with session.get(url) as response:
            await asyncio.sleep(1)  # 限速
            return await response.text()

async def main(urls, concurrency=5):
    semaphore = asyncio.Semaphore(concurrency)
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url, semaphore) for url in urls]
        results = await asyncio.gather(*tasks)
        return results

if __name__ == "__main__":
    urls = [
        'https://www.example.com',
        'https://www.example.org',
        'https://www.example.net',
    ]
    
    results = asyncio.run(main(urls, concurrency=5))
    for result in results:
        print(result[:200])

8.2 处理 JavaScript 渲染的页面

使用 pyppeteer 或 playwright 处理 JavaScript 渲染的页面。

from pyppeteer import launch
import asyncio

async def fetch(url):
    browser = await launch()
    page = await browser.newPage()
    await page.goto(url)
    content = await page.content()
    await browser.close()
    return content

async def main(urls):
    tasks = [fetch(url) for url in urls]
    results = await asyncio.gather(*tasks)
    return results

if __name__ == "__main__":
    urls = [
        'https://www.example.com',
        'https://www.example.org',
        'https://www.example.net',
    ]
    
    results = asyncio.run(main(urls))
    for result in results:
        print(result[:200])

8.3 异步文件写入

使用 aiofiles 进行异步文件写入。首先需要安装:pip install aiofiles

import aiohttp
import asyncio
import aiofiles

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

async def save(content, filename):
    async with aiofiles.open(filename, 'w') as f:
        await f.write(content)

async def main(urls, concurrency=5):
    semaphore = asyncio.Semaphore(concurrency)
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url, semaphore) for url in urls]
        results = await asyncio.gather(*tasks)
        for i, result in enumerate(results):
            await save(result, f'page_{i}.html')

if __name__ == "__main__":
    urls = [
        'https://www.example.com',
        'https://www.example.org',
        'https://www.example.net',
    ]
    
    asyncio.run(main(urls, concurrency=5))

8.4 POST 请求

除了 GET 请求外,aiohttp 还支持其他类型的请求,例如 POST 请求:

async def post_data(session, url, data):
    async with session.post(url, data=data) as response:
        return await response.json()

8.5 使用代理

为了防止被封 IP,有时需要使用代理。aiohttp 支持通过设置 proxy 参数来使用 HTTP/HTTPS 代理。

import aiohttp
import asyncio

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

async def main(urls, proxy):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url, proxy) for url in urls]
        results = await asyncio.gather(*tasks)
        return results

if __name__ == "__main__":
    urls = [
        'https://www.example.com',
        'https://www.example.org',
        'https://www.example.net',
    ]
    proxy = 'http://your-proxy-server:port'
    
    results = asyncio.run(main(urls, proxy))
    for result in results:
        print(result[:200])

8.6 超时设置

为了避免某些请求长时间挂起,可以为请求设置超时时间:

timeout = aiohttp.ClientTimeout(total=60)  # 设置总超时时间为60秒
async with aiohttp.ClientSession(timeout=timeout) as session:
    # 使用会话发送请求

最后,请确认 正确关闭 ClientSession 和释放资源。防止内存泄漏问题

9. 总结

aiohttp 是一个强大的异步 HTTP 客户端库,非常适合用于编写高效的异步爬虫。通过合理使用 asyncio 和 aiohttp,可以轻松实现并发请求、错误处理等功能,从而提升爬虫的性能和稳定性。

你可能感兴趣的:(2025年爬虫和逆向教程,爬虫,python,aiohttp,asyncio,异步爬虫)