|
| 1 | ++++ |
| 2 | +title = "基于贝叶斯算法的Telegram广告拦截机器人(二):上线半月的故障、挑战与优化之路" |
| 3 | +author = ["Ramsay Leung"] |
| 4 | +date = 2025-09-13T14:28:00-07:00 |
| 5 | +lastmod = 2025-09-14T10:38:03-07:00 |
| 6 | +tags = ["telegram", "design", "programming", "rails", "rust"] |
| 7 | +categories = ["telegram"] |
| 8 | +draft = false |
| 9 | +toc = true |
| 10 | +showQuote = true |
| 11 | ++++ |
| 12 | + |
| 13 | +## <span class="section-num">1</span> 引言 {#引言} |
| 14 | + |
| 15 | +半个月前,我发布了一个基于贝叶斯算法的Telegram广告拦截机器人 `@BayesSpamSniperBot` (<https://t.me/BayesSpamSniperBot>) |
| 16 | + |
| 17 | +项目地址:<https://github.com/ramsayleung/bayes_spam_sniper> |
| 18 | + |
| 19 | +系列文章: |
| 20 | + |
| 21 | +- [基于贝叶斯算法的Telegram广告拦截机器人(一):从问题到产品]({{< relref "一个自学习的Telegram广告拦截机器人" >}}) |
| 22 | + |
| 23 | +尽管项目代码开源,但我始终以产品思维运营它。上线半个月以来,经历了故障、用户反馈与持续优化,现将这段经历分享出来。 |
| 24 | + |
| 25 | + |
| 26 | +## <span class="section-num">2</span> 上线即故障 {#上线即故障} |
| 27 | + |
| 28 | +没想到我的产品的第一个线上故障来得这么快,发布的时候直接不可用,把正常消息都给删了,用户在各种途径都向我反馈: |
| 29 | + |
| 30 | +{{< figure src="/ox-hugo/delete_all_message_1.jpg" >}} |
| 31 | + |
| 32 | +{{< figure src="/ox-hugo/delete_all_message_2.jpg" >}} |
| 33 | + |
| 34 | +故障的原因是因为我当时一直在收集垃圾广告的数据,太专注于垃圾广告数据,而忽略了收集的正常数据, |
| 35 | +导致垃圾广告数据过多,消息都被认为是垃圾广告,被误删了。 |
| 36 | + |
| 37 | +通过补充大量正常消息数据,重新平衡训练集,模型逐渐恢复正常识别能力。 |
| 38 | + |
| 39 | + |
| 40 | +## <span class="section-num">3</span> 挑战 {#挑战} |
| 41 | + |
| 42 | + |
| 43 | +### <span class="section-num">3.1</span> 邮件与即时消息的差异 {#邮件与即时消息的差异} |
| 44 | + |
| 45 | +我在[《基于贝叶斯算法的Telegram广告拦截机器人(一):从问题到产品》]({{< relref "一个自学习的Telegram广告拦截机器人" >}})里面提到过: |
| 46 | + |
| 47 | +> 常见的 Telegram 广告机器人是大多是基于关键字的,通过匹配关键字进行文本拦截,非常容易被发垃圾广告的人绕过。 |
| 48 | +> |
| 49 | +> 这不禁让我想起了保罗.格雷厄姆在《黑客与画家》一书在2002年介绍的情况: |
| 50 | +> |
| 51 | +> 当时电子邮件兴起,也有非常多的垃圾邮件,常见的垃圾广告拦截方式是关键字匹配+邮件地址黑名单,但是既低效也容易被绕过。 |
| 52 | +> |
| 53 | +> 保罗.格雷厄姆就创造性地使用贝叶斯算法(Bayes Theorem)实现了一个广告拦截器, 效果竟然出奇地好。 |
| 54 | +
|
| 55 | +但产品上线之后,我发现聊天软件消息和Email虽然都是文字,还是有很大差别的: |
| 56 | + |
| 57 | +Email 大多时候都是长文的,内容较长,并且大多情况,一封邮件上下文本身也是完整,就有较多的内容,较高的准确度来判断是否是广告。 |
| 58 | + |
| 59 | +而 Telegram, 微信这类的即时聊天软件,聊天消息大多都不长,可能把内容分成多条消息来发,就没有完整的上下文,比如: |
| 60 | + |
| 61 | +> 换U |
| 62 | +
|
| 63 | +<!--quoteend--> |
| 64 | + |
| 65 | +> 找我 |
| 66 | +
|
| 67 | +单条消息很较难准确判断是否是广告,所以对即时消息做广告拦截本身就更难, 「短文本+无上下文」是NLP中的经典难题,也是本项目最大的技术挑战。 |
| 68 | + |
| 69 | + |
| 70 | +### <span class="section-num">3.2</span> 漏删与误删 {#漏删与误删} |
| 71 | + |
| 72 | +漏删广告与误删消息是相互矛盾的结果 |
| 73 | + |
| 74 | +因为如果想要提高拦截度,自然就需要把置信度降低,把疑似是广告的消息都拦截掉,但是因为拦截强度的上升,又会把正常消息误删。 |
| 75 | + |
| 76 | +如果想要不误删消息,那么难免需要增加拦截的阈值,只有超过一定的置信阈值才拦截,这样准确度增加了,难免就会存在漏删广告的可能性。 |
| 77 | + |
| 78 | +那么是否可以既准确拦截广告,又避免误删正常消息呢? |
| 79 | + |
| 80 | +在即时消息上下文不齐整,消息短而密集的特性下,可以基本是不可能的。 |
| 81 | + |
| 82 | +而用户对消息被误删,以及垃圾广告被漏删的容忍度也是不同的,漏删广告,群成员可以举报或管理员删,但是误删就没法恢复. |
| 83 | + |
| 84 | +所以我只能是提高置信度,把阈值设置成95%, 也就是说只有模型认为超过95%的概率这个是广告,才能把这条消息删掉,虽然会增加漏删广告的概率,但是起码能保证不误删正常消息。 |
| 85 | + |
| 86 | + |
| 87 | +## <span class="section-num">4</span> 优化之路 {#优化之路} |
| 88 | + |
| 89 | + |
| 90 | +### <span class="section-num">4.1</span> 自动删除消息 {#自动删除消息} |
| 91 | + |
| 92 | +产品上线之后,很快就有用户来试用了,然后有用户就提了一个非常好的优化建议。 |
| 93 | + |
| 94 | +{{< figure src="/ox-hugo/detect_spam_and_ban_user.jpg" >}} |
| 95 | + |
| 96 | +这个警告的消息不会自动删除,如果有很多人在群里发广告,那么群里就会有一堆这样的消息,也算是对群消息的污染。 |
| 97 | + |
| 98 | +所以用户建议: |
| 99 | + |
| 100 | +> 可以发这个提醒,但在几分钟后也把这个提醒消息删除掉 |
| 101 | +
|
| 102 | +我觉得这是个非常好的优化体验,因为就把这个功能给加上了,提醒消息本身会在5分钟后自动删除。 |
| 103 | + |
| 104 | +{{< figure src="/ox-hugo/auto_delete_warning_message.jpg" >}} |
| 105 | + |
| 106 | +倾听用户的声音是非常重要的,他们可能就会从他们的角度提出非常好的建议。 |
| 107 | + |
| 108 | +但是不要盲目听从用户的建议,比如也有用户建议: |
| 109 | + |
| 110 | +> 我觉得还应该有以下功能. |
| 111 | +> |
| 112 | +> 1. 恢复消息, 恢复用户. (让管理员恢复误删的消息和用户) |
| 113 | +> 2. 主动投喂正常消息. (让管理员主动投喂一些消息. 比如, 群里面昨天 的消息, 随便选一些正常的, 投喂给机器人) |
| 114 | +
|
| 115 | +恢复消息这个功能没有太大必要,并且也不实用,因为恢复消息这个功能本身就很微妙,是直接恢复被删除的消息呢,还是重新发一条新消息? |
| 116 | + |
| 117 | +如: |
| 118 | + |
| 119 | +> - 2025-09-09 10:01:00 张三: 我今天吃了鸡翅 |
| 120 | +> - 2025-09-09 10:02:00 李四:鸡翅有啥好的(被误删消息) |
| 121 | +> - 2025-09-09 10:03:00 王五:人家就喜欢吃,你管得着嘛 |
| 122 | +
|
| 123 | +如果是直接恢复被删除的消息,当前时间是 `2025-09-09 11:00:00` ,把消息恢复之后,还有人会手动刷历史消息,查找旧消息么? |
| 124 | + |
| 125 | +Telegram客户端不一定支持会跳转被恢复的旧消息,这意味着,你恢复误删的消息,也没人看得到。 |
| 126 | + |
| 127 | +假如是重新发一条新消息 `鸡翅有啥好的`, 因为缺失了上下文,群里的人反而会疑惑,你在说什么。 |
| 128 | + |
| 129 | +解决误删问题本质是提高拦截的准确率,而非考虑如何恢复被误删消息,准确率提高了,误删就会减少, |
| 130 | +自然就不需要考虑如何恢复消息,用户体验还会更好. |
| 131 | + |
| 132 | +而主动投喂消息这个想法有点理所当然了。 |
| 133 | + |
| 134 | +没有任何群管理员有意愿帮忙训练这个机器人,对用户而言,他们只想要一个好用的广告拦截机器人,至于怎么开发,训练出来的,用户并不在乎。 |
| 135 | + |
| 136 | +所以用户不会有意愿和动力来优化这个机器人,不好用就再换一个好了,更何况,逐条消息收集的效率实在太慢太慢了, |
| 137 | +所以我后面想出了一个比手工收集数据提效至少100倍的主意。 |
| 138 | + |
| 139 | + |
| 140 | +### <span class="section-num">4.2</span> 过滤重复消息 {#过滤重复消息} |
| 141 | + |
| 142 | +发现人难免会有误区,总会以为别人会和自己一样,之前看到发垃圾广告的人的时候,总会觉得他们是正常的用户手工发。 |
| 143 | + |
| 144 | +但是最近几天发现了一些规律,有用户把同一条消息反复发,不同的群还是发同样的内容 |
| 145 | +即使是复制粘贴也难免会多个或者少个空格,然后消息被删了还一直发同样的内容。 |
| 146 | + |
| 147 | +此外,还有一些群,内容的聊天内容都是广告,我还很奇怪,大家都在发广告,正常用户不都跑了嘛? |
| 148 | + |
| 149 | +{{< figure src="/ox-hugo/spam_group.jpg" >}} |
| 150 | + |
| 151 | +此时,我才意识到,发消息的都是机器人。 |
| 152 | + |
| 153 | +所以我加了个优化,计算消息内容的 hash 值,保存到数据库,并为这个字段建立索引。 |
| 154 | + |
| 155 | +后面检测消息的时候,先根据 hash 值查询,检查是否存在已有的消息,如果消息已经存在且已经被标记成广告或者正常消息,那么就无需再使用模型检测,可以直接返回之前的检测结果。 |
| 156 | + |
| 157 | +这样既提高了准确度,也优化了性能,也减少了人工干预的成本。 |
| 158 | + |
| 159 | +同一个用户如果在同一个群发了三条广告,那么就会自动被封禁掉,也就是相同的广告只要发三条,就会马上被自动封禁掉。 |
| 160 | + |
| 161 | + |
| 162 | +### <span class="section-num">4.3</span> 自动收集数据 {#自动收集数据} |
| 163 | + |
| 164 | +使用机器学习算法来实现一个类似的垃圾广告过滤器并不难,困难的持续收集高质量的训练数据,训练数据是非常宝贵的,毕竟数据才是核心资产。 |
| 165 | + |
| 166 | +而对于我这个产品来说,最难的是冷启动时的训练数据问题: |
| 167 | + |
| 168 | +因为没有训练数据,模型就不准确,模型不好用就不会有人使用,自然也无法通过用户来收集垃圾广告数据,就无法良性循环, |
| 169 | +存在一个鸡生蛋,还是蛋生鸡的问题。 |
| 170 | + |
| 171 | +所以冷启动时,我是手动加了非常多的 Telegram大群,然后人工在里面收集垃圾广告. |
| 172 | + |
| 173 | +但是这个效率实在是太低了,我收集了快一周才只有几百条数据, |
| 174 | +一个是我无法一直盯着各个群,另外是这种20w的大群,一般都会有几个管理员,会手工删除广告,一会没有看垃圾广告数据就会被删掉了。 |
| 175 | + |
| 176 | +这样手工收集数据实在在太痛苦了,我就在想有没有什么办法自动收集数据呢? |
| 177 | + |
| 178 | +我本来想的是直接把我的机器人拉到这些大群里面,即使没有管理员权限无法删除消息,也可以收集数据嘛,后面才意识到 Telegram 有个规定,只有群管理员才有权限加机器人,因为我不是管理员,所以自动没有权限添加机器人。 |
| 179 | + |
| 180 | +但是 telegram 的客户端是开源的,他们提供了 [tdlib](https://github.com/tdlib/td) [^fn:1]这个跨平台的 C++ 库便于社区构建第三方的 Telegram 客户端,那么我自然可以使用这个库来登录我自己的账号,然后使用我的模型来过滤消息,然后把疑似广告的数据都收集起来,我再人工确认下。 |
| 181 | + |
| 182 | +(顺便说一下,tdlib 和 [telegram-bot-api](https://github.com/tdlib/telegram-bot-api) [^fn:2]这两个库竟然都是同一个[作者 Aliaksei Levin](https://github.com/levlam) [^fn:3]在维护,实在是太强了。) |
| 183 | + |
| 184 | +我现在需要做的就是添加各种大群,然后程序就会自动监听并收集数据,我再人工批量确认下。 |
| 185 | + |
| 186 | +{{< figure src="/ox-hugo/spam_group_list.jpg" >}} |
| 187 | + |
| 188 | +{{< figure src="/ox-hugo/telegram_data_collector.jpg" >}} |
| 189 | + |
| 190 | +{{< figure src="/ox-hugo/maybe_spam_list.jpg" >}} |
| 191 | + |
| 192 | +实现起来也不复杂, 200行代码就实现了这个监听消息,分析,并且收集的功能。 |
| 193 | + |
| 194 | +得益于这个自动化的数据收集程序,我1周不到就收集了近上万条的高质量训练数据了,效率实在高太多太多了。 |
| 195 | + |
| 196 | +懒惰真的是程序员的美德, 这个经历再次证明:自动化工具往往能成倍提升效率,这正是工程师价值的体现. |
| 197 | + |
| 198 | + |
| 199 | +## <span class="section-num">5</span> 推广 {#推广} |
| 200 | + |
| 201 | +所谓酒香也怕巷子深,没有用户使用,代码写得再好也没有意义。从产品角度,运营推广至关重要。 |
| 202 | + |
| 203 | +作为个人开发者,我没有大量粉丝关注,也没有营销预算,因此采用了传统的推广方式:撰写博客并在相关社区分享。 |
| 204 | + |
| 205 | +我撰写了两篇双语博客文章,中文版本分享至: |
| 206 | + |
| 207 | +- V2EX: [我写一个基于贝叶斯算法的 Telegram 广告拦截机器人](https://www.v2ex.com/t/1156542) |
| 208 | +- Emacs China: [分享一下我用Emacs写的Telegram广告拦截机器人](https://emacs-china.org/t/emacs-telegram/30043) |
| 209 | +- 微信公众号「宫孙说」:<https://mp.weixin.qq.com/s/Sgq9vDqpHykwge11bwZJrw> |
| 210 | +- 项目被收录到[阮一峰的科技爱好者周刊(第 364 期)](https://www.ruanyifeng.com/blog/2025/09/weekly-issue-364.html) |
| 211 | + |
| 212 | +英文版本发布至: |
| 213 | + |
| 214 | +- Reddit: [Built my first Rails project: A Telegram spam blocker bot](https://old.reddit.com/r/rails/comments/1n6p791/built_my_first_rails_project_a_telegram_spam/) - 获得一些讨论 |
| 215 | +- HackerNews:<https://news.ycombinator.com/item?id=45105908> |
| 216 | +- Twitter: <https://x.com/foobar_ramsay/status/1967277792267247916> |
| 217 | + |
| 218 | +虽然推广效果有限,但这些努力为项目带来了最初的用户关注。 |
| 219 | + |
| 220 | + |
| 221 | +## <span class="section-num">6</span> 成果与数据 {#成果与数据} |
| 222 | + |
| 223 | +上线半个月,截止到目前为止, 已经有超过80个群使用过这个机器人,用户数已经比我预期要多了: |
| 224 | + |
| 225 | +| 指标 | 数值 | |
| 226 | +|------------|-----| |
| 227 | +| GitHub Stars | 106 | |
| 228 | +| 使用群组数 | 83 | |
| 229 | +| 训练数据量 | 10543 | |
| 230 | + |
| 231 | +最开心的是看到我自己的程序在这些群成功拦截垃圾广告,就很有成就感,证明我做的东西真的能用户解决问题。 |
| 232 | + |
| 233 | + |
| 234 | + |
| 235 | +## <span class="section-num">7</span> 结语 {#结语} |
| 236 | + |
| 237 | +这半个月的运营让我深刻体会到:产品不是代码写完就结束,而是从用户反馈中不断迭代的开始。 |
| 238 | + |
| 239 | +产品是需要持续运营的,而写代码只是产品生命周期的其中一个环节,甚至不是最耗费时间的环节。 |
| 240 | + |
| 241 | +下一步,我计划进一步优化模型准确率,并探索多语言支持,也欢迎关注我的频道或提交Issue一起讨论。 |
| 242 | + |
| 243 | +- 开源地址:<https://github.com/ramsayleung/bayes_spam_sniper> |
| 244 | +- 立即体验:<https://t.me/BayesSpamSniperBot> |
| 245 | +- 我的频道(菠萝油与天光墟): <https://t.me/pipeapplebun> |
| 246 | + |
| 247 | +<div class="qr-container" center> |
| 248 | + |
| 249 | +<img src="/ox-hugo/qrcode_gh_e06d750e626f_1.jpg" alt="qrcode_gh_e06d750e626f_1.jpg" class="qr-container" width="160px" height="160px" center="t" /> |
| 250 | +公号同步更新,欢迎关注👻 |
| 251 | + |
| 252 | +</div> |
| 253 | + |
| 254 | +[^fn:1]: <https://github.com/tdlib/td> |
| 255 | +[^fn:2]: <https://github.com/tdlib/telegram-bot-api> |
| 256 | +[^fn:3]: <https://github.com/levlam> |
0 commit comments