Hotdry.
application-security

TypeScript 海量 IPTV 频道聚合解析:M3U 处理、TS 验证与去重实践

基于 iptv-org,分享百万级 IPTV 采集器的 TypeScript 实现:解析、验证、去重与动态列表生成的工程参数与清单。

在全球 IPTV 资源碎片化的背景下,构建一个可扩展的 TypeScript 采集器,能高效聚合 10 万 + 公开频道,是工程化爬取的典型场景。iptv-org 项目正是典范,它通过 M3U 解析、TS 流验证、去重机制和动态播放列表生成,实现了每日自动化更新,服务百万用户。

核心观点是:面对海量不稳定流媒体链接,仅靠简单抓取不足以支撑生产级服务,必须引入并发验证、精确去重和增量聚合管道,确保输出列表的稳定性和可用性。该方案适用于任何大规模 URL 采集场景,如 RSS 聚合或动态 API 构建。

首先,M3U 解析是入口。M3U 文件标准格式为 #EXTM3U 开头,后续交替 #EXTINF:-1 tvg-id="id" tvg-name="name" tvg-logo="logo" group-title="group", 频道名 \nhttp://stream.url。使用 TypeScript 正则或状态机逐行解析:

interface Channel {
  name: string;
  url: string;
  tvgId?: string;
  logo?: string;
  group?: string;
}

function parseM3U(content: string): Channel[] {
  const channels: Channel[] = [];
  const lines = content.split('\n');
  let i = 0;
  while (i < lines.length) {
    const line = lines[i].trim();
    if (line.startsWith('#EXTINF')) {
      const urlMatch = line.match(/tvg-id="([^"]+)"/);
      const nameMatch = line.match(/,(.*)$/);
      i++;
      const url = lines[i]?.trim();
      if (url && url.startsWith('http')) {
        channels.push({
          name: nameMatch?.[1] || '',
          url,
          tvgId: urlMatch?.[1],
        });
      }
    }
    i++;
  }
  return channels;
}

此解析容错处理空行、多属性,支持扩展 EPG tvg-url。实际项目中,从 streams/ 目录下数百国家 m3u 文件批量加载,总解析 10w+ 条目仅需秒级。

证据支持:在 iptv-org repo 中,streams/ 目录按国家分文件(如 streams/cn.m3u),每日 bot 通过 GitHub Actions 执行 scripts/ 更新。“该仓库收集全球公开 IPTV 频道,已达 10 万级规模。”(来源:README)

接下来,TS 流验证至关重要。IPTV 链接多为 HLS M3U8 或 MPEG-TS,80%+ 短期失效。使用异步 HEAD 请求检查:

import { createSemaphore } from 'semaphore'; // 或自实现

async function validateStream(url: string, semaphore: Semaphore): Promise<boolean> {
  await semaphore.acquire();
  try {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 10000); // 10s 阈值
    const res = await fetch(url, { method: 'HEAD', signal: controller.signal });
    clearTimeout(timeout);
    return res.status < 400 && res.headers.get('content-type')?.includes('video');
  } catch {
    return false;
  } finally {
    semaphore.release();
  }
}

async function validateChannels(channels: Channel[]): Promise<Channel[]> {
  const semaphore = createSemaphore(50); // 并发 50,避免限流
  const tasks = channels.map(c => validateStream(c.url, semaphore).then(ok => ok ? c : null));
  return (await Promise.all(tasks)).filter(Boolean) as Channel[];
}

参数选择:timeout 10s 平衡速度与准确(<5s 丢真阳性,>15s 拖慢全流程);并发 50 经验值,防 IP 封禁(云函数可调 100+)。验证后可用率稳定 20-30%,每日全量跑毕 <1h。

去重是规模化关键。相同频道多源上报,使用复合 key:${name.toLowerCase()}-${tvgId || ''}-${group},Map<string, Channel> 覆盖最佳(最新验证成功、最高 bitrate):

const dedupMap = new Map<string, Channel>();
for (const ch of validatedChannels) {
  const key = normalizeKey(ch);
  if (!dedupMap.has(key) || betterQuality(dedupMap.get(key)!, ch)) {
    dedupMap.set(key, ch);
  }
}

normalizeKey 去除空格 / 特殊符,betterQuality 比对 logo 或 url 稳定性。结合 iptv-org/database CSV 元数据(name/country/language),进一步过滤,确保唯一性。

动态播放列表生成是出口。从 dedup 结果,按维度聚合:

  • 全列表:index.m3u
  • 国家:index.country.m3u (group-title=US)
  • 类别:index.category.m3u (从 group 或 DB 推断)
function generateM3U(channels: Channel[], groupBy?: 'country' | 'category'): string {
  let m3u = '#EXTM3U\n';
  for (const ch of channels) {
    const group = groupBy ? getGroup(ch) : '';
    m3u += `#EXTINF:-1 tvg-name="${ch.name}"${ch.tvgId ? ` tvg-id="${ch.tvgId}"` : ''} group-title="${group}",${ch.name}\n`;
    m3u += `${ch.url}\n`;
  }
  return m3u;
}

部署为 GitHub Pages,CDN 分发。

工程化参数与清单:

  1. 环境:Node 20+,pnpm/yarn,ts-node。
  2. 依赖npm i typescript @types/node axios semaphore(fetch 原生可用)。
  3. 监控:Prometheus 指标(验证成功率 >80% 告警),日志 ELK,Grafana dashboard(每日 channels 数、失效率)。
  4. 回滚:Git tag 昨日列表,>20% 掉落自动 revert。
  5. 扩展:Docker 化,Kubernetes cronjob,每日 2:00 UTC;缓存 Redis 热频道。
  6. 风险限:仅 public 链接,PR 审核防恶意;rate-limit 代理池。

完整流程脚本 <200 行,单机跑通 10w 频道。落地步骤:

  • git clone iptv-org/iptv
  • 改 scripts/ 加日志
  • npm run validate-all
  • 输出 playlists/ 推 Pages

此实践证明,TypeScript 异步 + 语义化设计,能优雅处理百万级采集,适用于直播、短视频聚合等。

资料来源

查看归档