ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nonebot与TextRank实战:构建QQ群聊每日总结机器人

Nonebot与TextRank实战:构建QQ群聊每日总结机器人 简介面向 Python 开发者和 QQ 群管理员的机器人项目基于 Nonebot 框架利用 TextRank 等机器学习算法对每日聊天记录自动生成总结帮助提升群互动体验、实现聊天信息的高效提炼适用于社群运营与信息管理场景。压缩包共 28 个文件、2.05MB核心为 18 个 Python 源码文件涵盖 ML 训练、文本预处理、网络请求与 JSON 转换等模块另有 requirements.txt 依赖清单、TextRank 算法 PDF 说明、配置文件及多张演示截图目录结构清晰、便于按需查阅。目前已有 275 人浏览学习。读者可获取完整可运行的 Nonebot 机器人代码配合 MyTextRankDemo.py 示例与 TextRank 论文文档系统理解聊天数据清洗、关键词提取、情感分析与总结生成的全流程同时项目内含 setup.py 与 README 说明适合有一定 Python 基础、想将机器学习落地到实际聊天场景的开发者快速上手与二次开发。1. 基于 Nonebot 的 QQ 群机器人一张能跑的机器学习每日总结实现如果你管理着一个活跃的 QQ 群每天上百条聊天记录里既有技术讨论、也有插科打诨想靠人工盯完再整理一份总结基本不现实。这份资源给的不是「演示用的玩具机器人」而是一套基于 Nonebot 框架、真正能把当天的群聊记录抓下来、跑算法、吐出一段像样总结的完整工程。核心亮点在它对 TextRank 算法的落地——不是调库一行搞定而是自己实现了分词、去停用词、建图、迭代收敛的全过程附带了 MyTextRankDemo.py 作为独立演示入口你甚至可以不接 QQ 先跑通算法再谈集成。适合两类人一是刚入门 Nonebot 想找个能跑的中型项目做参考的开发者二是对 NLP 摘要算法感兴趣、想看看 TextRank 在真实中文场景下到底有多少坑的算法学习者。2. 拆项目结构先读懂每个文件在替谁干活2.1 资源包里的核心模块划分与职责边界解开压缩包后首先需要分清「框架代码」和「算法代码」两条线。主入口 bot.py 是 Nonebot 标准的启动文件负责创建机器人实例、加载插件和配置config.py 集中管理机器人运行所需的配置项包括 QQ 号、API 地址、数据库路径等。setup.py 是 Python 包安装入口requirements.txt 则锁定了运行所需的依赖版本。这一层的设计很典型Nonebot 本身是插件化的所以主体代码都在各插件模块里而这份资源的特色是并未把算法逻辑硬塞进 Nonebot 插件里而是独立成了一套 ml 工具集。ml 目录下的 Utils.py 是工具方法的大本营IOUtils、JsonUtils、ConversionUtils、NetUtils 各自负责 IO 操作、JSON 解析、格式转换和网络请求。你可能会好奇为什么一个群聊总结机器人需要网络请求模块——因为获取聊天记录通常不是直接读本地数据库而是通过 QQ 机器人框架提供的 HTTP API 拉取NetUtils 干的就是这件事。TextRank 算法的核心实现在 MyTextRankDemo.py它不依赖 Nonebot 环境直接运行即可看到摘要结果这是设计和调试算法的关键入口。2.2 选型逻辑为什么 Nonebot 2 TextRank而不是随便调个大模型接口选 Nonebot 做主框架的理由很实在它对 QQ 协议的支持是通过适配器完成的最常见的做法是搭配 mirai 或者 go-cqhttp 这类协议端使用这份资源包里的 FG-mirai 文件证实了作者的实践路径——通过 mirai 接入 QQ而不是走 WebSocket 直连。Nonebot 2 是异步框架事件驱动模型非常适合聊天机器人这种高并发、短请求的场景。调试时你可以在本地跑 Nonebot 客户端连接远程服务器也可以全部本地部署两种模式切换在 config.py 里就能完成。TextRank 算法的选择则体现了作者的工程判断做每日群聊总结本质上是一种抽取式摘要任务——从当天聊天记录里选出信息量最大的几句话。抽取式摘要不需要生成新文本所以不依赖大模型的生成能力一台普通 PC 甚至树莓派就能跑。TextRank 的思想是把每个句子看作图中的一个节点句子之间的相似度作为边的权重然后通过 PageRank 式的迭代计算每个句子的权重权重最高的句子就是摘要候选。这种方式虽然不如深度学习模型「聪明」但胜在可控没有黑匣子每个句子的分数都能追溯到计算过程这对调试和生产维护极其重要。2.3 核心调用链从消息事件到每日总结的完整数据流整个系统的工作流程可以抽象成一条流水线Nonebot 框架监听到群消息事件 → 插件层调用 NetUtils 从 mirai HTTP API 拉取当天的聊天记录 → JsonUtils 解析返回的 JSON 数据 → ConversionUtils 将原始消息清洗成纯文本列表 → TextRank 算法对文本列表执行分词、去停用词、建图、迭代 → 输出摘要句子 → 机器人将摘要发回群里。注意顺序清洗和截断发生在算法之前如果聊天记录过长算法性能会急剧下降这一步在代码里通常体现为对输入文本的长度截断。文件清单里的 TextRank-algorithm.pdf 是算法原理解读文档建议在跑代码之前先通读一遍尤其是其中关于窗口大小和阻尼系数的推导部分。assets 目录里的两张图片是算法处理过程中生成的中间图形一张是句子关系图一张是权重迭代曲线图可以直接用来对照你的输出结果是否正确。这是这份资源最具价值的地方——它把算法从「黑匣子」变成了「可视化过程」调试起来有据可依。3. TextRank 实现细节从配置参数到收敛条件逐一拆解3.1 分词与停用词策略中文场景的第一个性能瓶颈TextRank 处理中文语料的第一步是分词。没有分词后续的句子相似度计算就是空谈。很多入门教程直接让你用 jieba 默认模式但这份资源在 MyTextRankDemo.py 里对分词做了定制——加载了额外的停用词表并且在代码里明确标注了核心参数。以下是我在跑通代码后提炼出的关键配置段落# MyTextRankDemo.py 中与分词和停用词相关的核心配置 stop_words set() # 加载自定义停用词表这里按行读取每行一个词 for line in open(cn_acmsmu.txt, r, encodingutf-8): stop_words.add(line.strip()) # 以及下面的分词函数final_cutcn_acmsmu.txt 这个文件是关键。它不是常规的中文停用词表而是一份哈工大停用词表的扩展版本。exp_stop_words 存在的原因很实际默认的 jieba 分词对聊天场景的噪声处理不够像「哈哈哈」「666」「表情」这类聊天高频词如果不加进停用词会被 TextRank 当作重要节点参与迭代严重拉低摘要质量。我一般会在这个表里继续追加当前群的专用词汇——比如群名本身、群友常用的口头禅、特定梗词——每次追加后重新跑一遍摘要效果对比比单纯堆算法改进有效得多。3.2 句子相似度计算TextRank 的图权重从哪来TextRank 的图结构定义是每个句子是一个节点句子之间的相似度通过词重叠计算。具体公式是「共现词数量 / 两个句子长度的对数之和」这种做法在短文本场景下比标准的余弦相似度更稳定——群聊的单条消息普遍很短用户消息往往只有几个字到几十个字直接拼接成文档会让句子间长度差异过大相似度计算容易失真。关键实现如下def calc_sentence_similarity(sentence1, sentence2): 两个句子的 TextRank 相似度计算。 这里使用词重叠指标代码直接来自资源包 我补了注释方便理解每个步骤的含义。 # 把字符串转成词集合以便做交运算 words1 set(sentence1) words2 set(sentence2) # 交集元素个数即为共现词数量 overlap len(words1 words2) # 对数求和做分母刻意压低长句的影响 denominator math.log(len(words1) len(words2)) if denominator 0: return 0 return overlap / denominator重点在分母的设计math.log(len(words1) len(words2)) 做的效果是「惩罚长句」。群聊中经常有刷屏的段子和小作文如果不用对数压缩这类冗长文本会因为自身词汇多而与很多句子产生表面关联这会让 TextRank 误以为它们是话题中心。我在接入生产环境后特意验证过去掉这个对数项摘要结果会被两三条长段子带偏真实话题反而排不上去。3.3 窗口大小与迭代收敛什么参数值得调、什么参数别乱动TextRank 在句子级别构建图时通常不设置窗口——也就是所有句子之间都可能建立连接因为我们要看的是全局话题中心。但在词级别构建图时窗口大小决定了共现关系的范围。这份资源的 TextRank 代码里词级共现窗口默认是 5这个值的调整对结果影响非常大def create_word_graph(words): 根据词共现窗口构建图结构。 window_size 决定了两个词被视作关联的半径。 graph {} window_size 5 # 默认窗口大小 for i, word in enumerate(words): # 获取当前词之后 window_size 范围内的相邻词 for j in range(1, window_size 1): if i j len(words): neighbor words[i j] # 在图上为 co-occurrence 计数 graph[word][neighbor] graph[word].get(neighbor, 0) 1 return graph窗口值设置有一个基本原则如果群聊记录句子普遍较短平均 10 个词以内窗口 5 够用如果聊天话题跳转极快建议下调到 3否则两分钟内谈论完全不同话题的词会被强行拉进同一共现图里最后关键词提取会变成「大杂烩」。迭代收敛条件方面代码里设置的是最大迭代 100 次、收敛阈值 0.001这两项属于标准值一般不需要动。需要警惕的是如果语料很短比如总共就几十条消息TextRank 在稀疏图上迭代很快达到局部收敛这时候不要被「收敛快」误导问题在输入数据量不足而不是参数调得好。4. 把机器人跑起来环境配置、依赖安装与第一轮调试4.1 从零搭建运行环境Python 版本与依赖冲突处理这份资源的运行环境要求不复杂Python 3.8 以上即可。requirements.txt 里锁定了 Nonebot2、jieba、requests、numpy、scikit-learn 等核心依赖。最容易翻车的是 Nonebot2 的安装方式——命令行工具是 nb-cli创建工程项目时可以用 nb create 快速生成但你如果直接 pip install nonebot2需要手动配置 pyproject.toml版本兼容性问题会接踵而至。我建议的操作顺序是# 创建虚拟环境避免污染系统 Python python3 -m venv qqbot_env source qqbot_env/bin/activate # 先安装命令行工具和框架主体 pip install nb-cli nonebot2[fastapi] # 按 requirements.txt 安装项目依赖 pip install -r requirements.txt # 验证 nonebot 是否能够正常加载插件 nb --version python -c from nonebot.adapters.qq import Adapter; print(QQ Adapter OK)requirements.txt 里的 scikit-learn 实际上在 TextRank 实现中用得不多因为作者的算法是纯手写没有调用 sklearn 的文本特征提取模块。但保留这个依赖是有道理的——如果后续你想对比 TextRank 和 TF-IDF 的摘要效果直接 import TfidfVectorizer 即可省得重新装环境。安装时如果遇到 Nonebot 自带 pydantic 版本与你本机其他项目冲突别硬刚在虚拟环境里单独轮询版本把 pydantic 降到 1.x 是更稳妥的操作。4.2 配置 config.py四大参数组逐一解析config.py 是整个接入过程的枢纽配置分为四个参数组Nonebot 自身配置、mirai 连接配置、日志配置、算法相关配置。以下是按照实际部署场景整理出的最核心部分# config.py 的核心配置项解析 from pydantic import BaseSettings class Config(BaseSettings): # Nonebot 框架配置 host: str 127.0.0.1 # Nonebot 服务监听地址 port: int 8080 # Nonebot 服务监听端口 superusers: set[str] {你的QQ号} # 超级用户拥有管理权限 # mirai HTTP API 配置 mirai_host: str 127.0.0.1 mirai_port: int 8085 # 注意不能和 Nonebot 端口冲突 mirai_api_key: str your-verify-key # mirai 的 verify key qq_number: int 你的机器人QQ号 # 机器人自己的 QQ 号 # 日志配置 log_level: str INFO # 调试时可改为 DEBUG关键说明host 和 port 是 Nonebot 服务自己暴露的地址mirai_host 和 mirai_port 则是 Nonebot 主动去连接 mirai 的地址这组关系很多人第一次接触会混淆。mirai_port 默认 8085 是基于 mirai-http-api 插件的默认配置如果你在 mirai 侧改过端口这里必须同步修改。superusers 里的 QQ 号是运营者身份标记用于判断是否执行管理命令——注意判断命令时用字符串比对如果与 mirai 返回的消息类型不一致会导致永远匹配不上这是最常见的翻车点。4.3 启动流程与连通性测试先验证协议再验证算法完整启动链路是先启动 mirai 协议端 → 再启动 Nonebot 服务 → 在 QQ 群里发一条测试消息 → 观察日志。如果在第一步就卡住问题大概率不在你的代码里——mirai 需要单独下载启动器并且要配置好 device.json 和 bot 的 QQ 号。我踩过的一个大坑是 mirai 登录被风控表现为启动时报「当前设备环境异常」解决方案是更换 mirai 的协议配置或接入其他协议端。Nonebot 启动成功后建议不要直接测总结功能先做一轮最小化连通性测试# 启动 Nonebot在项目根目录执行 nb run # 另开一个终端手动调用 mirai HTTP API 验证消息发送通道 curl -X POST http://127.0.0.1:8085/sendGroupMessage \ -H Content-Type: application/json \ -d {\target\: 你的测试群号, \messageChain\: [{\type\: \Plain\, \text\: \连通性测试\}]}如果 curl 返回成功且群里收到消息说明 Nonebot 到 mirai 的通道是通的。这时候启动流程里最耗时的排障阶段已经过去接下来只需要在 Nonebot 侧监听群消息事件把事件类型从 GroupMessageEvent 映射到总结功能入口即可。我建议先把 8085 端口暴露到本机调试调试完毕再决定是否用内网穿透方式实现远程部署——远程部署会引入网络超时和重连机制问题不属于这份资源的默认路径。5. 避坑手册运行这个项目最常见的六个坑及排查方法5.1 编码与乱码问题控制台输出正常群里发出来全是问号现象本机调试时控制台输出中文正常但机器人发到 QQ 群里的消息全部变成「??」或乱码。原因mirai HTTP API 默认按 UTF-8 解码但发送时部分文本经过 JSON 序列化后编码信息丢失尤其是表情符号、特殊标点和 Emoji 混合的场景。解决在发送消息前强制对文本做一遍 Unicode 规范化处理方式是在 NetUtils.py 的发送函数里加一层编码转换校验确认传入的字符串是 Unicode 对象而非 bytes 类型必要时用str.encode(utf-8)再解码还原以此切断乱码源头。最直接的血泪教训是不要在组装消息时手动拼接字符串片段而是构建消息链列表让每个 plain 段独立编码。5.2 算法不收敛或收敛过慢迭代次数达到上限但权重仍未稳定现象跑 TextRank 时迭代 100 次仍没达到收敛阈值日志打印的权重变化一直波动。原因图结构过于稀疏或过于稠密。过于稠密通常由停用词没过滤干净引发——高频噪声词连接了大量节点导致权重无法稳定。解决先在 cn_acmsmu.txt 里追加这批高频噪声词然后重新加载停用词表。如果仍然波动把窗口值从 5 降到 3降低图中边的总数同时把收敛阈值从 0.001 放宽到 0.005观察权重是否趋于稳定。这一套组合操作在调试中能解决九成以上的迭代问题。5.3 分词结果被自定义词典干扰专业术语被切得面目全非现象群聊中大量讨论「Nonebot」技术点分词结果显示为「None」「bot」导致摘要关键词完全跑偏。原因jieba 默认词典不识别 Nonebot 这类拼接词切分成了独立成分。解决在 jieba 初始化时添加自定义词典把专有名词一次性加入import jieba # 加载自定义词典每个词一行写在 user_dict.txt 中 jieba.load_userdict(user_dict.txt) # 以下为 user_dict.txt 的格式供快速理解 # Nonebot 5 n # TextRank 5 n # QQ群 5 n5.4 拉取聊天记录时时间窗口错位总结的是昨天的数据现象机器人显示「今日总结」但内容里全是前一天晚上的聊天内容。原因每日总结的触发时间点用的是 UTC 时间没有转换到中国时区UTC8导致名义上的「今天 0 点」实际是前一天下午 4 点。解决在定时任务触发时显式指定时区用datetime.now(timezone(timedelta(hours8)))作为时间基准同时拉取聊天记录时把截止时间设置为触发器前 1 分钟避免边界消息遗漏。5.5 依赖版本冲突Nonebot 2 与 Pydantic V2 的兼容性现象安装完依赖启动后报错 type object Config has no attribute parse_obj。原因pydantic 2.x 移除了部分旧版 APINonebot 2 早期的适配器代码仍然依赖 pydantic 1.x 的行为。解决降级 pydantic 到 1.10.x安装命令为pip install pydantic2同时检查 fastapi 版本与 pydantic 是否配套。避免升级依赖时一次全量更新——Nonebot 生态里每个适配器对 pydantic 版本的要求差异极大锁定版本号是保险做法。5.6 群消息过多导致内存飙升千人群的聊天记录直接吃满内存现象群成员活跃时段拉取的一小时聊天记录让程序内存占用突破 2GB进程被系统杀死。原因NetUtils 一次性拉取全部消息且 ConversionUtils 未对原始消息做去重和过滤无效消息全量进入了算法。解决在拉取前增加分页限制每次拉取不超过 200 条再配合「去重 去命令消息 去链接」的预过滤逻辑。TextRank 本身对输入长度敏感超长输入不仅慢摘要质量也会退化——因为节点的排序分数会被大量低信息量句子稀释。6. 进阶技巧用调试模式跑通每日总结的端到端流程从命令行直接运行 MyTextRankDemo.py 是验证算法质量最快的方式。你要做的是把当日聊天记录导出成本地文件然后用该文件作为输入执行算法。执行后观察三件事摘要句子是否覆盖当天核心话题、是否存在重复信息的堆砌、以及运行耗时是否在可接受范围内。# 导出一段真实聊天记录到 test_chat.txt # 每行代表一条消息格式为“用户名消息内容” # 运行 TextRank 演示程序入口默认读取该文件并输出摘要 python MyTextRankDemo.py test_chat.txt # 如果希望直接观察中间结果可在 my_text_rank() 函数内添加断点 # 重点观察句子的候选权重大小与最终排序之间的对应关系测试用的语料不要人工编造用一个活跃度正常的群真实导出即可。注意群聊消息会包含大量回复叫、消息撤回、系统通知等噪声清理时需要过滤掉具体实现是检查文本是否以「」开头、是否包含「撤回了一条消息」等特征串这类规则写好后可以复用到生产环境的预过滤环节。同时观察跑完后输出的 JSON 结果——文本内容是否完整、句子是否按权重降序排列你甚至可以把输出重定向到文件里用 diff 工具对比两次修改的算法效果差异。建议做一个简单回归脚本把 3 到 5 天的历史聊天记录作为固定测试集每次修改算法参数后跑一遍对比摘要结果的变化。这套「以历史数据为基准」的做法让你从「调参靠运气」走向「调参可验证」每次改动都有客观依据。从那以后我每次调摘要相关逻辑都会强制走一遍这个回归流程——先跑历史数据再上生产环境两者结果一致才敢切流量。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取
返回列表