news 2026/5/31 11:25:08

基于Playwright与本地语音识别的视频会议自动化助手开发实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Playwright与本地语音识别的视频会议自动化助手开发实践

1. 项目缘起与核心需求解析

隔离到第22天,那种感觉,懂的都懂。每天面对同样的四面墙,线上会议成了连接外界的唯一窗口,但冗长、重复且常常效率低下的会议流程,真的能把人逼疯。就在这种背景下,我萌生了一个想法:能不能让一个“机器人”来替我处理那些机械性的、规则明确的会议任务?比如自动签到、记录关键发言、甚至在特定条件下代为发言或提问?这就是“Zoombot”项目的起点——一个旨在自动化处理视频会议中繁琐事务的私人助手。

这个项目的核心需求非常明确:解放人力,提升线上协作效率。具体拆解下来,可以分为几个层面:

1.1 自动化执行重复性任务这是最直接的需求。在团队每日站会、每周复盘会等固定流程的会议中,总有一些动作是重复的:进入会议室、修改昵称、开启/关闭麦克风、在聊天框发送固定信息(如“到”、“同意”)、甚至是在特定时间点发送提醒。手动操作这些不仅枯燥,还容易因分心而错过会议核心内容。Zoombot 的首要目标就是接管这些“体力活”。

1.2 智能记录与信息萃取线上会议的信息流是碎片化的,语音、聊天文字、共享屏幕内容交织在一起。人工记录很难做到全面、准确,尤其是快速回顾时,找到某个关键决策或待办事项如同大海捞针。Zoombot 需要具备“耳朵”和“眼睛”,能够监听语音并将其转为文字,抓取聊天区的重要链接或结论,自动生成结构化的会议纪要,提炼出 Action Items(行动项)、Owner(负责人)和 Deadline(截止时间)。

1.3 条件触发与交互响应更进阶的需求是让 Zoombot 具备一定的“智能”,能够根据会议上下文进行交互。例如,当主持人提到“请大家反馈一下进度”时,Zoombot 可以自动在聊天框发送我预设的进度报告;或者当检测到会议时长超过预定时间30分钟时,自动发送一条礼貌的提醒消息。这要求机器人不仅能“听”,还要能“理解”并“行动”。

1.4 隐私与安全的底线思维开发这样一个工具,隐私和安全是绝对不能逾越的红线。它必须是一个纯粹的本地化或受控的客户端工具,所有音频、视频数据的处理都应发生在用户自己的设备上,绝不能将会议内容上传至不明服务器。同时,其行为必须符合会议平台的使用条款,不能用于恶意刷屏、干扰会议等用途,这既是法律合规要求,也是基本的职业道德。

基于这些需求,Zoombot 不是一个试图创造通用人工智能的庞大项目,而是一个聚焦于具体场景、解决实际痛点的效率工具。它的技术栈选择、架构设计都将紧紧围绕“安全、轻量、实用、可扩展”这四个原则展开。

2. 技术选型与架构设计思路

确定了要做什么,接下来就是“怎么做”。技术选型直接决定了项目的可行性、开发效率和最终体验。我的核心思路是:利用成熟的、可编程的桌面自动化框架作为“手”和“眼”,结合轻量级的本地语音/文本处理服务作为“脑”,通过清晰的规则引擎进行调度。

2.1 自动化控制层:为什么是 Playwright 和 PyAutoGUI?要让机器人模拟人在电脑前的操作,自动化框架是基石。我放弃了早期考虑的单纯“图像识别点击”方案(如使用pyautogui的截图匹配),因为它在不同分辨率、主题下的稳定性欠佳。最终选择了Playwright作为主力。

  • 理由:Playwright 是微软开源的浏览器自动化测试框架,它直接通过浏览器开发工具协议(CDP)与浏览器内核通信,能精准定位页面上的任何元素(按钮、输入框、聊天窗口),不受屏幕分辨率或UI主题变化的影响。对于 Zoom、Teams、Webex 等基于Web的应用或客户端内嵌的Web视图,控制精度和可靠性极高。你可以让它精准地点击“解除静音”按钮,而不是去估算一个屏幕坐标。
  • 补充角色:对于客户端中非Web的部分,或者需要全局快捷键模拟(如快速静音Alt+A)的情况,PyAutoGUIkeyboard/pynput这类库作为补充依然有价值。例如,监听全局热键来激活/休眠Zoombot。

