news 2026/8/21 2:28:18

59秒突破:构建高性能异步机器人框架的实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
59秒突破:构建高性能异步机器人框架的实战指南

在实际的自动化测试和机器人开发项目中,我们经常遇到一个核心挑战:如何让一个自动化流程或机器人(Bot)在极短的时间窗口内,完成一系列复杂的、依赖外部响应的操作。例如,在抢购、秒杀、高频交易模拟或自动化压力测试场景中,59秒可能是一个关键的时间阈值。本文将深入探讨如何设计并实现一个能够在59秒内完成“最突破”性能表现的机器人(100bot),这通常意味着它需要突破常规的性能瓶颈、网络延迟和逻辑处理速度的限制。

本文的目标读者是具备一定编程基础,对网络请求、并发处理和性能优化有初步了解的开发者。我们将从概念设计入手,逐步讲解如何构建一个高并发、低延迟的机器人框架,涵盖环境准备、核心代码实现、性能验证以及生产环境下的关键注意事项。通过本文,你将能够理解并实践一套从零构建高性能自动化任务执行器的完整方案。

1. 理解“59秒突破”背后的性能挑战

“59秒内完成最突破的一集”这个表述,在技术语境下通常指向一个性能目标:让一个机器人(Bot)在不到一分钟的时间内,突破常规限制,完成一项极具挑战性的任务。这背后涉及几个核心的技术挑战:

  1. 高并发处理能力:单个机器人实例可能不足以在时限内完成任务,需要并发或分布式地运行多个实例(即“100bot”的意象)。
  2. 极低的网络延迟:任务往往涉及与远程服务器的多次交互,网络往返时间(RTT)是主要瓶颈之一。
  3. 高效的任务调度与执行逻辑:避免不必要的等待、串行操作和资源竞争。
  4. 资源利用与防阻塞:妥善管理内存、CPU和网络连接,防止单个失败任务阻塞整个流程。
  5. 对抗反机器人机制:在实际场景中,目标服务器可能设有频率限制、验证码或行为分析,需要策略性绕过。

要实现“突破”,就不能仅仅写一个简单的循环请求脚本。我们需要一个结构清晰、各司其职的框架。

1.1 核心架构设计

一个能够应对上述挑战的机器人框架,可以抽象为以下几个模块:

  • 任务生成器(Task Producer):负责定义需要执行的具体操作单元,例如“发起一次HTTP请求并解析结果”。
  • 任务队列(Task Queue):作为缓冲层,解耦任务生成与执行。在高并发场景下,队列能平滑流量,避免任务丢失。
  • 工作者池(Worker Pool):一组并发执行单元(Worker),从队列中获取任务并执行。池的大小决定了并发度。
  • 会话与状态管理(Session & State Management):对于需要保持登录态或上下文的任务,需要管理Cookie、Token等状态。
  • 结果处理器(Result Processor):收集、分析工作者返回的结果,进行汇总、持久化或触发下一步操作。
  • 监控与控制器(Monitor & Controller):监控整个系统的运行状态(如队列长度、工作者活跃数、成功率),并可能动态调整参数。

在59秒的极限场景下,我们通常采用异步非阻塞I/O模型来最大化利用单机资源,或者采用多进程/分布式模型来利用多机资源。

2. 环境准备与依赖配置

我们将使用Python进行演示,因为它拥有丰富的异步编程和网络请求库。选择asyncioaiohttp构建一个异步高性能的机器人原型。

2.1 基础环境

确保你的开发环境满足以下要求:

  • Python版本: >= 3.8 (强烈推荐3.8+,对asyncio支持更完善)
  • 操作系统: Linux/macOS/Windows (Windows上对asyncio的支持可能略有不同,但基本功能一致)
  • 包管理工具pip

2.2 项目依赖

创建一个新的项目目录,并初始化一个requirements.txt文件。

# requirements.txt aiohttp>=3.8.0 # 异步HTTP客户端/服务器 asyncio>=3.4.3 # 异步I/O框架 (Python内置,此处注明版本要求) aiofiles>=23.0.0 # 异步文件操作,用于结果写入 pytest>=7.0.0 # 测试框架 pytest-asyncio>=0.21.0 # 支持异步测试

使用pip安装依赖:

pip install -r requirements.txt

