当前位置: 首页 > news >正文

第九课:异步爬虫进阶: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相关内容。

相关文章:

  • 【Java 和 Scala】-- Java 与 Scala 的 Assert 断言对比
  • AI Agent系列(四) -Agent架构认知
  • 【ODHead】BEVDet的 CenterHead的推理和拓展到蒸馏损失的算法细节
  • java后端开发day31--集合进阶(一)-----Collection集合List集合数据结构1
  • Hive SQL 精进系列:字符串拼接的三种常用方式
  • 【WRF-Chem】预处理工具(Preprocessors)总结
  • es-索引详解
  • 论文笔记 - ULTRA-SPARSE MEMORY NETWORK
  • 解决:外部调用存储过程时突然变慢,但是在sql server运行很快
  • ios打包需要的证书及步骤
  • flutter dio库 源码赏析
  • Java继承机制深度解析:子类如何继承父类及内存原理解析
  • 《A Gentle Introduction to Graph Neural Networks》-GNN的综述性论文
  • 玩转python:掌握Python数据结构之Trie树
  • Django部署Filemanagement
  • Next.js介绍(React框架)
  • 32- 两数之和 II - 输入有序数组
  • AutoGen学习笔记系列(十四)Advanced - Serializing Components
  • OpenSSL 的主要功能及其示例命令
  • Python 文件和异常(存储数据)
  • “一嗨租车”陷“五年后扣费”疑云,用户:违章处理莫名消失
  • 印度杰纳布河上游两座水电站均已重新开闸
  • 九家企业与上海静安集中签约,投资额超10亿元
  • 菲护卫艇企图侵闯中国黄岩岛领海,南部战区:依法依规跟踪监视、警告驱离
  • 潘功胜:央行将设立5000亿元服务消费与养老再贷款
  • 是谁提议特朗普向好莱坞征税?