2.2 信息感知层:语音与文本的捕获机器人要“知道”会议上发生了什么,就需要获取信息。

  • 语音转文字(STT):这是实现智能记录和条件触发的关键。考虑到隐私和离线可用性,我没有选择调用云端API(如Google Cloud Speech-to-Text),而是采用了VoskWhisper.cpp这样的本地开源模型。Vosk 轻量、速度快,对于中英文的会议场景基本够用;如果对精度要求极高,可以选用裁剪后的 Whisper 模型。它们都能在本地实时将系统音频或麦克风输入转为文字,数据不出本地。
  • 文本抓取:对于聊天框内容,Playwright 可以轻松获取指定DOM元素的文本内容。我们需要做的就是定时(例如每5秒)抓取聊天消息列表的新增项。

2.3 核心大脑:规则引擎与状态机这是 Zoombot 的“逻辑中心”。它不是一个复杂的AI,而是一个基于规则的决策系统。我设计了一个简单的事件-条件-动作(ECA)规则引擎。

  • 事件:例如,“收到新的聊天消息”、“语音转文字结果中包含关键词‘投票’”、“当前时间达到会议预定结束时间”。
  • 条件:对事件进行过滤和判断。例如,“如果消息发送者是主持人”、“如果关键词‘投票’出现在过去30秒的语音记录中”。
  • 动作:条件满足时执行的操作。例如,“在聊天框回复‘已投票’”、“发送预设的进度报告文本”、“模拟按下静音快捷键”。 通过 YAML 或 JSON 配置文件来定义这些规则,使得 Zoombot 的行为可以高度定制化,无需修改代码即可适配不同会议的习惯。

2.4 整体架构草图一个简化的架构流程如下:

  1. 启动与初始化:加载配置文件,启动Playwright控制指定浏览器并跳转到会议链接,登录(或使用已保存的会话)。
  2. 信息采集线程
    • 线程A:通过Playwright持续监听聊天框DOM变化,捕获新文本。
    • 线程B:通过pyaudio捕获系统音频输出(或虚拟音频线混音后的音频),送入本地Vosk模型进行持续语音识别。
  3. 事件处理中心:将采集到的文本(聊天、语音转写)封装成事件,投入一个事件队列。
  4. 规则引擎线程:持续从事件队列中取出事件,与预加载的规则集进行匹配。若某条规则的“事件”和“条件”均满足,则触发其对应的“动作”。
  5. 动作执行器:执行动作,可能是通过Playwright操作浏览器元素,也可能是通过keyboard库模拟全局按键。

注意:多线程编程需要小心处理资源竞争和状态同步。例如,当规则引擎正在执行一个“发送消息”的动作时,信息采集线程应该暂停向事件队列投放新的“聊天消息”事件,避免自我触发导致循环。

3. 核心模块实现与实操要点

纸上谈兵终觉浅,我们来深入几个核心模块的代码级实现细节和踩坑经验。

3.1 基于 Playwright 的会议界面精准控制Playwright 的核心是“选择器”。以 Zoom Web 版为例,要找到“聊天”按钮并点击,不能靠猜坐标,而要用开发者工具定位其唯一属性。

from playwright.sync_api import sync_playwright def control_zoom_meeting(): with sync_playwright() as p: # 使用 Chromium 浏览器,可指定用户数据目录避免每次登录 browser = p.chromium.launch_persistent_context( user_data_dir="./zoom_profile", headless=False # 调试时设为False,实际运行可设为True ) page = browser.pages[0] if browser.pages else browser.new_page() page.goto("你的Zoom会议链接") # 等待会议界面加载,并点击“加入会议”等初始按钮(根据实际情况调整) page.click('button:has-text("加入会议")') # 示例1:精准点击“聊天”按钮 # 通过开发者工具找到按钮的aria-label或者data-testid属性 chat_button = page.locator('[aria-label="打开聊天面板"]').first if chat_button.is_visible(): chat_button.click() print("已打开聊天面板") # 示例2:向聊天框输入并发送消息 # 先定位聊天输入框 chat_input = page.locator('[data-testid="chat-input"]').first chat_input.fill("大家好,Zoombot已上线。") # 定位发送按钮并点击 send_button = page.locator('[data-testid="chat-send-button"]').first send_button.click() # ... 其他控制逻辑 browser.close()

