← 返回列表

在不下载原文件的前提下,如何用 Python 自动索引 Telegram 视频元数据

分类:Telegram群组发布于:2026-08-16

telegram中文搜索群组

在 Telegram 群组、频道或个人会话中,视频文件往往包含大量具有检索价值的信息,例如文件名、文件大小、时长、分辨率、编码格式、消息时间以及所属会话。很多开发者误以为,只有把完整视频下载到本地后,才能完成内容整理和搜索索引。

实际上,只要你拥有对应会话的访问权限,就可以使用 Python 和 Telegram 的 MTProto 客户端读取消息对象中的视频元数据,而不必下载原始媒体文件。本文将以 Telethon 为例,介绍如何设计一个稳定、节省带宽并且便于扩展的 Telegram 视频元数据索引程序。

📌 一、先理解 Telegram 视频元数据的组成

Telegram 中的视频通常以媒体消息的形式存在,视频信息可能位于 Message.media.document 对象中。文件大小、MIME 类型、文件名、宽度、高度、时长和视频属性等字段,通常可以直接从消息结构中读取。

需要注意的是,Telegram 的 Bot API 与 MTProto 客户端能力并不完全相同。普通机器人受到权限和接口范围限制,而 Telethon、Pyrogram 等 MTProto 客户端更适合进行历史消息遍历、媒体属性解析和批量索引

视频元数据通常包括:
- Telegram 文件 ID
- 文件唯一标识 document.id
- 文件大小 size
- MIME 类型 mime_type
- 文件名 file_name
- 视频时长 duration
- 视频宽度 width
- 视频高度 height
- 消息所在会话和消息 ID
- 发布时间 date

🛠️ 二、准备 Python 与 Telegram API 环境

首先需要在 Telegram 官方开发者页面创建应用,获取 API IDAPI Hash。这两个参数用于让 MTProto 客户端建立身份认证连接,不能公开写入网页、代码仓库或日志。

然后安装 Telethon 和用于保存索引结果的基础依赖。本文使用 SQLite 作为示例数据库,因为它不需要额外部署服务,适合个人工具和小规模索引任务。

python -m venv .venv
source .venv/bin/activate

pip install telethon

# Windows PowerShell
# .venv\Scripts\Activate.ps1
# pip install telethon

第一次运行客户端时,Telethon 会要求输入手机号码、登录验证码以及可能存在的二次验证密码。认证成功后,程序会生成本地会话文件,后续运行通常不需要重复登录。

🔐 安全保存密钥

建议通过环境变量读取密钥,并将会话文件放置在权限受控的目录中。不要把 api_hash、手机号、验证码或 session 文件提交到 Git 仓库。

import os

API_ID = int(os.environ["TG_API_ID"])
API_HASH = os.environ["TG_API_HASH"]
SESSION_NAME = os.getenv("TG_SESSION", "video_indexer")

🔎 三、读取视频属性而不下载原文件

Telethon 的 iter_messages 方法可以按顺序读取会话消息,而不会自动把视频文件下载到本地。程序只需要判断消息是否包含媒体,再从 Document 属性中提取结构化信息。

视频的时长、宽度和高度一般存放在 DocumentAttributeVideo 中,文件名则通常存放在 DocumentAttributeFilename 中。不同消息可能缺少某些字段,因此代码必须使用默认值,不能假设所有视频都拥有完整信息。

import os
import sqlite3
from datetime import timezone
from telethon import TelegramClient
from telethon.tl.types import (
    DocumentAttributeVideo,
    DocumentAttributeFilename
)

API_ID = int(os.environ["TG_API_ID"])
API_HASH = os.environ["TG_API_HASH"]
SESSION_NAME = "video_indexer"

