目录

第九课异步爬虫进阶aiohttp与多线程的技术博客

第九课:异步爬虫进阶:aiohttp与多线程的技术博客

在Python爬虫开发中,性能优化始终是一个重要的课题。随着网络数据的爆炸式增长,传统的同步爬虫在面对大量请求时显得力不从心。异步爬虫和多线程技术应运而生,成为提升爬虫性能的关键手段。本文将深入探讨Python异步爬虫进阶技术,重点介绍aiohttp与多线程的结合使用,通过同步与异步请求对比、asyncio事件循环原理、线程池ThreadPoolExecutor以及性能优化技巧等方面,帮助读者掌握高效爬虫的开发技巧。

1. 同步与异步请求对比

同步请求

同步请求是指在发送请求后,程序会阻塞等待响应,直到响应到达后才继续执行后续代码。这种方式在处理少量请求时简单直观,但在处理大量请求时,会导致程序效率低下,因为大量时间被浪费在等待响应上。

import requests

# 同步请求方式
def sync_request(url):
    response = requests.get(url)
    return response.text

# 同步请求多个接口
urls = ['http://example.com/1', 'http://example.com/2', 'http://example.com/3']
results = [sync_request(url) for url in urls]
print(results)
异步请求

异步请求则允许程序在等待响应的同时执行其他任务,通过事件循环调度异步任务,提高程序并发性和响应速度。这在处理大量网络请求时尤为有效。

import asyncio
import aiohttp

urls = ['http://example.com/1', 'http://example.com/2', 'http://example.com/3']

# 定义异步请求方法
async def async_request(session, url):
    async with session.get(url) as response:
        return await response.text()
 
async def main():

    # 请求多个接口
    async with aiohttp.ClientSession() as session:
        tasks = [async_request(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        print(results)
 
asyncio.run(main())

2. asyncio事件循环原理

事件循环简介

事件循环是asyncio库的核心组件,负责调度和执行异步任务。它不断地检查任务队列,当有任务准备好(如I/O操作完成)时,便调度该任务执行。事件循环确保了异步任务的高效并发执行。

工作原理
  • 事件队列: 存储所有待处理的任务和事件。
  • 轮询检查: 事件循环不断检查事件队列,查看是否有新的事件或任务需要处理。
  • 调度执行: 一旦有任务准备好,事件循环调度该任务执行。
  • 非阻塞执行: 如果某个任务需要等待(如网络请求),事件循环不会阻塞,而是继续处理其他任务。
示例代码
import asyncio
 
async def task1():
    print("Task 1 开始")

    # 模拟耗时操作
    await asyncio.sleep(2)
    print("Task 1 完成")
 
async def task2():
    print("Task 2 开始")

    # 模拟耗时操作
    await asyncio.sleep(1)
    print("Task 2 完成")
 
async def main():
    task_1 = asyncio.create_task(task1())
    task_2 = asyncio.create_task(task2())
    await asyncio.gather(task_1, task_2)
 
asyncio.run(main())

3. 线程池ThreadPoolExecutor

线程池简介

线程池是一种线程管理技术,通过预先创建一定数量的线程并放入池中,当有任务需要执行时,从池中取出线程执行任务,任务完成后线程归还池中。这减少了线程创建和销毁的开销,提高了程序性能。

ThreadPoolExecutor使用方法
from concurrent.futures import ThreadPoolExecutor
 
def worker(num):
    print(f"Worker {num} is working")
 
with ThreadPoolExecutor(max_workers=5) as executor:
    futures = [executor.submit(worker, i) for i in range(10)]
    for future in futures:

        # 等待任务完成
        future.result()
示例代码
import threading
from concurrent.futures import ThreadPoolExecutor
 
def task(n):
    print(f"Task {n} executed by {threading.current_thread().name}")
 
with ThreadPoolExecutor(max_workers=3) as executor:
    executor.map(task, range(1, 11))

4. 性能优化技巧

并发请求优化

使用aiohttp和asyncio实现并发请求,可以显著提高爬虫性能。通过创建多个异步任务并同时发送请求,减少等待时间。

import asyncio
import aiohttp
 
async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()
 
async def main():
    urls = ['http://example.com/1', 'http://example.com/2', 'http://example.com/3']
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        print(results)
 
asyncio.run(main())
限制并发量

使用asyncio的Semaphore限制并发量,避免对目标服务器造成过大压力,同时保证程序的稳定性和效率。

import asyncio
import aiohttp
 
CONCURRENCY = 5
semaphore = asyncio.Semaphore(CONCURRENCY)
 
async def fetch(session, url):
    async with semaphore:
        async with session.get(url) as response:
            return await response.text()
 
async def main():
    urls = ['http://example.com/1', 'http://example.com/2', 'http://example.com/3']
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        print(results)
 
asyncio.run(main())
多线程与异步结合

在某些场景下,可以结合多线程和异步编程,进一步提升性能。例如,使用线程池处理CPU密集型任务,使用异步处理I/O密集型任务。

import asyncio
import aiohttp
from concurrent.futures import ThreadPoolExecutor
 
def cpu_bound_task(n):
    # 模拟CPU密集型任务
    total = 0
    for i in range(n):
        total += i
    return total
 
async def io_bound_task(session, url):
    async with session.get(url) as response:
        return await response.text()
 
async def main():
    urls = ['http://example.com/1', 'http://example.com/2', 'http://example.com/3']
    with ThreadPoolExecutor(max_workers=3) as executor:
        loop = asyncio.get_running_loop()
        cpu_tasks = [loop.run_in_executor(executor, cpu_bound_task, 10**6) for _ in range(3)]
        async with aiohttp.ClientSession() as session:
            io_tasks = [io_bound_task(session, url) for url in urls]
            cpu_results = await asyncio.gather(*cpu_tasks)
            io_results = await asyncio.gather(*io_tasks)
        print(f"CPU results: {cpu_results}")
        print(f"IO results: {io_results}")
 
asyncio.run(main())

总结

通过本文的介绍,我们深入了解了Python异步爬虫进阶技术,包括同步与异步请求的对比、asyncio事件循环原理、线程池ThreadPoolExecutor的使用以及性能优化技巧。通过结合aiohttp和多线程技术,我们可以构建高效、稳定的爬虫系统,满足大规模数据抓取的需求。希望本文能为读者在Python爬虫开发道路上提供一些有益的参考和启发。

关注我!!🫵 持续为你带来Python相关内容。