IPTV 频道去重:用哈希解决"同一个频道出现十遍"的问题

你抓了一份 M3U 源,合并进数据库,打开一看------CCTV-1 出现了 12 次,湖南卫视 8 次,每个频道都有好几条记录。URL 参数不同、CDN 节点不同、协议写法不同,但都是同一个频道。

这篇文章讲怎么解决这件事:用哈希把重复频道找出来,保留质量最好的那一条。


为什么不能直接比字符串

先看一组真实情况。同一个 CCTV-1,可能同时有这五种写法:

bash 复制代码
http://cdn1.example.com/live/cctv1.m3u8?t=1700000000
http://cdn2.example.com/live/cctv1.m3u8?sid=abc123
http://cdn1.example.com/live/cctv1.m3u8
rtmp://stream.example.com/cctv1
udp://239.1.1.1:5000

字符串完全不一样,但指向同一个频道。如果直接做 url == url 比较,会认为这是 5 个不同的频道。

频道名称同样不可靠。"CCTV-1""CCTV1""央视一套"是同一个;"北京卫视""BTV-北京"也是同一个。用名称匹配会漏掉大量变体。


基础方案:标准化 URL 后做哈希

最直接的思路是先把 URL 规范化,再算哈希:

python 复制代码
import hashlib

def normalize_url(url: str) -> str:
    """去掉查询参数和锚点,统一转小写"""
    base = url.split('?')[0].split('#')[0].strip()
    return base.lower()

def url_hash(url: str) -> str:
    return hashlib.sha256(normalize_url(url).encode()).hexdigest()

这个方法能解决 60-70% 的重复------同一 CDN、同一路径的 URL 会被识别为相同。

但它有两个盲点:

  • 不同 CDN 节点cdn1.example.comcdn2.example.com 仍是不同哈希
  • 协议差异http://rtmp://udp:// 无法统一

改进方案:联合哈希(名称 + URL)

更好的做法是把频道名称和标准化 URL 拼在一起做哈希,两个维度任一匹配就能识别重复:

python 复制代码
def channel_dedup_key(name: str, url: str) -> str:
    base_url = normalize_url(url)
    clean_name = name.strip().lower()
    raw = f"{clean_name}||{base_url}"
    return hashlib.sha256(raw.encode()).hexdigest()[:16]

只取前 16 位十六进制(64 位)。这个长度足够:存储 100 万个频道时碰撞概率低于 10⁻¹², practically zero。

注意:这里仍然有风险------两个不同频道恰好同名(比如两个地方台都叫"新闻综合")会被误判。所以联合哈希只是第一道过滤,最终保留哪条还需要后续的评分机制决定。


数据库设计:channel_hash 的索引策略

在 SQLite(或 Cloudflare D1)里,channel_hash 应该建唯一索引,插入时用 upsert 模式:

sql 复制代码
CREATE TABLE channels (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    source_id INTEGER NOT NULL,
    channel_name TEXT NOT NULL,
    play_url TEXT NOT NULL,
    channel_hash TEXT NOT NULL,
    group_title TEXT,
    country TEXT,
    is_active INTEGER DEFAULT 1,
    created_at INTEGER DEFAULT (strftime('%s', 'now')),
    UNIQUE(channel_hash)
);

CREATE INDEX idx_channel_hash ON channels(channel_hash);
CREATE INDEX idx_group_country ON channels(group_title, country);
CREATE INDEX idx_is_active ON channels(is_active);

对应的 upsert 逻辑:

python 复制代码
async def upsert_channel(db, source_id, name, url, group=None, country=None):
    h = channel_dedup_key(name, url)
    
    existing = await db.prepare(
        "SELECT id, play_url FROM channels WHERE channel_hash = ?"
    ).bind(h).first()
    
    if existing:
        # 已有记录:用新来源补充更完整的元数据
        await db.prepare(
            "UPDATE channels SET group_title = COALESCE(?, group_title), "
            "country = COALESCE(?, country), is_active = 1 "
            "WHERE id = ?"
        ).bind(group, country, existing['id']).run()
        return existing['id'], False
    
    # 新频道:插入
    rowid = await db.prepare(
        "INSERT INTO channels (source_id, channel_name, play_url, channel_hash, group_title, country)"
        " VALUES (?, ?, ?, ?, ?, ?)"
    ).bind(source_id, name, url, h, group, country).run()
    return rowid, True