2.3 项目结构规划

一个清晰的项目结构有助于管理复杂度。建议如下:

59s_breakthrough_bot/ ├── config/ │ └── settings.py # 配置文件,存放目标URL、并发数、超时时间等 ├── core/ │ ├── __init__.py │ ├── task.py # 任务定义 │ ├── worker.py # 工作者实现 │ ├── session_manager.py # 会话管理 │ └── queue_manager.py # 队列管理(可使用asyncio.Queue) ├── utils/ │ ├── __init__.py │ ├── logger.py # 日志配置 │ └── helper.py # 通用工具函数 ├── main.py # 主程序入口 ├── requirements.txt └── README.md

3. 构建异步高性能机器人核心

我们将从核心模块开始,逐步实现一个能在59秒内发起大量请求并处理结果的机器人。

3.1 定义任务(Task)

任务是执行的最小单元。这里我们定义一个简单的HTTP GET请求任务。

# core/task.py import asyncio from dataclasses import dataclass from typing import Any, Optional import aiohttp @dataclass class Task: """一个基本的HTTP请求任务""" task_id: int url: str method: str = 'GET' headers: Optional[dict] = None data: Any = None # 任务元数据,可用于传递上下文 metadata: Optional[dict] = None async def execute(self, session: aiohttp.ClientSession) -> dict: """ 执行任务 :param session: aiohttp客户端会话,用于复用连接 :return: 包含状态和结果的字典 """ result = { 'task_id': self.task_id, 'url': self.url, 'status': 'pending', 'response_status': None, 'data': None, 'error': None } try: async with session.request( method=self.method, url=self.url, headers=self.headers, data=self.data ) as response: result['response_status'] = response.status # 这里可以根据需要读取文本、JSON或字节 # 例如,读取文本: result['data'] = await response.text() result['status'] = 'success' except asyncio.TimeoutError: result['status'] = 'timeout' result['error'] = 'Request timeout' except aiohttp.ClientError as e: result['status'] = 'client_error' result['error'] = str(e) except Exception as e: result['status'] = 'failed' result['error'] = str(e) return result

关键点解释

  • 使用dataclass简化任务对象的定义。
  • execute方法是异步的,它接收一个复用的aiohttp.ClientSession,这是高性能的关键。
  • 结果字典包含了足够的信息用于后续分析:任务ID、状态、HTTP状态码、响应数据或错误信息。

3.2 实现工作者(Worker)与工作者池

工作者负责从队列中获取任务并执行。我们将创建一个工作者池来管理多个工作者。

# core/worker.py import asyncio import aiohttp from typing import List from .task import Task class Worker: """单个工作者,持续从队列中取任务执行""" def __init__(self, worker_id: int, task_queue: asyncio.Queue, result_queue: asyncio.Queue, session: aiohttp.ClientSession): self.worker_id = worker_id self.task_queue = task_queue self.result_queue = result_queue self.session = session self.is_running = True async def run(self): """工作者的主循环""" print(f"Worker-{self.worker_id} started.") while self.is_running: try: # 从队列获取任务,设置超时避免工作者永远阻塞 task: Task = await asyncio.wait_for(self.task_queue.get(), timeout=1.0) # 执行任务 result = await task.execute(self.session) # 将结果放入结果队列 await self.result_queue.put(result) # 标记任务完成 self.task_queue.task_done() except asyncio.TimeoutError: # 队列为空超时,检查是否应该停止 continue except Exception as e: print(f"Worker-{self.worker_id} encountered an error: {e}") # 即使出错,也标记任务完成,避免队列阻塞 if not self.task_queue.empty(): self.task_queue.task_done() def stop(self): """停止工作者""" self.is_running = False class WorkerPool: """工作者池,管理一组工作者""" def __init__(self, pool_size: int, task_queue: asyncio.Queue, result_queue: asyncio.Queue): self.pool_size = pool_size self.task_queue = task_queue self.result_queue = result_queue self.workers: List[Worker] = [] self.session: Optional[aiohttp.ClientSession] = None async def start(self): """启动工作者池,创建Session和所有工作者""" # 创建一个共用的ClientSession,这是性能最佳实践 connector = aiohttp.TCPConnector(limit=0, limit_per_host=0) # 不限制总连接数和每主机连接数 self.session = aiohttp.ClientSession(connector=connector) for i in range(self.pool_size): worker = Worker(i, self.task_queue, self.result_queue, self.session) self.workers.append(worker) # 并发运行所有工作者 await asyncio.gather(*(worker.run() for worker in self.workers)) async def stop(self): """停止工作者池,关闭Session""" for worker in self.workers: worker.stop() if self.session: await self.session.close()