实操心得

  • 选择器策略:优先使用>import vosk import pyaudio import json import queue import threading class VoiceRecognizer: def __init__(self, model_path="model/vosk-model-small-en-us-0.15"): self.model = vosk.Model(model_path) self.recognizer = vosk.KaldiRecognizer(self.model, 16000) self.audio_queue = queue.Queue() self.text_queue = queue.Queue() self.is_listening = False def _audio_callback(self, in_data, frame_count, time_info, status): """PyAudio回调函数,将音频数据放入队列""" self.audio_queue.put(bytes(in_data)) return (None, pyaudio.paContinue) def _recognition_thread(self): """识别线程,从音频队列取数据,识别后放入文本队列""" while self.is_listening: try: data = self.audio_queue.get(timeout=1) if self.recognizer.AcceptWaveform(data): result = json.loads(self.recognizer.Result()) text = result.get('text', '').strip() if text: self.text_queue.put(text) print(f"识别到语音: {text}") except queue.Empty: continue def start_listening(self): """开始监听系统音频""" self.is_listening = True p = pyaudio.PyAudio() # 关键:选择正确的输入设备索引。系统音频输出通常是一个“立体声混音”或“回路”设备。 # 在Windows声音设置中启用“立体声混音”,并在此处找到其索引。 stream = p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, input_device_index=self._find_loopback_device_index(p), # 需要自定义函数 frames_per_buffer=4000, stream_callback=self._audio_callback) threading.Thread(target=self._recognition_thread, daemon=True).start() print("语音监听已启动...") # 主线程可以继续做其他事,通过 text_queue.get() 获取识别结果 return stream def _find_loopback_device_index(self, pyaudio_instance): """查找用于捕获系统音频的回路设备索引,这是一个平台相关的复杂步骤""" # 此处需要根据操作系统枚举设备并筛选,代码较长,略。 # 简而言之:在Windows上可能需要安装VB-Audio Virtual Cable等虚拟音频驱动来创建环路。 pass

    踩坑实录

    • 最大的坑:获取系统音频。默认的麦克风输入只能捕获你说话的声音,无法捕获其他参会者的声音。解决方案有两种:
      1. 虚拟音频线(推荐):安装如 VB-Audio Virtual Cable 或 BlackHole (macOS) 的虚拟音频设备。在系统声音设置中,将“扬声器”输出到虚拟线,再将 PyAudio 的输入设备设置为这条虚拟线。这样所有系统声音都会被“导入”到我们的程序。
      2. 特定平台API:在Windows上可以使用wasapi循环捕获,但配置复杂。虚拟音频线方案更通用、稳定。
    • 模型选择:Vosk 有小(small)、中(medium)、大(large)模型。small模型速度最快,占用资源少,适合实时性要求高的场景,但准确率稍低。对于会议场景,smallmedium通常足够,因为会议语音通常比较清晰。务必下载对应的语言模型(如中文cn)。
    • 实时性与延迟:语音识别有不可避免的延迟(几百毫秒到几秒)。对于要求实时触发的动作(如听到关键词立刻回复),需要权衡。可以将规则设置为“在最近5秒的识别文本中搜索关键词”,而不是只针对最新的一句话。

    3.3 规则引擎的简易实现规则引擎是 Zoombot 的“灵魂”,它决定了机器人的行为逻辑。下面是一个极其简化的实现示例,展示其工作原理。

    import re import time from datetime import datetime class RuleEngine: def __init__(self): self.rules = [] def load_rules_from_config(self, config): """从配置文件加载规则""" for rule_def in config: self.rules.append({ 'name': rule_def['name'], 'event_type': rule_def['event'], # 'chat_message', 'speech_text', 'timer' 'condition': rule_def.get('condition'), # 一个可调用的函数或lambda表达式 'action': rule_def['action'] # 一个可调用的函数 }) def process_event(self, event): """处理一个传入的事件""" for rule in self.rules: if rule['event_type'] != event['type']: continue # 检查条件 condition_met = True if rule['condition']: try: condition_met = rule['condition'](event['data']) except Exception as e: print(f"规则 '{rule['name']}' 条件检查出错: {e}") continue # 执行动作 if condition_met: print(f"触发规则: {rule['name']}") try: rule['action'](event['data']) except Exception as e: print(f"执行规则 '{rule['name']}' 动作时出错: {e}") # 示例规则配置 rule_config = [ { 'name': '主持人问好时自动回复', 'event': 'chat_message', 'condition': lambda data: data['sender'] == '主持人' and '大家好' in data['text'], 'action': lambda data: send_chat_message("主持人好,Zoombot在线。") }, { 'name': '语音中提到“投票”时发送投票确认', 'event': 'speech_text', 'condition': lambda data: '投票' in data['text'] and '开始' in data['text'], 'action': lambda data: send_chat_message("收到,开始投票。") }, { 'name': '会议超时提醒', 'event': 'timer', 'condition': lambda data: data['current_time'] > data['scheduled_end_time'], 'action': lambda data: send_chat_message("提示:会议已超过预定结束时间。") } ] # 模拟事件 engine = RuleEngine() engine.load_rules_from_config(rule_config) # 假设收到一个聊天事件 chat_event = { 'type': 'chat_message', 'data': {'sender': '主持人', 'text': '大家好,我们准备开始'} } engine.process_event(chat_event)

    这个简易引擎足以应对大多数自动化场景。你可以通过扩展conditionaction的函数能力,来实现更复杂的逻辑,比如调用外部API获取数据后再回复。

    4. 配置、部署与安全实践

    让 Zoombot 好用且可靠,离不开细致的配置和部署考量。

    4.1 配置文件设计一个结构清晰的配置文件(如config.yaml)能让非技术用户也能定制机器人的行为。

    meeting: url: "https://zoom.us/j/1234567890" scheduled_duration_minutes: 60 automation: pre_join_actions: - action: "wait_for_element" selector: "button:has-text('加入会议')" timeout_sec: 30 - action: "click" selector: "button:has-text('加入会议')" - action: "wait" milliseconds: 2000 - action: "fill" selector: "input[name='name']" value: "我的Zoombot" rules: - name: "auto_mute_on_join" event: "page_loaded" condition: "" action: "press_keys" key_combination: "Alt+A" description: "加入会议后自动静音" - name: "reply_to_roll_call" event: "chat_message" condition: | data['sender'].lower() in ['host', '主持人', 'moderator'] and any(word in data['text'].lower() for word in ['roll call', '签到', 'attendance']) action: "send_chat" message: "到" cooldown_sec: 5 # 防止短时间内重复触发 - name: "record_action_items" event: "speech_text" condition: | # 使用正则表达式匹配类似“某某负责...在下周五前完成”的句式 import re pattern = r'([^,。]+?)(负责|跟进)(.+?)(在|于|before\s+)([^,。]+?[日前周月])' return bool(re.search(pattern, data['text'])) action: "append_to_file" file_path: "./meeting_notes/action_items.txt" format: "[{timestamp}] {matched_text}" audio: input_device: "虚拟音频线 (VB-Audio Virtual Cable)" # 或设备索引号 vosk_model_path: "./models/vosk-model-small-cn-0.22"

    4.2 本地化部署与开机自启Zoombot 应该是一个“开箱即用”的后台服务。

    • 打包:使用PyInstaller将 Python 脚本及其依赖打包成单个可执行文件(.exe或 无扩展名的可执行文件),方便分发。
    • 开机自启(Windows):可以将打包后的.exe或一个启动它的.bat脚本的快捷方式,放入%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup文件夹。
    • 资源管理:在代码中做好日志记录(logging模块),将运行日志、识别文本、触发的动作都记录到文件,方便后期排查问题。同时,要监控进程的资源占用,避免长时间运行导致内存泄漏。

    4.3 安全与伦理实践指南这是开发此类工具的重中之重,必须时刻谨记。

    1. 绝对的数据本地化:语音识别模型、会议日志、所有中间数据都必须存储在用户本地设备。配置文件里不要出现任何外部API密钥(除非是用户明确知晓并自愿配置的、用于扩展功能的合法API)。
    2. 透明告知义务:如果要在团队会议中使用 Zoombot,务必事先告知所有参会者,有一个自动化工具在协助记录或响应。可以在机器人加入会议时,自动发送一条消息说明其功能和目的,例如:“【自动助手通知】本助手仅用于自动记录会议纪要和执行预设的响应,所有处理均在本地进行。”
    3. 遵守平台规则:仔细阅读 Zoom、Teams 等平台的使用条款。大多数条款禁止未经授权的自动化、爬虫或干扰服务的行为。Zoombot 的设计初衷是辅助个人提升效率,而非攻击或滥用服务。避免高频操作(如每秒发送多条消息),模拟人类操作间隔,这是对自己账号的保护。
    4. 权限最小化:Zoombot 只需要控制浏览器和读取音频的权限。不要赋予它访问无关文件系统、网络或其他敏感资源的权限。

    5. 进阶优化与问题排查实录

    项目基本跑通后,便是漫长的优化和填坑过程。这里分享几个提升稳定性和用户体验的关键点。

    5.1 提升识别与触发的准确性

    • 语音识别降噪与上下文:本地模型在嘈杂环境下效果会下降。可以在音频输入前端使用noisereduce这样的Python库进行简单的降噪处理。此外,Vosk 识别是流式的、逐句的,缺乏上下文关联。可以维护一个最近N句的文本缓存,当进行关键词匹配时,不仅检查当前句,也检查缓存中的上下文,这能有效减少误触发和漏触发。
    • 规则条件的精细化设计:简单的关键词匹配(如“投票”)误触发率很高。可以结合多个条件:
      • 发送者过滤:只响应主持人或特定人员的发言/消息。
      • 时间窗口:例如,“仅在会议开始后的前10分钟内响应签到”。
      • 频率限制:为每条规则设置冷却时间(cooldown_sec),防止被同一事件反复触发。
      • 正则表达式:用更精确的模式匹配代替简单包含,例如匹配“请.*签到”而不是“签到”。

    5.2 应对界面变化与异常处理会议软件的UI可能会更新,导致选择器失效。这是自动化脚本最大的天敌。

    • 选择器容错:不要只依赖一个选择器。可以准备一组备选选择器,按顺序尝试。
      def safe_click(page, selectors): for selector in selectors: locator = page.locator(selector).first if locator.is_visible(): locator.click() return True print(f"错误:无法通过任何选择器找到元素。选择器列表: {selectors}") return False # 使用 safe_click(page, [ '[data-testid="chat-button"]', '[aria-label="Chat"]', 'button:has-text("Chat")' ])
    • 心跳与自愈机制:让 Zoombot 定期(如每5分钟)执行一个“健康检查”,例如尝试查找会议标题或自己的头像。如果检查失败,可能意味着页面崩溃或网络断开,可以尝试自动刷新页面并重新执行登录流程(依赖持久化上下文)。
    • 完善的日志系统:记录每一个关键步骤的成功与失败,包括截图功能。当规则触发或发生错误时,自动截取当前屏幕保存到日志目录,这是事后排查问题的黄金资料。

    5.3 常见问题速查表下表整理了我开发过程中遇到的一些典型问题及解决方案:

    问题现象可能原因排查步骤与解决方案
    Playwright 无法找到会议按钮1. 页面未加载完成。
    2. 选择器因UI更新失效。
    3. 页面处于iframe内。
    1. 增加page.wait_for_load_state('networkidle')
    2. 使用浏览器开发者工具重新检查元素,更新选择器,采用更稳定的属性(如>语音识别无任何输出
    1. 音频输入设备选择错误。
    2. 音频格式(采样率、位深)不匹配。
    3. 虚拟音频线未正确配置。
    1. 使用pyaudio列出所有设备,确认输入设备索引是否正确对应“立体声混音”或虚拟音频线。
    2. 确保pyaudio打开的流参数(rate=16000,format=pyaudio.paInt16)与Vosk模型要求一致。
    3. 在系统声音设置中,确认播放设备输出到了虚拟音频线,且录音设备中虚拟音频线已启用并设为默认。
    Zoombot 行为错乱,重复触发1. 规则条件过于宽泛。
    2. 事件被重复投放。
    3. 没有设置冷却时间。
    1. 收紧规则条件,增加发送者、关键词上下文等限制。
    2. 检查信息采集线程,确保相同内容不会因重复抓取而多次生成事件。
    3. 为每条规则添加last_triggered_time记录,实现冷却机制。
    程序运行一段时间后卡死或无响应1. 内存泄漏(如未释放浏览器资源)。
    2. 某个线程阻塞(如队列操作死锁)。
    3. 规则动作执行时间过长。
    1. 确保browser.close()被正确调用。使用try...finally块。
    2. 检查多线程代码,对队列操作使用timeout参数,避免无限等待。
    3. 将耗时的动作(如复杂的文本处理)放入单独的线程池,避免阻塞主事件循环。
    无法在后台静默运行浏览器或终端窗口总是弹出。1. Playwright 启动浏览器时使用headless=True模式(但部分网站可能检测无头模式)。
    2. 对于Windows,可以将Python脚本打包为.exe并以pythonw.exe方式运行(无控制台窗口)。
    3. 使用系统任务计划程序或nohup(Linux) 在后台启动。

    5.4 从自动化到半智能的展望目前的 Zoombot 本质上是基于规则的自动化。要让其更“智能”,可以考虑以下低成本扩展方向:

    • 意图识别:使用轻量级的本地 NLP 库(如Rasa NLUspaCy的小模型),对识别出的文本进行简单的意图分类(是“提问”、“分配任务”还是“做出决定”),从而触发更复杂的动作流。
    • 个性化记忆:为不同的联系人(主持人、某位同事)维护简单的交互历史,实现基于上下文的差异化响应。例如,如果上次会议是张三负责记录,当主持人说“这次谁来做记录?”时,Zoombot 可以自动回复“建议可以由李四来尝试?”(当然,这需要非常谨慎的规则设计)。
    • 与外部工具集成:将提取出的 Action Items 自动同步到任务管理工具(如 Todoist、Trello)或日历中。这可以通过调用这些工具的官方API(需用户授权)来实现,将自动化从会议内延伸到会议外。

    隔离的日子,是挑战,也是创造的契机。Zoombot 这个项目,从最初一个“偷懒”的想法,到一步步实现、调试、优化,最终成为一个真正能提升效率的工具,整个过程本身就是对抗无聊和重复的最佳方式。它并不完美,也无需完美,因为它解决的是我个人场景下的具体问题。如果你也受困于重复的会议操作,不妨以此文为参考,动手打造属于你自己的那个“Bot”。记住,最好的工具永远是那个最懂你需求的、你自己参与创造的工具。

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

AI工具如何真正驱动CRM升级?揭秘头部企业已验证的5步集成方法论

更多请点击: https://kaifayun.com 第一章:AI工具与CRM整合方案 将AI能力深度嵌入客户关系管理系统(CRM)正成为提升销售转化、客户服务响应与客户洞察力的关键路径。现代CRM不再仅是数据存储平台,而是需要实时理解对话…

作者头像 李华
网站建设 2026/5/31 11:24:02

GPX Studio终极指南:如何免费在线编辑GPX文件?

GPX Studio终极指南:如何免费在线编辑GPX文件? 【免费下载链接】gpxstudio.github.io The online GPX file editor 项目地址: https://gitcode.com/gh_mirrors/gp/gpxstudio.github.io 你是否曾经为处理GPS轨迹文件而烦恼?想要编辑骑行…

作者头像 李华
网站建设 2026/5/31 11:22:25

如何为Royal TSX实现完整中文界面本地化

如何为Royal TSX实现完整中文界面本地化 【免费下载链接】Royal_TSX_Chinese_Language_Pack Royal_TSX的简体中文汉化包 项目地址: https://gitcode.com/gh_mirrors/ro/Royal_TSX_Chinese_Language_Pack Royal TSX简体中文汉化包为macOS平台的Royal TSX远程连接管理工具…

作者头像 李华
网站建设 2026/5/31 11:22:10

抖音批量下载终极指南:免费快速保存无水印视频

抖音批量下载终极指南:免费快速保存无水印视频 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support. 抖音…

作者头像 李华
网站建设 2026/5/31 11:19:46

IOTA 学习笔记(三):IOTA 的技术演进路线

如果只看早期介绍,IOTA 很容易被理解成一个“基于 DAG 的无区块账本”。这个理解有一定道理,但并不完整。因为 IOTA 的技术路线经历了多次重要演进,从最初的 Tangle,到 Coordinator,再到 Coordicide、Stardust、IOTA 2…

作者头像 李华
网站建设 2026/5/31 11:18:59

如何永久免费使用IDM:简单安全的试用期重置完整指南

如何永久免费使用IDM:简单安全的试用期重置完整指南 【免费下载链接】idm-trial-reset Use IDM forever without cracking 项目地址: https://gitcode.com/gh_mirrors/id/idm-trial-reset Internet Download Manager(IDM)是广受好评的…

作者头像 李华