def extract_video_metadata(message):
    media = message.media
    document = getattr(media, "document", None)

    if document is None:
        return None

    video = None
    filename = None

    for attribute in document.attributes:
        if isinstance(attribute, DocumentAttributeVideo):
            video = attribute
        elif isinstance(attribute, DocumentAttributeFilename):
            filename = attribute.file_name

    if video is None:
        return None

    message_time = message.date
    if message_time and message_time.tzinfo is None:
        message_time = message_time.replace(tzinfo=timezone.utc)

    return {
        "message_id": message.id,
        "document_id": document.id,
        "access_hash": getattr(document, "access_hash", None),
        "file_size": document.size,
        "mime_type": document.mime_type,
        "file_name": filename or "",
        "duration": video.duration,
        "width": video.w,
        "height": video.h,
        "supports_streaming": bool(video.supports_streaming),
        "message_date": message_time.isoformat() if message_time else None,
        "caption": message.message or ""
    }

上面的函数只处理消息对象和属性对象,不调用 download_media,因此不会主动下载原始视频。对于大规模任务,这种方式可以显著降低磁盘占用和网络流量。

🗃️ 四、把元数据保存到可检索数据库

只有读取没有保存,无法形成真正可用的索引系统。可以为会话标识、消息 ID、文件唯一 ID、文件名、分辨率、时长和字幕说明建立字段,并对常用搜索字段设置索引。

def init_db(connection):
    connection.execute("""
        CREATE TABLE IF NOT EXISTS videos (
            chat_id INTEGER NOT NULL,
            message_id INTEGER NOT NULL,
            document_id INTEGER NOT NULL,
            access_hash INTEGER,
            file_size INTEGER,
            mime_type TEXT,
            file_name TEXT,
            duration INTEGER,
            width INTEGER,
            height INTEGER,
            supports_streaming INTEGER,
            message_date TEXT,
            caption TEXT,
            PRIMARY KEY (chat_id, message_id)
        )
    """)
    connection.execute(
        "CREATE INDEX IF NOT EXISTS idx_video_name ON videos(file_name)"
    )
    connection.execute(
        "CREATE INDEX IF NOT EXISTS idx_video_document ON videos(document_id)"
    )
    connection.commit()

def save_metadata(connection, chat_id, data):
    connection.execute("""
        INSERT OR REPLACE INTO videos (
            chat_id, message_id, document_id, access_hash,
            file_size, mime_type, file_name, duration,
            width, height, supports_streaming,
            message_date, caption
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    """, (
        chat_id,
        data["message_id"],
        data["document_id"],
        data["access_hash"],
        data["file_size"],
        data["mime_type"],
        data["file_name"],
        data["duration"],
        data["width"],
        data["height"],
        int(data["supports_streaming"]),
        data["message_date"],
        data["caption"]
    ))
    connection.commit()

将会话 ID 与消息 ID 作为联合主键,可以避免程序重复运行时产生大量重复记录。使用 INSERT OR REPLACE 还能在消息属性发生变化时更新已有索引。

电报精准找群黑科技提示:

由于 Telegram 官方搜索对中文支持极差,很多优质的推广、技术和资源群组隐藏极深。如果你正在寻找相关的活跃社群,强烈推荐使用本站首页的 【TTSO - Telegram 智能搜索 Bot】。作为目前最好用的电报综合搜索导航,只需输入关键词,即可秒级触达数十万个精选 TG 中文群组、资源频道。一键直达,帮你节省 90% 的找群时间!

⚙️ 五、遍历频道并完成自动索引

下面的示例会遍历指定频道或群组,将识别到的视频元数据写入 SQLite。为了控制请求速度,可以使用 wait_time,并通过 limit 控制单次扫描范围。

from telethon import TelegramClient

async def index_chat(chat_ref, limit=None):
    connection = sqlite3.connect("telegram_videos.db")
    init_db(connection)

    async with TelegramClient(SESSION_NAME, API_ID, API_HASH) as client:
        entity = await client.get_entity(chat_ref)
        chat_id = entity.id

        async for message in client.iter_messages(
            entity,
            limit=limit,
            wait_time=1
        ):
            metadata = extract_video_metadata(message)
            if metadata:
                save_metadata(connection, chat_id, metadata)
                print(
                    f"indexed message={metadata['message_id']} "
                    f"{metadata['width']}x{metadata['height']}"
                )

    connection.close()