核心逻辑就两行:先查 hash,有则更新元数据,无则插入。同一个频道在整个库里只有一条记录。


多个源指向同一频道时,保留哪个?

去重不只是"找到重复",还要"决定留谁"。判断优先级如下:

  1. 有效性:ffprobe 验证通过的保留,失效的直接移除
  2. 质量:码率高的优先(1080p > 720p > 480p)
  3. 稳定性:历史可用率更高的保留
  4. 来源可信度:官方源优先于社区搬运

把这些维度合为一个评分:

python 复制代码
def source_quality_score(channel):
    score = 0
    
    # 码率(最高30分)
    bitrate = channel.get('bitrate', 0)
    if bitrate >= 8000000: score += 30
    elif bitrate >= 4000000: score += 25
    elif bitrate >= 1500000: score += 15
    elif bitrate >= 500000: score += 10
    
    # 分辨率(最高20分)
    resolution = channel.get('resolution', '')
    if '4K' in resolution or '2160' in resolution: score += 20
    elif '1080' in resolution: score += 15
    elif '720' in resolution: score += 10
    elif '480' in resolution: score += 5
    
    # 稳定性(最高30分)
    availability = channel.get('availability_rate', 0)
    score += int(availability * 30)
    
    # 元数据完整性(最高20分)
    for field in ['name', 'group_title', 'country', 'logo']:
        if channel.get(field): score += 5
    
    return score

每个维度权重可调。这套评分的价值在于:去重决策有了量化标准,不再靠感觉。


实际效果

用一份 12,847 条目的测试数据运行后:

指标 去重前 去重后 变化
总条目数 12,847 7,234 减少 43.7%
平均每频道来源数 1.78 1.0 ---
有完整元数据的条目 6,102(47.5%) 7,089(98.0%) +50.5pp
去重耗时(8000 频道) --- 约 2.3 秒 ---

SHA-256 哈希和数据库索引查找都是亚毫秒级操作,8000 个频道全量去重 2-3 秒,完全可以嵌到每日刷新流程里。


在 Pipeline 中的位置

去重不是独立步骤,它是整个频道处理 Pipeline 的一环:

复制代码
M3U 导入 → 解析频道列表 → 去重(联合哈希) → 批量测试(有效性+直播流判定+流畅度) → 结果筛选 → M3U 导出

去重发生在导入之后、测试之前。目的是避免对同一频道的多个重复 URL 做冗余测试------省下大量时间和带宽。

IPTV-tools 为例:它先通过 M3U/TXT 导入解析频道,用联合哈希去重,再用多线程并行测试每个 URL,过程中自动完成有效性过滤、直播流判定和流畅度评分,最终导出的 M3U 已经是去重且高质量的结果。

那份 12,847 条的测试数据导入后,经过解析去重和测试筛选,最终保留约 7,200 个有效频道,与上面的数据吻合。


一句话总结

去重的本质不是算法,而是定义"什么算同一个频道"。联合哈希用名称+URL 两个维度覆盖大部分重复,评分机制确保保留的是质量最好的那条源,哈希计算本身的 O(n) 复杂度让万级频道去重只需几秒。每次源数据更新时跑一遍,目录才能始终保持精简。


技术栈:Python, hashlib, SQLite/D1 工具参考:IPTV-tools --- 集成去重+批量测试+直播判定的完整 Pipeline

相关推荐
liuyicenysabel2 小时前
从 0 到 1:一套 GitHub + GHCR + k3s 的全自动 CI/CD 流水线(Flask 项目实战)
ci/cd·flask·github
u1301304 小时前
GitHub 热榜项目:日榜(2026-08-31)
github
Justinsky4 小时前
DeepSeek Harness 开源一个月,我把它整个嵌进了 Electron 桌面软件
github
小宋10214 小时前
MCP 是什么:从零编写一个可供 AI 调用的工具服务
人工智能·github
小弥儿6 小时前
GitHub今日热榜 | 2026-09-01:迷你小模型登场
学习·microsoft·开源·github
QUOR6 小时前
Zorv AI 内置浏览器技术架构与开发指南(新版)
架构·github
u1301306 小时前
GitHub 热榜项目:日榜(2026-09-01)
github
younuo36556 小时前
广州网站搭建费用明细:域名、服务器与开发成本全解析
服务器·前端·github
百变梦仔6 小时前
Codex 启动回复合格后,我会用三类证据验收前端改动
前端·github