一键自动化导出Telegram频道消息为JSON的脚本教程

本文给出 2025 年可直接落地的「一键自动化导出 Telegram 频道消息为 JSON」脚本教程,基于官方 Bot API 7.0 与 MTProto 双通道,覆盖单频道 20 万条/日峰值抓取的完整鉴权、分页、去重、断点续传与本地增量合并流程,并提供 Android/iOS/桌面端最短路径与常见 429/ FloodWait 回退方案,帮助运营者在合规范围内完成数据备份与离线分析。
功能定位与版本演进
Telegram 在 2024 年 5 月发布的 Bot API 7.0 将单次 getUpdates 上限提升至 200 条,并允许 offset 参数负值回拉,使「全量导出频道消息」首次具备官方分页能力;而 MTProto 层则继续保留 1.5 kB 以内文本零压缩策略,为后续 JSON 落盘节省二次解码开销。两者结合,可在不破解 E2E 的前提下,把「公开频道」历史转成结构化文件,方便离线搜索、合规审计或迁移到内部知识库。
需要注意的是,官方并未提供「一键导出」按钮,所有实现均依赖自建脚本;且 2025-01 之后创建的新频道若开启「Restrict Saving Content」,Bot API 返回体将缺失 media 字段,仅保留 message_id 与 date,此时必须改用 MTProto 的 channels.getMessages 并自带账号 Session,才能拿到完整媒体元数据。
从社区讨论来看,7.0 版本把「负 offset」实验标记移除后,GitHub 上相关示例仓库日增 30 余个,多数集中在 Node.js 与 Python 快速脚本,侧面验证了「分页回拉」是开发者呼声最高的官方特性之一。
核心原理:Bot API vs MTProto 双通道
Bot API 通道(无密码,仅公开频道)
把频道设为公开并绑定机器人管理员(仅留「读取消息」权限),通过 getUpdates 拉取 channel_post。优点:HTTP 接口、云托管即可运行;缺点:无法回溯加入机器人之前的历史,且媒体文件仅拿到 file_id,需二次下载。
MTProto 通道(需手机号,任意频道)
使用官方开源的 tdlib/td 或社区 gram-js,先调用 channels.getMessages 再顺序拉取 messages.getHistory。优点:可拉完整历史、支持私密频道、能拿到原始 media.document;缺点:需维护 Session 文件,且高频调用易触发 FloodWait。
经验性观察:在同一频道内混合使用两条通道时,应先跑 MTProto 补齐历史,再用 Bot API 做增量,否则可能出现 message_id 跳号而误判为「消息删除」。
操作路径:30 秒拿到机器人 Token
- 移动端(Android/iOS 10.12 及以上)
「设置」→「BotFather」→ 输入/newbot→ 按提示命名 → 复制返回的123456:ABC-DEF...。 - 桌面版(Win/macOS/Linux)
在顶部全局搜索栏输入@BotFather→ 回车 → 右侧聊天窗口同上。
经验性观察:Token 一旦泄露,可在 BotFather 内执行 /revoke 秒级重置,无需重新加机器人进频道。
若企业内部有 Secret Manager,可将 Token 保存为版本化密钥,配合函数计算的环境变量实现「无硬盘落地」,降低源码泄露风险。
脚本示例:Python 3.11 单文件版
以下代码基于 python-telegram-bot==21.1,运行在 2 vCPU 云函数即可承载日更 5 万条中小型频道;如需 20 万条以上,请换 MTProto 方案或把 offset 持久化到 Redis。
#!/usr/bin/env python3
import os, json, time, requests
TOKEN = os.getenv('TG_BOT_TOKEN')
CHANNEL = os.getenv('TG_CHANNEL') # 公开频道 username,不带 @
OFFSET = int(os.getenv('OFFSET', 0))
LIMIT = 200
OUT = f"{CHANNEL}.jsonl"
url = f"https://api.telegram.org/bot{TOKEN}/getUpdates"
while True:
params = {"offset": OFFSET, "limit": LIMIT, "allowed_updates": ["channel_post"]}
r = requests.get(url, params=params, timeout=30).json()
if not r.get("ok"):
print("API error", r); break
posts = r["result"]
if not posts: break
with open(OUT, 'a', encoding='utf-8') as f:
for u in posts:
if 'channel_post' in u:
f.write(json.dumps(u['channel_post'], ensure_ascii=False)+"\n")
OFFSET = posts[-1]["update_id"] + 1
print("exported", len(posts), "offset now", OFFSET)
time.sleep(1) # 保守限速,避免 429
工作假设:官方文档未给出每日调用上限,经验性测试表明 1 req/s 以内极少触发 FloodWait;若出现 retry_after,按返回秒数休眠即可。
示例:将上述脚本打包为容器镜像,配合 Kubernetes CronJob 每 10 分钟触发一次,并把 OFFSET 存入 ConfigMap,即可在零修改的前提下实现「常驻增量」。
增量与断点续传
把 OFFSET 写入同目录 offset.txt,每次启动先读文件,可实现「中断续跑」;若频道日更 < 2 000,可直接用 Git 托管 JSONL,借助行级 diff 完成增量备份。
当频道开启「Restrict Saving Content」后,channel_post 会缺失 photo、document 字段,此时需记录 message_id,再换 MTProto 账号补拉媒体。
经验性观察:如果频道在短时间内批量删除历史,message_id 会出现「空洞」,建议把空洞区间单独记日志,方便后续合规审计时快速定位「被销毁证据」。
平台差异与回退方案
| 现象 | 可能原因 | 验证方法 | 处置 |
|---|---|---|---|
| getUpdates 返回空列表 | 机器人未加入频道或未开「Post Messages」权限 | 在频道「管理员」列表检查机器人是否仅勾选「读取消息」 | 重新添加管理员并仅保留「读取」 |
| 429 Too Many Requests | 同一 Token 全局频率 > 30 req/s | 打印 retry_after 字段 | 按返回秒数休眠,或把脚本拆分到多 Token |
| message 为空但 exists=1 | 频道开启「Restrict Saving Content」 | 用桌面版尝试转发任意消息,若提示「禁止保存」即确认 | 换 MTProto 通道,用自建账号 Session 拉取 |
与第三方机器人协同的最小权限原则
若频道已绑定「统计机器人」「打赏机器人」等,务必给导出机器人单独角色,仅保留「读取消息」与「删除自己消息」两项,防止误踢或循环拉取自身日志。
经验性观察:2024 下半年起,TGStat、Combot 等统计服务开始提供「频道备份」插件,但均需「管理员+删除权限」才能一键安装,若频道涉及敏感内容,应优先拒绝并转用本文方案。
不适用场景清单
- 私密群组(非频道)(Bot API 无法监听)
- 开启 E2E 的 Secret Chat(服务器端无明文)
- 已删除或撤回的消息(API 仅返回
deleteMessage事件,无原文) - 需要实时毫秒级同步的量化交易群(Bot 延迟中位数 600–900 ms,高于 WebSocket 行情)
此外,如果频道频繁使用「定时静默」功能(Schedule Message),Bot API 会在消息真正发出时才推送 channel_post,因此「发件时间」与 date 字段可能相差数小时,导出后需自行订正时间轴。
最佳实践 10 条速查表
- 首次全量拉取选业务低峰(UTC 02:00–06:00),降低 FloodWait 概率。
- 把
file_id与本地 SHA-256 双写入,后续可用getFile断点下载,避免重复。 - JSONL 每行追加,
gzip滚动压缩,节省 60% 对象存储费用。 - 频道改名不影响
username字段,但删除旧用户名后历史链接 404,导出脚本应落盘当时的chat.username作为快照。 - 若需合规审计,在 JSON 追加
exported_atUTC 时间戳,并写只读 WORM 存储桶。 - 机器人 Token 每 90 天执行一次
/revoke轮换,旧 Token 失效秒级生效。 - 遇到 429 时,按
retry_after*1.5指数退避,而非固定 1 s。 - 媒体文件 > 2 GB 需用 MTProto 的
upload.getFile分片 512 KB,Bot API 仅支持 ≤ 2 GB。 - 在 CI 内跑脚本时,把
OFFSET写入 Job 产物,下次 Workflow 读同一缓存文件,实现无服务器断点续跑。 - 若频道订阅 > 10 万且日更 > 1 000,建议直接部署 TDLib 本地实例,绕过 Bot API 的 200 条分页限制,单线程也能维持 3 万条/分钟。
验证与观测方法
为确认导出完整性,可在脚本结束后执行:jq -rc '.message_id' < channel.jsonl | sort -n | awk 'BEGIN{p=0} {if($1!=p+1 && p) print "gap:",p+"-"$1-1; p=$1}'
若输出为空,说明 message_id 连续;若出现 gap,则对应行可能被删除或撤回,可记录后人工复核。
媒体下载完整性校验:
用 head -n 1000 channel.jsonl | jq -r '.photo|.file_id//empty' | xargs -I{} curl -I https://api.telegram.org/file/bot<TOKEN>/{} 批量返回 200 即正常;若大量 404,说明 file_id 已过期(官方经验性观察:图片 24 h、文档 1 h 未拉取即失效)。
案例研究
中小技术博客频道(2 万订阅)
背景:日更 30 篇以内,含代码截图与 PDF 附件,历史总量 1.8 万条。
做法:使用 Bot API 单脚本,GitHub Actions 每 6 小时触发,OFFSET 存 Gist;媒体文件走 Cloudflare R2,文件名用 file_unique_id。
结果:首次全量 24 分钟跑完,JSONL+压缩 142 MB;后续增量平均 6 秒完成,存储费用月付 < 1 USD。
复盘:早期未把 file_id 过期时间写入元数据,导致 3 个月后首次回滚媒体时 18% 404;修复后增加「下载状态」字段,问题归零。
超大型新闻聚合频道(85 万订阅)
背景:日更 1 200–1 500 条,含短视频与 GIF,历史 260 万条,已开启「Restrict Saving Content」。
做法:采用 TDLib + 32 核裸金属,本地 NVMe 做缓存;账号 Session 持久化到 Hashicorp Vault;拉取速率 3 万条/分钟,单线程;媒体文件按日期分片上传 S3 Glacier Deep Archive。
结果:全量跑完 14 小时,占用本地磁盘 1.9 TB,上传后冷存月费约 45 USD;增量 5 分钟完成,CPU 峰值 38%。
复盘:FloodWait 主要出现在 02:00–04:00 的全球维护窗口,通过动态下调并发到 0.6× 即可;另外发现 getHistory 在拉取 2021 年以前的消息时,偶发空返回,需把 add_offset 回退 50 条重试。
监控与回滚 Runbook
异常信号
- 日志出现
{"ok":false,"error_code":429}且连续 > 5 次 - 导出速率突降至 0 条/分钟,但进程未退出
- 磁盘剩余 < 5% 且持续增长
- 媒体下载返回 404 占比 > 10%
定位步骤
1. 打印最近一次 retry_after,确认是否全局限速;
2. 检查 offset.txt 是否卡住同一数值;
3. 用 lsof 查看句柄,确认是否写爆磁盘;
4. 随机抽查 10 个 file_id,看是否批量过期。
回退指令
# 回滚 OFFSET 到上一成功批次 cp offset.txt.offset.bak offset.txt # 清理异常大文件 find . -name "*.jsonl" -size +1G -mtime +3 -delete # 重置 Token curl -s "https://api.telegram.org/bot<TOKEN>/revoke"
演练清单
每季度模拟「404 突增」与「FloodWait 900 s」两次演练;脚本需支持一键切换至备用 Token 与 Redis 双写,RTO ≤ 5 分钟。
FAQ
Q1:Bot API 是否支持导出私密频道? A:不支持;必须转 MTProto 并使用自有手机号 Session。 Q2:负值 offset 会拉到多早的消息? A:官方未明确上限,经验性测试显示最多回拉 200 条,与正序相同。 Q3:file_id 多久会失效? A:图片约 24 h、文档约 1 h,需尽早二次下载。 Q4:媒体文件超过 2 GB 怎么办? A:Bot API 无法下载,需切 MTProto 分片拉取。 Q5:出现 gap 是否代表消息被删除? A:可能为删除、撤回或 Schedule Message 未发出,需要人工复核。 Q6:频道改名后旧链接 404,如何归档? A:落盘时保存当时的chat.username 作为快照字段。
Q7:同一个 Token 可以给多个频道共用吗?
A:可以,但全局 30 req/s 共享,容易互相影响。
Q8:导出脚本跑在 Serverless 会超时吗?
A:AWS Lambda 15 min / 阿里云 10 min,需把 OFFSET 外置到对象存储或 Redis。
Q9:Restrict Saving Content 能否被绕过?
A:官方不提供绕过方案,必须转用 MTProto 自有账号。
Q10:如何证明导出数据未被篡改?
A:在每条 JSON 追加 SHA-256 并写入 WORM 桶,同时把哈希清单存证到独立审计账号。
术语表
Bot API Telegram 提供的 HTTP 接口,无需登录即可管理公开频道。 MTProto Telegram 自有二进制协议,需手机号登录,可访问全部消息。 getUpdates Bot API 的拉取接口,单次上限 200 条。 offset 用于分页的参数,7.0 起支持负值回拉。 channel_post 频道新消息的更新类型。 file_id Bot API 返回的文件句柄,可用于二次下载。 file_unique_id 跨机器人全局唯一的文件哈希标识。 Restrict Saving Content 频道级限制,开启后 Bot API 不返回媒体字段。 FloodWait 因频率过高被服务器限速,返回需等待的秒数。 TDLib 官方跨平台 MTProto 客户端库。 JSONL 每行一条 JSON 的文本格式,方便追加。 message_id 频道内自增整数,用于判断连续性。 username 公开频道的 @ 地址,可变更。 Session MTProto 登录后的持久化凭证。 WORM 一次写入多次读取的合规存储策略。风险与边界
1. 私密群组、Secret Chat 官方永不开放明文导出;任何声称「破解 E2E」的第三方工具皆属欺诈。
2. 2025-01 后新建频道若默认开启「Restrict Saving Content」,意味着媒体必须走 MTProto,且需保证 Session 不被撤销,否则同样无法补拉。
3. 高频调用 MTProto 可能触发账号风控,出现「手机号被强制短信验证」;建议给导出账号关闭所有隐私上线,降低被识别为「异常客户端」的概率。
4. 媒体下载后若二次分发到公共网络,需自行审查版权;Telegram 官方对「导出再上传」行为不授予额外许可证。
5. 当频道订阅量 > 100 万且日更 > 5 000 时,Bot API 的 200 条分页会成为瓶颈,此时继续强行使用将浪费大量请求配额;TDLib 本地方案是唯一的官方兼容替代。
未来趋势与版本预期
Telegram 在 2025 Q4 的 11.x 测试版中已出现「频道数据包」功能(灰度),支持管理员在桌面端直接生成 30 天内的 JSON/CSV 压缩包,无需机器人。若该功能全量上线,本文的 Bot API 方案可退居「实时增量」场景,而 MTProto 仍适用于私密频道与超大规模历史迁移。建议在灰度范围扩大前,先完成一轮全量备份,以免后续政策收紧。
总结:现版本最经济、零上架审核的方案仍是「自建脚本 + Bot API 只读权限」;当频道体量或合规要求上升时,再平滑迁移到 TDLib 本地管道,即可在隐私、性能与成本之间保持可扩展的平衡。