if __name__ == "__main__":
    import asyncio
    asyncio.run(index_chat("your_channel_username", limit=1000))

如果目标是持续维护索引,可以记录每个会话最后扫描到的消息 ID,下次只读取新增消息。对于消息量较大的频道,还应设计断点续扫、异常重试和日志记录,避免程序中断后从头开始。

📊 可加入的搜索字段

在实际项目中,可以对文件名和 caption 做关键词清洗,同时保存语言、主题、来源频道和标签等业务字段。若需要全文搜索,SQLite FTS5、Elasticsearch 或 PostgreSQL 全文检索都比简单的模糊查询更适合。

⚠️ 六、权限、限流与数据合规问题

程序只能读取当前账号有权访问的聊天内容,不能通过技术手段绕过私密群组权限、删除消息限制或用户隐私设置。索引公开频道时,也应遵守 Telegram 使用规则以及当地适用的版权和数据保护法律。

Telegram API 存在请求频率限制,短时间发送大量请求可能触发 FloodWaitError。生产环境应捕获该异常,根据服务器返回的等待时间暂停任务,并使用队列、退避策略和合理的并发数。

from telethon.errors import FloodWaitError
import asyncio

try:
    async for message in client.iter_messages(entity, wait_time=1):
        metadata = extract_video_metadata(message)
        if metadata:
            save_metadata(connection, entity.id, metadata)
except FloodWaitError as error:
    print(f"API 请求过于频繁,请等待 {error.seconds} 秒")
    await asyncio.sleep(error.seconds)

另外,document.id 主要用于识别 Telegram 文件对象,不能简单当作永久公开下载地址。实际播放或后续访问仍然受到账号权限、消息状态和 Telegram 文件访问令牌变化的影响。

✅ 七、常见错误与优化建议

第一,不能只通过文件扩展名判断视频类型,因为部分上传者会修改文件名或省略扩展名。更可靠的做法是同时检查 MIME 类型、视频属性和媒体对象结构。

第二,不要把索引任务设计成一次性脚本。通过保存扫描进度、执行增量更新、建立唯一约束和记录异常日志,可以让系统长期稳定运行。

第三,元数据索引并不等于内容理解。如果需要根据画面内容搜索,还要在获得合法授权的前提下,进一步处理缩略图、字幕或音频转写结果,而这些步骤可能涉及额外的数据处理和隐私风险。

❓ 常见问题解答(FAQ)

1. 不下载视频,能否获取时长和分辨率?

通常可以。Telegram 消息中的 DocumentAttributeVideo 会提供时长、宽度和高度等信息,程序只读取消息对象,不需要请求完整文件内容。

2. Bot API 能完成同样的任务吗?

如果机器人能够接收到相关消息,Bot API 可以读取部分媒体信息,但它无法像用户账号一样自由遍历所有历史会话。需要扫描频道历史记录时,通常应优先评估 MTProto 客户端方案。

3. 是否可以索引私密群组?

只有登录账号已经加入该群组,并且账号拥有正常访问权限时,程序才可以读取相关消息。任何试图绕过邀请、权限或隐私控制的做法都不属于正常的元数据索引范围。

4. 为什么有些视频没有文件名?

上传方式、客户端版本和文件类型都可能导致文件名属性缺失。索引时应使用空字符串或消息 caption 作为补充,并将 document.id 作为稳定的对象识别字段之一。

5. 如何让索引支持中文搜索?

可以先对文件名和 caption 做统一大小写、空白符、标点符号与同义词处理,再使用 SQLite FTS5 或专业搜索引擎建立全文索引。对于中文内容,还可以根据业务场景加入分词器和标签体系。

总体而言,使用 Telethon 直接读取 Telegram 消息中的媒体属性,就能在不下载原视频的前提下建立轻量级索引。将权限控制、请求限流、增量扫描和数据合规纳入设计后,这套方案可以扩展为频道资料库、媒体检索工具或内部内容管理系统。

telegram中文搜索群组
Telegram搜索入口客服ID@TTSO联系