在全球 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);
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);
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 分发。
工程化参数与清单:
- 环境:Node 20+,pnpm/yarn,ts-node。
- 依赖:
npm i typescript @types/node axios semaphore(fetch 原生可用)。
- 监控:Prometheus 指标(验证成功率 >80% 告警),日志 ELK,Grafana dashboard(每日 channels 数、失效率)。
- 回滚:Git tag 昨日列表,>20% 掉落自动 revert。
- 扩展:Docker 化,Kubernetes cronjob,每日 2:00 UTC;缓存 Redis 热频道。
- 风险限:仅 public 链接,PR 审核防恶意;rate-limit 代理池。
完整流程脚本 <200 行,单机跑通 10w 频道。落地步骤:
- git clone iptv-org/iptv
- 改 scripts/ 加日志
npm run validate-all
- 输出 playlists/ 推 Pages
此实践证明,TypeScript 异步 + 语义化设计,能优雅处理百万级采集,适用于直播、短视频聚合等。
资料来源: