☰
Telegram普号隐身监控:MTProto协议实现关键词监听与人工响应
2026/9/25 7:03:30 网站建设 项目流程

简介:这是一套面向即时通讯安全研究与个人学习场景的群组关键词监听机器人源码,基于PHP实现,可对群聊与频道内容进行自动化监控,在命中预设关键词时触发提示,便于理解多账户潜伏与实时人工响应的技术链路。资源包共57个文件,约94KB,以36个php源码文件为核心,涵盖事件观察者、控制器、会话迁移与API扩展等模块;另有9个zbak备份、2个json配置、yml与docker编排、sh启动脚本及docx说明文档,整体结构清晰,便于按模块阅读与二次调试。目前已有128人学习。读者可从中获取完整的关键词监听实现思路、事件驱动架构与配置样例,适合具备一定PHP基础、希望研究消息监控机制与自动化响应流程的学习者参考,仅限学习交流,请勿用于商业用途。

1. 电报群关键词监听机器人:普号隐身监控到底怎么落地

做社群运营的同行大概率都遇到过这种场景:群里有人发了一句“tg机器人怎么接”“tg api接码使用方法”,等运营看到时已经过去两小时,用户早跑去别家问了。人工盯群不现实,用 Bot API 又有个硬伤——机器人账号进群会显示“已加入”,用户一看就知道有机器人在监听,敏感话题立刻转移。这就是“普号隐身监控”要解决的问题:用一个普通用户账号(非 Bot)挂在群里,不显示机器人标识,实时抓取消息里的关键词,命中后推送给人工客服去响应。

这套方案的核心不是“监听”本身,而是三件事的组合:普号如何稳定在线、关键词如何高效匹配、命中后如何把上下文完整交给人工。适合做私域社群运营、售后客服、线索收集的团队,也适合想研究 Telegram MTProto 协议的技术人。下面从协议选型一路讲到部署排错,都是能直接抄的配置。

2. 协议选型与账号准备:为什么 Bot API 干不了这活

2.1 Bot API 和 MTProto 的本质差别

Telegram 对外提供两套接口。Bot API 是官方封装好的 HTTP 接口,简单、稳定、有官方文档,但它的身份就是“机器人”——进群有系统提示,无法读取进群前的历史消息,也无法伪装成真人。MTProto 是 Telegram 客户端使用的底层协议,普通用户账号走的就是这条路。用 MTProto 登录一个普号,它在服务器眼里就是一个正常的手机客户端,进群不留痕,能收全量消息。

这就是“普号隐身监控”的技术底座。你要监听的是群里的自然对话,用户不会对着一个机器人账号说真话,所以必须用普号。常见做法是用 Telethon(Python)或 GramJS(Node.js)这类 MTProto 客户端库,它们把协议细节封装好了,你只需要处理登录、事件回调和消息解析。

选型上,Python 生态的 Telethon 文档最全、社区案例最多,适合快速起步;如果你整个后端是 Node.js,GramJS 更顺手。两者在关键词监听这个场景下能力对等,差别主要在异步模型和部署习惯。

2.2 普号登录与会话持久化

普号登录需要手机号 + 验证码,首次登录后必须把 session 持久化,否则每次重启都要重新验证,频繁登录还会触发风控。Telethon 的StringSession或SQLiteSession都能做到,生产环境建议用文件型 session 并做好备份。

# 首次登录并导出 session 字符串,之后复用即可 from telethon import TelegramClient from telethon.sessions import StringSession API_ID = 1234567 # 从 my.telegram.org 申请 API_HASH = "your_api_hash" with TelegramClient(StringSession(), API_ID, API_HASH) as client: # 首次运行会要求输入手机号和验证码 print(client.session.save()) # 把输出保存到环境变量,后续直接复用

这段代码的逻辑是:用空的 StringSession 启动客户端,Telethon 会走完整的登录流程,登录成功后session.save()返回一个可复用的字符串。参数说明:API_ID和API_HASH是应用级凭证,一个应用可以登录多个账号;session 字符串等同于账号的登录态,泄露等于账号被盗,必须放在环境变量或密钥管理服务里,不要硬编码进仓库。

注意:一个 API_ID 下登录的账号数量过多会被限制,团队规模大时建议按账号分组申请多个应用凭证。

2.3 账号养号与风控边界

新注册的普号直接拉进十几个群开始监听,大概率几天内就被限制甚至封禁。血泪经验是:新号先正常使用一到两周,加几个群、发几条消息、有正常的在线时长,再逐步接入监听。单个账号建议同时监听的群不超过 20 个,消息频率高的群要单独评估。如果业务量大,正确做法是横向扩账号,而不是让一个号硬扛。

3. 关键词监听的核心实现:从收消息到命中判定

3.1 事件监听与消息过滤

Telethon 的事件系统可以监听指定群的新消息。核心是把NewMessage事件绑定到处理函数,在处理函数里做关键词匹配。要注意区分群消息、频道消息和私聊,event.is_group和event.is_channel能帮你过滤。

from telethon import TelegramClient, events from telethon.sessions import StringSession import os client = TelegramClient( StringSession(os.environ["TG_SESSION"]), int(os.environ["TG_API_ID"]), os.environ["TG_API_HASH"] ) # 监听所有群组的新消息 @client.on(events.NewMessage(incoming=True)) async def handler(event): if not event.is_group: return text = event.raw_text or "" if not text: return # 交给关键词引擎判定 hit = match_keywords(text) if hit: await push_to_agent(event, hit) client.start() client.run_until_disconnected()

逻辑说明:incoming=True只处理收到的消息,避免自己发的消息触发;event.is_group过滤掉私聊和频道;event.raw_text拿到纯文本,图片、语音等非文本消息这里为空,需要单独处理。参数上,events.NewMessage还支持chats=参数限定只监听特定群,群多的时候用这个减少无效回调。

3.2 关键词匹配引擎:别用 in 硬匹配

最朴素的写法是if keyword in text,但实际群里的话术千变万化:“tg机器人”和“TG 机器人”“电报机器人”都得命中,还有全角半角、大小写、中间插表情的情况。直接in匹配会大量漏判。

常见做法是三层匹配:先做文本归一化(去空格、转小写、全角转半角),再用正则做模糊匹配,最后对高价值词做同义词扩展。下面是一个可用的匹配函数:

import re import unicodedata def normalize(text: str) -> str: # 全角转半角 + 转小写 + 去多余空白 text = unicodedata.normalize("NFKC", text) text = text.lower() text = re.sub(r"\s+", "", text) return text # 关键词配置:主词 + 同义词列表 KEYWORD_MAP = { "机器人": ["机器人", "bot", "机械人"], "接码": ["接码", "验证码", "api接码"], "客服": ["客服", "售后", "人工"], } def match_keywords(text: str): norm = normalize(text) hits = [] for main_word, synonyms in KEYWORD_MAP.items(): for syn in synonyms: if normalize(syn) in norm: hits.append(main_word) break return hits

逻辑说明:unicodedata.normalize("NFKC", text)把全角字符统一成半角,这一步能解决“tg”和“tg”匹配不上的问题;去空白是为了应对“t g 机 器 人”这种故意拆字的规避;同义词表让“bot”和“机器人”归到同一个业务标签。参数上,KEYWORD_MAP的 key 是业务标签,value 是同义词,命中后返回标签列表,方便后续按标签路由到不同的人工客服。

注意:正则匹配高并发时是性能瓶颈,群消息量大时建议把关键词预编译成re.Pattern对象,或者用 Aho-Corasick 算法做多模式匹配,几千个关键词也能做到毫秒级。

3.3 命中后的上下文打包

只推一句“有人提到机器人”给客服是没用的,客服需要知道谁在哪个群、说了什么、前后文是什么。所以命中后要抓取消息上下文,通常取命中消息的前后各 3 到 5 条,连同发送者信息、群名称、消息链接一起打包。

async def push_to_agent(event, hits): # 抓取上下文:命中消息前后各 3 条 context_msgs = [] async for msg in client.iter_messages( event.chat_id, offset_id=event.message.id + 3, reverse=True, limit=7 ): context_msgs.append(f"{msg.sender_id}: {msg.raw_text}") payload = { "group": event.chat.title, "group_id": event.chat_id, "sender": event.sender_id, "hits": hits, "text": event.raw_text, "context": context_msgs, "msg_link": f"https://t.me/c/{event.chat_id}/{event.message.id}", } await send_to_agent_system(payload)

逻辑说明:iter_messages用offset_id定位到命中消息附近,reverse=True保证按时间正序排列,limit=7取前后各 3 条加命中本身。msg_link用t.me/c/格式生成群内消息直达链接,客服点一下就能跳到原消息。参数上,上下文条数不是越多越好,7 条是实践下来信息量和可读性的平衡点,太多客服反而不看。

4. 实时人工响应系统:从命中到客服接单

4.1 推送通道与消息队列

命中消息不能直接塞给客服,中间要有一个队列做缓冲和分发。常见架构是:监听服务把命中事件丢进 Redis 队列,客服端从队列消费。这样做的好处是监听和响应解耦,客服下班时消息不会丢,第二天还能看到积压。

import redis import json r = redis.Redis(host="localhost", port=6379, db=0) async def send_to_agent_system(payload): # 按业务标签路由到不同队列 for tag in payload["hits"]: queue_name = f"agent_queue:{tag}" r.lpush(queue_name, json.dumps(payload, ensure_ascii=False)) # 同时写一份全量日志,便于回溯 r.lpush("agent_queue:all", json.dumps(payload, ensure_ascii=False))