关键点解释

  • Worker的核心是一个循环,不断从task_queue中获取任务。使用wait_for设置超时,使得工作者在队列为空时也能定期检查停止信号。
  • WorkerPool负责创建和管理多个Worker实例,并提供一个共享的aiohttp.ClientSession。复用Session可以保持连接池,极大提升HTTP请求效率。
  • TCPConnector(limit=0, limit_per_host=0)表示不限制连接数,在高并发请求同一主机时至关重要,但需谨慎使用,避免对目标服务器造成过大压力。

3.3 主程序与任务调度

现在,我们将上述组件组合起来,创建一个能在59秒内执行大量任务的主程序。

# main.py import asyncio import aiohttp import time from core.task import Task from core.worker import WorkerPool async def producer(task_queue: asyncio.Queue, total_tasks: int, base_url: str): """任务生产者:生成指定数量的任务放入队列""" for i in range(total_tasks): # 构造任务,这里以查询不同ID为例 url = f"{base_url}?id={i}" task = Task(task_id=i, url=url) await task_queue.put(task) # 可以在这里加入动态生成逻辑,或从文件读取任务列表 print(f"[Producer] All {total_tasks} tasks have been queued.") async def consumer(result_queue: asyncio.Queue, total_tasks: int): """结果消费者:从结果队列中取出并处理结果""" successful = 0 failed = 0 start_time = time.time() for _ in range(total_tasks): result = await result_queue.get() if result['status'] == 'success': successful += 1 # 这里可以处理成功结果,例如解析数据、存储等 # print(f"Task {result['task_id']} succeeded with status {result['response_status']}") else: failed += 1 # 这里可以处理失败结果,例如记录日志、重试等 print(f"Task {result['task_id']} failed with error: {result['error']}") result_queue.task_done() end_time = time.time() duration = end_time - start_time print(f"\n[Consumer] All results processed in {duration:.2f} seconds.") print(f" Success: {successful}, Failed: {failed}") return duration async def main(): # 配置参数 TOTAL_TASKS = 1000 # 总任务数 (模拟100bot) WORKER_POOL_SIZE = 100 # 并发工作者数量 BASE_URL = "https://httpbin.org/get" # 一个用于测试的公开API TIME_LIMIT = 59 # 时间限制(秒) # 创建队列 task_queue = asyncio.Queue(maxsize=TOTAL_TASKS * 2) # 设置一个足够大的队列 result_queue = asyncio.Queue(maxsize=TOTAL_TASKS * 2) # 启动生产者(异步) producer_task = asyncio.create_task(producer(task_queue, TOTAL_TASKS, BASE_URL)) # 创建并启动工作者池 pool = WorkerPool(WORKER_POOL_SIZE, task_queue, result_queue) pool_task = asyncio.create_task(pool.start()) # 等待生产者完成(确保所有任务都已入队) await producer_task print("[Main] Producer finished. Waiting for workers to process...") # 启动消费者,并等待其在时间限制内完成 try: duration = await asyncio.wait_for(consumer(result_queue, TOTAL_TASKS), timeout=TIME_LIMIT+5) # 给一个缓冲时间 if duration <= TIME_LIMIT: print(f"\n🎉 突破成功!在 {duration:.2f} 秒内完成了 {TOTAL_TASKS} 个任务。") else: print(f"\n⚠️ 未能在 {TIME_LIMIT} 秒内完成,实际耗时 {duration:.2f} 秒。") except asyncio.TimeoutError: print(f"\n❌ 超时!在 {TIME_LIMIT} 秒内未能处理完所有任务。") finally: # 清理资源 await pool.stop() # 等待队列清空(可选) await task_queue.join() await result_queue.join() if __name__ == "__main__": asyncio.run(main())

4. 运行验证与性能分析

4.1 首次运行与基准测试

