为什么你的“每日对话人数”统计总是不准?
很多开发者简单地把“用户发送的消息数”当成“对话人数”,或者依赖Bot API的update记录做去重,结果发现数字要么虚高要么缺失。实际上,Telegram官方并不直接提供“每日活跃对话用户”的维度,我们需要基于原始消息事件自己构建统计模型。本文将从数据获取、存储、去重到报表输出,给你一套可直接落地的完整方案。
核心概念:什么才算“对话”
在Telegram机器人场景中,“对话”通常指用户向机器人发送的任意消息(包括文本、命令、按钮回调等)。一个用户一天内多次互动,只算一个“活跃对话人数”。因此,正确的统计粒度为:用户ID + 日期(UTC)。注意时区问题,建议统一用UTC存储,展示时再转换。
方案一:轻量级内存去重(适用小规模)
如果你的机器人每日消息量在几千条以内,可以使用内存字典+每日重置的方式。但缺点很明显:重启丢失、多实例无法同步。仅建议原型验证时使用。
from datetime import datetime, timedelta
from collections import defaultdict
# 键为日期字符串,值为用户ID集合
daily_users = defaultdict(set)
def on_message(update):
user_id = update.effective_user.id
day = datetime.utcnow().date().isoformat()
daily_users[day].add(user_id)
# 获取某天的活跃人数
def get_daily_count(day):
return len(daily_users.get(day, set()))
方案二:数据库持久化(推荐生产使用)
生产环境必须使用数据库。以SQLite为例,设计一张记录表:
CREATE TABLE user_daily_active (
user_id INTEGER,
date TEXT,
last_seen TIMESTAMP,
PRIMARY KEY (user_id, date)
);
每次收到消息时,执行INSERT OR IGNORE,若插入成功则说明该用户今天第一次互动,计数器加一。Python示例:
import sqlite3
from datetime import datetime
conn = sqlite3.connect('bot_stats.db', check_same_thread=False)
def record_activity(user_id):
today = datetime.utcnow().date().isoformat()
now = datetime.utcnow().isoformat()
cur = conn.cursor()
cur.execute("INSERT OR IGNORE INTO user_daily_active (user_id, date, last_seen) VALUES (?,?,?)", (user_id, today, now))
if cur.rowcount > 0:
conn.commit()
return True # 今日首次活跃
else:
# 更新last_seen
cur.execute("UPDATE user_daily_active SET last_seen=? WHERE user_id=? AND date=?", (now, user_id, today))
conn.commit()
return False
统计某天人数:
def daily_active_count(date_str):
cur = conn.cursor()
cur.execute("SELECT COUNT(*) FROM user_daily_active WHERE date=?", (date_str,))
return cur.fetchone()[0]
进阶:统计“发起对话”与“参与对话”区别
如果只想统计“主动发送消息”的用户,需过滤机器人回复后的回调等被动事件。建议在handler中分类:只监听Message类型,忽略CallbackQuery。或者增加一个message_type字段,根据业务需求区分。
可视化与报表输出
用Python的matplotlib或plotly生成每日趋势图。更简单的方案是定期导出到Google Sheets或Grafana。这里给出一个简单的命令行输出示例:
def print_last_7_days():
from datetime import date, timedelta
for i in range(7):
d = (date.today() - timedelta(days=i)).isoformat()
print(f": {daily_active_count(d)} 人")
常见坑与规避
- 时区混乱:务必用UTC存储,展示时按“服务器时区”或“用户时区”转换。
- 多实例部署:不要用内存方案,使用Redis或数据库。
- 重复消息:Webhook模式下可能重复投递,需要维护update_id去重,避免重复计数。
- 删除的用户:如果用户删除账号,user_id变为不可用,可定期清理不活跃数据。
总结
统计每日对话人数的核心是“以用户和日期为唯一键去重”。使用数据库持久化是最稳妥的方式,再配合定时任务生成报表。你可以在此基础上扩展出周活、月活等指标。本文方案已在使用教程栏目中多次实践,适用于大多数机器人场景。如果仍有疑问,欢迎在评论区留言。