逻辑说明:按标签路由让不同业务线的客服只看自己相关的消息,比如“接码”标签的队列给技术支持,“客服”标签的给售后。ensure_ascii=False保证中文正常存储。参数上,Redis 的lpush是左进右出,客服端用brpop阻塞消费,天然支持多客服竞争消费,不会重复处理。

4.2 客服端接单与状态回写

客服端可以是一个简单的 Web 面板,也可以是 Telegram 上的一个内部群。轻量做法是建一个内部客服群,监听服务把命中消息格式化后发到群里,客服直接在群里回复,回复内容通过普号转发到原群。这样客服不用切换工具,响应速度最快。

# 客服在内部群回复后,转发到原群 @client.on(events.NewMessage(chats=AGENT_GROUP_ID)) async def agent_reply(event): reply_to = event.message.reply_to_msg_id if not reply_to: return # 从缓存里取出原消息的映射关系 origin = r.get(f"msg_map:{reply_to}") if not origin: return origin = json.loads(origin) await client.send_message( origin["group_id"], event.raw_text, reply_to=origin["msg_id"] )

逻辑说明:客服在内部群回复某条推送时,reply_to_msg_id指向推送消息,通过msg_map找到对应的原群和原消息 ID,再用普号把回复发到原群并引用原消息。参数上,msg_map的 key 是内部群推送消息的 ID,value 存原群 ID 和原消息 ID,这个映射要在推送时写入 Redis 并设置合理的过期时间。

4.3 响应时效与人工介入的边界

实时人工响应不是所有命中都要人工处理。高频低价值的词(比如“客服”这种日常词)可以设置阈值,比如同一用户短时间内多次命中才推送,避免客服被淹没。高价值词(比如“接码”“api”)则要即时推送。这个阈值配置建议做成可热更新的,运营随时调整,不用重启服务。

5. 避坑与排查:普号监听最容易翻车的五个点

5.1 账号突然掉线,session 失效

现象:服务运行几天后突然收不到消息,日志显示AuthKeyUnregisteredError。原因:账号在别处登录、被官方风控、或者 session 文件损坏。解决:做好 session 备份,掉线时用备份恢复;同时监控在线状态,掉线立即告警。不要频繁重新登录,会加重风控。

5.2 消息收不全,漏消息严重

现象:明明群里有人发了关键词,但没触发推送。原因:一是账号被限流,消息延迟;二是NewMessage事件在断线重连后丢失了断线期间的消息。解决:启动时用iter_messages补拉最近一段时间的消息做去重比对;账号分散到多个,降低单号负载。

5.3 关键词误判,客服被垃圾消息淹没

现象:客服抱怨推送太多,大部分是无关消息。原因:关键词太宽泛,比如“客服”这个词在群里被大量正常提及。解决:引入白名单和黑名单,对发送者做过滤;设置命中频率阈值;把宽泛词降级为“仅记录不推送”。

5.4 回复发不出去,提示权限不足

现象:客服回复后,普号发到原群失败。原因:普号被群管理员禁言、被踢出群、或者群设置了仅管理员可发言。解决:监控普号在群内的状态,被禁言立即告警;重要群准备备用号。

5.5 上下文抓取报错,消息 ID 越界

现象:iter_messages在群消息很少时抛异常。原因:offset_id加上了超出范围的偏移量。解决:抓取前先判断消息 ID 范围,或者用 try/except 包住,失败时降级为只推命中消息本身。

6. 进阶:把监听系统做成可运营的资产

跑通基础版之后,真正拉开差距的是数据沉淀。我一般会做三件事:第一,把所有命中事件落库,字段包括群、发送者、关键词、时间、是否已响应,这样能算出每个群的活跃度和转化率;第二,给关键词做效果分析,哪些词命中多但转化低,就该调整;第三,把客服的响应话术沉淀成模板,命中后自动推荐话术,客服一键发送。

验证系统是否可靠,有个简单方法:建一个测试群,用另一个号按预设脚本发消息,看从发送到客服收到推送的端到端延迟。正常应该在 2 秒以内,超过 5 秒就要查网络和队列积压。下面是一个延迟打点的写法:

import time @client.on(events.NewMessage(incoming=True)) async def handler(event): recv_ts = time.time() # ... 匹配逻辑 ... if hit: latency = time.time() - recv_ts r.lpush("latency_log", f"{event.chat_id}:{latency:.3f}")

参数上,latency只统计处理耗时,不含网络传输,端到端延迟要加上推送和客服端消费的时间。建议把这两个指标分开监控,出问题时能快速定位是监听慢还是推送慢。

最后说个我踩过的坑:一开始为了省事,所有群共用一个关键词表,结果做电商的群和做技术的群互相干扰,推送噪音极大。后来改成按群分组配置关键词,运营自己维护,监听服务只负责执行,效率立刻上来了。这套系统的价值不在于技术多复杂,而在于把“人盯群”变成“系统筛、人来答”,让客服的每一分钟都花在真正有意向的用户身上。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询