运行上述main.py。由于我们使用了https://httpbin.org/get这个稳定的测试服务,你应该能看到类似以下的输出:

[Producer] All 1000 tasks have been queued. Worker-0 started. Worker-1 started. ... Worker-99 started. [Main] Producer finished. Waiting for workers to process... Task 123 failed with error: ... ... [Consumer] All results processed in 12.45 seconds. Success: 995, Failed: 5 🎉 突破成功!在 12.45 秒内完成了 1000 个任务。

结果分析

  • 耗时:远低于59秒,说明我们的异步框架基础性能足够。
  • 成功率:可能不是100%,因为网络存在波动,测试服务也可能有轻微限制。这符合真实场景。
  • 瓶颈观察:如果耗时接近或超过59秒,我们需要分析瓶颈所在。

4.2 性能瓶颈分析与调优

为了“突破”,我们需要找到并解决瓶颈。以下是一个排查和优化清单:

瓶颈点现象排查方法优化策略
网络延迟与带宽单个请求耗时很长,所有工作者都在等待I/O。使用pingtraceroute或在线工具测试目标服务器延迟和丢包率。用curlwget测试单请求速度。1. 更换更快的网络环境或使用代理(合规用途)。
2. 优化DNS解析(使用本地HOSTS或更快的DNS)。
3. 对于公网服务,选择地理位置上更近的节点。
目标服务器限制请求大量返回429(Too Many Requests)或503错误。查看响应头中的X-RateLimit-*字段。分析失败请求的规律(是否集中在某一时段)。1. 降低并发数(WORKER_POOL_SIZE)。
2. 在请求中加入随机延迟(asyncio.sleep(random.uniform(0.1, 0.5)))。
3. 实现更复杂的退避重试机制(如指数退避)。
本地资源限制程序运行后CPU占用率100%,或内存持续增长。使用系统监控工具(如top,htop,任务管理器)。在代码中记录内存使用情况。1. 调整工作者数量,使其与CPU核心数匹配(os.cpu_count())。
2. 优化任务execute方法,避免在内存中累积过大响应数据(及时处理或丢弃)。
3. 使用连接池限制(TCPConnector(limit=100))防止文件描述符耗尽。
队列竞争与调度工作者经常空闲,但队列中还有任务。打印队列长度变化。检查producer是否太慢。1. 确保producer是异步的,并且不会因为同步操作(如读大文件)而阻塞。
2. 使用asyncio.Queueput_nowaitget_nowait配合asyncio.sleep(0)来优化调度(高级技巧)。
DNS解析延迟每个请求的初始连接时间很长。在请求前后打时间戳,记录TCP Connect时间。1. 使用aiohttpTCPConnector并启用use_dns_cache=True(默认已启用)。
2. 考虑在程序启动时预先解析主机名。

优化后的WorkerPool初始化示例

# 更稳健的连接器配置 connector = aiohttp.TCPConnector( limit=100, # 限制总连接数 limit_per_host=20, # 限制对同一主机的并发连接数,避免被ban ttl_dns_cache=300, # DNS缓存时间 force_close=False, # 保持长连接 enable_cleanup_closed=True # 清理关闭的连接 ) self.session = aiohttp.ClientSession( connector=connector, timeout=aiohttp.ClientTimeout(total=30) # 设置总超时 )

5. 常见问题排查与实战技巧

在实际运行中,你可能会遇到以下问题:

5.1 错误:Event loop is closedRuntimeError

  • 现象:程序结束时或发生异常后报错。
  • 原因:在Windows上或某些异步操作未妥善结束时,事件循环可能提前关闭。
  • 解决:确保所有异步资源都被正确关闭。使用asyncio.run(main())(Python 3.7+)来管理事件循环生命周期是最佳实践。如果必须在旧版本或复杂环境下手动管理,请确保finally块中执行了loop.close()

5.2 错误:Timeout context manager should be used inside a task

  • 现象:在使用asyncio.wait_for时出错。
  • 原因:在非异步上下文或错误的位置调用了异步超时函数。
  • 解决:确保wait_for被用在async函数内,并且等待的对象是一个awaitable(如task_queue.get())。

5.3 问题:程序似乎“卡住”,不报错也不结束

  • 现象:日志停止输出,CPU使用率很低,程序挂起。
  • 排查
    1. 检查队列:生产者是否已将所有任务放入队列?消费者是否在等待结果?可以打印队列大小。
    2. 检查网络连接:目标服务器是否无响应?工作者是否在等待一个永远不会返回的HTTP请求?为aiohttp会话设置合理的超时(ClientTimeout)。
    3. 检查死锁:是否在异步函数中错误地使用了同步阻塞操作(如time.sleep、同步文件读写、CPU密集型计算)?这会导致整个事件循环阻塞。
  • 解决
    • 将同步阻塞操作替换为异步版本(如asyncio.sleep,aiofiles)。
    • 对于CPU密集型任务,使用asyncio.to_threadconcurrent.futures.ThreadPoolExecutor将其放到单独线程中执行,避免阻塞事件循环。

5.4 问题:内存使用量不断上升(内存泄漏)

  • 现象:程序运行一段时间后,占用内存越来越多。
  • 排查
    1. 检查结果处理consumer是否及时从result_queue中取走结果并处理?结果对象是否过大(如包含完整的HTML页面)?
    2. 检查引用循环:在复杂的异步回调中,可能意外创建了对象间的循环引用,导致垃圾回收器无法回收。虽然Python有循环垃圾回收,但异步任务中的引用需要留意。
    3. 使用内存分析工具:如tracemalloc或第三方库objgraphmemory_profiler
  • 解决
    • consumer中处理完结果后,显式地将大的临时变量设为None
    • 避免在任务或结果中存储不必要的数据。
    • 定期(如每处理1000个任务)强制进行垃圾回收(gc.collect()),但这通常是最后的手段。

6. 生产环境最佳实践与扩展方向

要将这个59秒突破机器人用于更严肃的场景,需要考虑以下方面:

6.1 配置外置化

不要将TOTAL_TASKSWORKER_POOL_SIZEBASE_URL等参数硬编码在代码中。使用配置文件(如config/settings.pyconfig.yaml)或环境变量来管理。

# config/settings.py import os from typing import Optional TOTAL_TASKS = int(os.getenv('TOTAL_TASKS', 1000)) WORKER_POOL_SIZE = int(os.getenv('WORKER_POOL_SIZE', 50)) BASE_URL = os.getenv('BASE_URL', 'https://httpbin.org/get') REQUEST_TIMEOUT = int(os.getenv('REQUEST_TIMEOUT', 30)) # ... 其他配置

6.2 完善的日志与监控

使用Python的logging模块替代print,并配置不同的日志级别(INFO, WARNING, ERROR)。将关键指标(如TPS-每秒事务数、成功率、平均响应时间)输出到日志或发送到监控系统(如Prometheus)。

# utils/logger.py import logging import sys def setup_logger(name: str) -> logging.Logger: logger = logging.getLogger(name) logger.setLevel(logging.INFO) handler = logging.StreamHandler(sys.stdout) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) return logger # 在core/worker.py中使用 logger = setup_logger(__name__) logger.info(f"Worker-{self.worker_id} started.") logger.error(f"Worker-{self.worker_id} encountered an error: {e}")

6.3 优雅停机与状态持久化

在收到终止信号(如Ctrl+C)时,应允许当前正在执行的任务完成,并将队列中未处理的任务状态保存下来,以便下次启动时恢复。

# main.py 中增加信号处理 import signal import sys def handle_exit(signum, frame): print("\nReceived exit signal, shutting down gracefully...") # 设置停止标志,让生产者和工作者自然结束循环 # ... 然后等待队列清空,保存状态 sys.exit(0) signal.signal(signal.SIGINT, handle_exit) signal.signal(signal.SIGTERM, handle_exit)

6.4 分布式扩展

当单机性能达到瓶颈时,需要考虑分布式。思路是将task_queueresult_queue替换为外部的消息队列,如RedisRabbitMQ。每个工作者可以部署在不同的机器上,从共享队列中拉取任务并推送结果。

  • 任务队列:使用Redis的List或Stream结构,生产者向其中推送任务描述(JSON格式)。
  • 工作者:每个工作者实例独立运行,从Redis中BLPOP任务,执行后向另一个结果Stream或List推送结果。
  • 协调者:需要一个主进程或脚本来监控整体进度、管理工作者生命周期。

6.5 对抗反机器人策略

对于有防护的网站,简单的并发请求会被轻易识别和封禁。你需要升级你的机器人:

  1. 请求头随机化:模拟真实浏览器的User-Agent、Accept-Language等头部信息库,并随机选择。
  2. 请求间隔随机化:在请求之间加入随机延迟,模拟人类操作。
  3. 会话管理:处理登录、Cookie、JWT Token的获取与刷新。
  4. 代理IP池:使用多个代理IP轮询发送请求,避免IP被封。
  5. 浏览器自动化:对于JavaScript渲染严重的网站,可能需要使用playwrightselenium的无头浏览器,但这会极大降低性能,与“59秒突破”的目标相悖,需权衡。

实现“59秒内最突破的一集”,本质上是系统工程问题,需要在架构设计、资源利用、网络优化和错误处理之间找到最佳平衡点。从本文的最小可行异步框架出发,通过持续的 profiling(性能剖析)、监控和迭代,你能够构建出适应各种苛刻场景的高性能自动化解决方案。下一步,可以尝试集成分布式队列、实现更复杂的任务依赖关系、或者针对特定API协议(如WebSocket, gRPC)进行优化。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/21 2:26:24

基于Cloudflare Workers与Durable Objects构建边缘Git托管服务

在分布式协作开发中&#xff0c;Git 作为版本控制的核心工具&#xff0c;其托管平台&#xff08;如 GitHub、GitLab&#xff09;的稳定性和可定制性至关重要。然而&#xff0c;当我们需要一个轻量、私有、可完全掌控且能随业务弹性扩展的 Git 服务时&#xff0c;传统的自建方案…

作者头像 李华
网站建设 2026/8/21 2:25:13

程序员数小时破解Claude隐形水印,欧盟AI监管遭现实考验

编者按&#xff1a;Anthropic的一次合规表态&#xff0c;数小时内就被技术社区“拆台”。这不仅是全球首个国家级AI监管框架与真实黑客文化的正面碰撞&#xff0c;也提醒所有人&#xff1a;AI溯源&#xff0c;远未成熟。 上周&#xff0c;Anthropic宣布将在其Claude模型生成的文…

作者头像 李华
网站建设 2026/8/21 2:22:51

智能体评测协议效度剖析:如何构建真实反映AI能力的评估体系

1. 项目概述&#xff1a;当我们在谈论智能体评测时&#xff0c;到底在测什么&#xff1f;最近和几个做AI智能体&#xff08;Agent&#xff09;的朋友聊天&#xff0c;大家不约而同地提到了同一个困惑&#xff1a;现在各种智能体评测榜单层出不穷&#xff0c;今天这个榜单说A模型…

作者头像 李华
网站建设 2026/8/21 2:21:08

基于STM32的智能安全头盔系统:开源项目实战与二次开发指南

这次我们来看一个基于STM32的骑行者智能安全头盔系统开源项目。项目已经完整开源&#xff0c;包含了核心源码和详细的原理图&#xff0c;对于嵌入式开发、物联网应用以及智能硬件爱好者来说&#xff0c;这是一个非常值得研究的实战案例。它不是一个停留在概念阶段的设计&#x…

作者头像 李华
网站建设 2026/8/21 2:19:46

STM32智能小车项目全解析:从避障循迹到WIFI控制的嵌入式综合实践

这次我们来看一个功能相当全面的STM32智能小车项目。它集成了超声波避障、循迹清扫、舵机控制、清扫模块、车速控制、电压检测、WIFI控制和OLED显示&#xff0c;几乎囊括了嵌入式课程设计或电子竞赛中智能小车的主流功能。对于正在学习STM32、想从零搭建一个综合性实践平台的同…

作者头像 李华
网站建设 2026/8/21 2:17:35

STM32智能宠物喂食器开源项目全解析:从硬件到软件的完整复现指南

这次我们来看一个基于 STM32 的智能宠物喂食系统开源项目。对于有宠物但经常需要外出的朋友来说&#xff0c;定时、定量、甚至远程喂食是个刚需。市面上的成品要么功能单一&#xff0c;要么价格不菲。这个项目直接把完整的解决方案开源了&#xff0c;包含了核心的源码和硬件原理…

作者头像 李华