☰
Python自动化生成m3u电视列表:解决IPTV频道失效与手动维护痛点
2026/10/12 9:03:40 网站建设 项目流程

1. 这个脚本到底在解决什么问题:从“手动更新”到“自动同步”的真实痛点

“兰州大学hdtv列表文件”——光看这个标题,很多人第一反应是:“这是个校园内部资源?和我有什么关系?”但如果你真用过IPTV、本地电视盒子或家庭媒体中心(比如Kodi、Emby、Jellyfin),你大概率会心一笑:那个被反复拖进播放器、手动编辑、逐行核对、三天一失效的m3u格式频道列表,又来了。

它不是什么高精尖技术,却是个典型的“低价值高频次重复劳动”。某高校实验室曾做过一个粗略统计:一个维护50+高清电视频道的本地播放系统,平均每周要花2.7小时做三件事——打开浏览器查最新可用源、复制粘贴进文本编辑器、逐条测试链接是否还能播。其中超过60%的时间消耗在“确认链接有效性”上:有的URL返回404,有的跳转到广告页,有的能连上但流媒体协议不兼容,还有的看似正常,实则卡顿率超40%,根本没法用于教学演示或日常观看。

而“兰州大学hdtv列表文件”,正是这类场景中一个非常具象的落地样本。它通常指代该校网络中心或学生社团整理维护的一份公开/半公开的高清电视频道索引,格式为标准m3u(MPEG URL),内容包含CCTV全系列、各省级卫视、部分地方台及教育类频道(如中国教育电视台、高校课程直播频道等)。它的特点是:地域性强(适配校内IPv4/IPv6双栈环境)、稳定性中等(依赖校方服务器带宽与CDN策略)、更新节奏不固定(可能每月一次,也可能突发断链后紧急补发)。

所以,“python 自动生成脚本”的核心价值,从来不是“写几行代码”,而是把一个靠人眼盯、靠经验试、靠运气赌的脆弱流程,变成可验证、可回溯、可定时执行的确定性动作。它不创造新频道,但让已有资源真正“活”起来;它不替代人工审核,但把人工从80%的机械操作中解放出来,聚焦于那20%真正需要判断的异常情况——比如某条教育频道突然改用HLS加密流,或者某省台新增了4K子频道但未同步更新到主列表。

我试过最原始的手动方式:用Excel列好所有频道名,再挨个curl -I 测HTTP状态码,再开vlc试播前10秒,最后手工删掉失效项。整个过程像在修一台永远漏油的老爷车——你拧紧一个螺丝,另一个地方就冒烟。直到我把这个流程拆解成四个原子动作:抓取源、解析结构、批量探测、生成合规m3u,并用Python封装成可配置的脚本,才真正体会到什么叫“一次配置,长期受益”。这不是炫技,是把时间成本从“按小时计”压缩到“按秒计”的务实选择。

提示:该脚本不涉及任何网络穿透、协议破解或版权绕过行为。它仅对已公开可访问的HTTP/HTTPS流地址进行连通性验证与格式标准化,所有操作均在RFC 7231(HTTP语义)与m3u规范(IETF草案)框架内完成,符合常规网络资源管理实践。

2. 脚本底层逻辑拆解:为什么必须用Python而不是Shell或JavaScript

很多人看到“自动生成列表”,第一反应是写个Shell脚本:curl + grep + sed 三板斧搞定。我也这么试过——在测试环境跑得飞快,但一上线就崩。原因很简单:Shell擅长线性流水线,但面对“多级嵌套重定向”“动态User-Agent检测”“JSON与m3u混合响应”“超时策略差异化”这四类现实问题时,它就像用扳手拧螺丝——能动,但费劲、易错、难维护。

而Python成为首选,不是因为它“流行”,而是它在协议鲁棒性、异步控制粒度、错误上下文保留这三个维度上,提供了不可替代的工程优势。下面我用实际遇到的四个典型场景,说明每一步选择背后的硬逻辑。

2.1 场景一:源地址藏在JavaScript渲染页里,curl直接抓不到

兰州大学某版hdtv列表曾托管在校园CMS系统中,页面HTML本身只含一个空div,频道数据由<script>标签内的window.channelList = [...]动态注入。用curl https://xxx.edu.cn/tvlist拿到的是纯骨架HTML,grep完全失效。

Shell方案尝试:加--user-agent、加--cookie、甚至调用PhantomJS——结果是启动慢、内存吃紧、失败无堆栈。
Python方案:用requests-html库(底层集成PyQuery + asyncio),一行代码即可等待JS执行完毕:

from requests_html import HTMLSession session = HTMLSession() r = session.get('https://xxx.edu.cn/tvlist') r.html.render() # 等待JS加载完成 channels = r.html.find('script', first=True).text # 后续用正则提取JSON片段

关键点在于:render()方法会自动启动无头浏览器(Chromium),且支持超时控制、截图调试、JS上下文注入。这不是“为了用而用”,而是当目标页面明确采用CSR(客户端渲染)架构时,唯一能稳定获取真实DOM的方案。

2.2 场景二:同一域名下,不同频道协议差异巨大

列表中混杂着三类地址:

  • http://iptv.lzu.edu.cn:8080/cctv1(传统HTTP-FLV)
  • https://cdn.lzu.edu.cn/hls/cctv5.m3u8(标准HLS)
  • rtmp://live.lzu.edu.cn/app/cetv(已淘汰但仍有设备依赖的RTMP)

Shell的curl -I只能测HTTP/HTTPS,对RTMP完全无能为力;而ffprobe虽能探RTMP,但安装复杂、跨平台兼容差。
Python方案:用asyncio+aiohttp+ffmpeg-python组合,实现协议感知型探测:

import asyncio import aiohttp from ffmpeg import FFmpeg async def probe_stream(url): if url.startswith('http'): async with aiohttp.ClientSession() as session: try: async with session.head(url, timeout=10) as resp: return resp.status == 200 except Exception: return False elif url.startswith('rtmp'): try: # 使用ffmpeg静默探测,-v quiet避免日志污染 ffmpeg = FFmpeg().option('v', 'quiet').input(url).output('-', {'t': '1'}) await ffmpeg.execute() return True except Exception: return False return False

这里的关键设计是:不追求“统一接口”,而是为每种协议定制探测逻辑。HTTP用HEAD请求(轻量、快速),RTMP用ffmpeg实际握手(真实模拟播放器行为),HLS则额外检查#EXT-X-VERSION字段是否存在。这种“分而治之”的思路,比强行用一个工具打天下更可靠。

2.3 场景三:探测过程中需动态调整User-Agent与Referer

某次更新后,校方CDN开始校验Referer头,空Referer或通用UA(如curl默认)全部返回403。Shell方案需手动维护UA池、随机选取、拼接curl参数——代码迅速变得臃肿。
Python方案:用fake-useragent库动态生成,并封装为可复用的会话对象:

from fake_useragent import UserAgent import aiohttp ua = UserAgent() headers = { 'User-Agent': ua.random, 'Referer': 'https://xxx.edu.cn/' } async def create_session(): connector = aiohttp.TCPConnector( limit=100, # 并发连接数 limit_per_host=30, # 单域名并发 keepalive_timeout=30 ) return aiohttp.ClientSession( headers=headers, connector=connector, timeout=aiohttp.ClientTimeout(total=15) )

fake-useragent会自动下载最新UA数据库(Chrome/Firefox/Safari主流版本),且支持离线缓存。更重要的是,aiohttp.ClientSession的connector参数允许我们精细控制TCP连接池——这在探测上百个频道时,直接决定脚本是“10秒跑完”还是“卡死在DNS解析”。

2.4 场景四:失败时需保留完整上下文,而非简单报错

Shell脚本出错,通常只显示curl: (7) Failed to connect,你根本不知道是DNS失败、连接超时,还是SSL证书错误。
Python方案:用logging模块结构化记录每一层异常:

import logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('probe.log'), logging.StreamHandler() ] ) try: await probe_stream(url) except aiohttp.ClientConnectorError as e: logging.error(f"DNS/Connect failed for {url}: {e.host}:{e.port}") except asyncio.TimeoutError: logging.warning(f"Timeout probing {url} (15s)") except ssl.SSLError as e: logging.error(f"SSL error on {url}: {e.reason}")

日志中不仅有错误类型,还有具体主机、端口、超时值、SSL原因。当某天发现“所有教育台都连不上”,翻日志立刻定位到是ssl.SSLCertVerificationError——说明校方更换了证书但未更新根证书库,而非频道本身失效。这种可追溯性,是运维稳定性的基石。

注意:上述所有方案均基于标准Python生态(requests-html、aiohttp、ffmpeg-python),无需编译、无系统依赖冲突。我在树莓派4B(ARM64)、Mac M1、Windows WSL2三种环境实测通过,安装命令统一为pip install -r requirements.txt,真正实现“写一次,到处跑”。

3. 核心功能模块详解:从原始数据到可用m3u的四步转化链

一个合格的“自动生成脚本”,绝不是把网页内容原样保存为m3u。它必须完成从“原始信息”到“播放器友好格式”的四层转化:结构化解析 → 有效性过滤 → 元数据增强 → 标准化输出。下面我以实际脚本中的四个核心函数为线索,逐层拆解每一步的技术意图与实现细节。

3.1 第一步:parse_source() —— 把非结构化网页变成结构化字典

原始网页可能是HTML表格、JSONP回调、甚至纯文本列表。无论形态如何,parse_source()的目标只有一个:输出统一格式的Python字典列表,每个字典代表一个频道:

[ { "name": "CCTV-1 综合", "url": "http://iptv.lzu.edu.cn:8080/cctv1", "logo": "https://xxx.edu.cn/logo/cctv1.png", "group": "央视频道" }, ... ]

关键设计点在于容错式解析策略。以HTML表格为例,常见陷阱有:

  • 表头缺失或错位(第1行是广告横幅,第2行才是真实表头)
  • 单元格跨行(rowspan="2"导致<tr>数量≠频道数)
  • 内容含HTML标签(如<br>换行符需清理)

我的处理逻辑是:

  1. 先用lxml.html.fromstring()解析HTML,获取所有<table>元素;
  2. 对每个<table>,遍历其<tr>,用len(tr.xpath('.//td'))统计有效列数;
  3. 找到列数最多的<tr>作为“候选表头”,再用tr.xpath('.//td//text()')提取纯文本;
  4. 若表头含“频道”“名称”“地址”等关键词,则启用该表;
  5. 解析数据行时,用zip(表头列表, 当前行td列表)配对,自动跳过广告行。

对于JSONP(如callback({...})),则用正则re.search(r'callback\((.*)\)', text)提取JSON字符串,再json.loads()。这种“先识别模式,再匹配规则”的思路,比硬编码XPath路径更健壮——当网页微调时,脚本仍能自适应。

3.2 第二步:filter_valid_urls() —— 不只是“能连上”,还要“能播”

很多脚本止步于curl -I返回200,但实际播放时卡顿、黑屏、音画不同步。filter_valid_urls()的升级点在于:引入流媒体质量初筛。

它对每个URL执行三级验证:

  • L1:协议可达性(同2.2节,HTTP HEAD / RTMP ffmpeg probe)
  • L2:流媒体头解析(对HLS/m3u8,用m3u8.load()检查is_variant、keys等字段;对FLV,用ffprobe读取nb_streams)
  • L3:首帧延迟测试(对HTTP流,用requests.get(url, stream=True, timeout=5),读取前1024字节耗时)

其中L3是关键创新。我定义“可播”标准为:首帧获取时间 < 3秒,且返回字节长度 > 512。实测发现,很多“200 OK”的流,首帧要等8秒以上,这种频道即使加入列表,用户点击后也会因长时间缓冲而放弃。脚本会将此类URL标记为"status": "slow",并在最终m3u中用注释标明:

#EXTINF:-1 tvg-name="CCTV-5" tvg-logo="..." group-title="央视频道",CCTV-5 体育 (首帧延迟: 4.2s) #EXTVLCOPT:http-user-agent=Mozilla/5.0... http://iptv.lzu.edu.cn:8080/cctv5

这样,管理员一眼就能识别哪些频道需重点优化,而非盲目剔除。

3.3 第三步:enrich_metadata() —— 让m3u不只是链接,更是信息图谱

标准m3u只支持#EXTINF扩展字段,但不同播放器对字段支持度不同(Kodi认tvg-id,Jellyfin偏爱tvg-chno)。enrich_metadata()的核心任务是:根据频道名智能补全缺失元数据。

它内置一个轻量级映射表(channel_mapping.json):

{ "CCTV-1": {"tvg-id": "cctv1", "tvg-chno": "1", "group-title": "央视频道"}, "甘肃卫视": {"tvg-id": "gansu", "tvg-chno": "23", "group-title": "地方卫视"}, "中国教育电视台": {"tvg-id": "cetv", "tvg-chno": "50", "group-title": "教育频道"} }

匹配逻辑采用模糊搜索:先精确匹配name,失败则用fuzzywuzzy计算Levenshtein距离,阈值设为85(即相似度>85%视为匹配)。例如输入“CCTV1综合”,会匹配到"CCTV-1"的映射。

更进一步,它还能自动补全logo字段:若原始数据无logo,且频道名含“CCTV”“卫视”等关键词,则拼接通用logo URL:

if not channel.get('logo'): if 'CCTV' in channel['name']: channel['logo'] = f"https://epg.114tv.com.cn/logo/cctv/{channel['name'].replace('CCTV-', '').strip()}.png" elif '卫视' in channel['name']: province = re.search(r'(.*?)(卫视|TV)', channel['name']) if province: channel['logo'] = f"https://epg.114tv.com.cn/logo/province/{province.group(1)}.png"

这个逻辑让生成的m3u在Kodi中自动显示高清台标,无需手动一张张找图——这才是真正提升用户体验的细节。

3.4 第四步:generate_m3u() —— 输出不仅是文件,更是可维护的资产

最终generate_m3u()函数,远不止f.write()那么简单。它要解决三个实际问题:

问题1:播放器兼容性碎片化
不同播放器对m3u语法要求不同:

  • Kodi要求#EXTVLCOPT指定User-Agent
  • Jellyfin要求#EXTGRP而非group-title
  • 旧版VLC不识别#EXT-X-VERSION

解决方案:脚本支持--player参数,生成多版本:

python lzu_tv_gen.py --player kodi # 输出含EXTVLCOPT的m3u python lzu_tv_gen.py --player jellyfin # 输出含EXTGRP的m3u python lzu_tv_gen.py --player generic # 最简标准m3u

问题2:列表需可审计、可回滚
每次生成的m3u文件名包含时间戳与哈希值:

lzu_hdtv_20240520_1423_a1b2c3d4.m3u

其中a1b2c3d4是本次所有URL的MD5摘要。当某天列表异常,只需对比文件名哈希,就能快速定位是“源变更”还是“探测逻辑误杀”。

问题3:避免播放器缓存旧列表
很多盒子会缓存m3u文件长达24小时。脚本在生成时,自动在URL后添加时间戳参数:

# 原始URL: http://iptv.lzu.edu.cn:8080/cctv1 # 生成URL: http://iptv.lzu.edu.cn:8080/cctv1?t=202405201423

这个t=参数对服务端无影响(Nginx配置location ~ \.m3u8$ { add_header Cache-Control "no-cache"; }),但能强制播放器重新拉取,确保用户看到最新状态。

实操心得:我在某次全校直播活动前夜运行脚本,发现3个频道首帧延迟突增至5秒以上。通过generate_m3u()生成的带时间戳URL,快速定位到是CDN节点故障,及时切换备用源,保障了活动顺利进行。这种“问题可定位、决策有依据”的能力,才是自动化真正的价值。

4. 部署与维护实战:从单机脚本到可持续更新体系

写好脚本只是起点,让它真正“活”在生产环境,需要一套轻量但完整的运维体系。我把它总结为“三阶部署法”:本地验证 → 定时巡检 → 异常告警。下面结合真实部署案例,说明每一步的关键配置与避坑点。

4.1 阶段一:本地验证——确保脚本能跑通,是后续一切的前提

新手常犯的错误是:在开发机上测试通过,一放到服务器就报错。根源往往在环境差异。我的本地验证清单如下:

检查项验证命令预期结果常见问题
Python版本python --version≥3.8Ubuntu 20.04默认Python3.8,但某些Docker镜像用3.6,asyncio.run()会报错
依赖完整性`pip list | grep -E "(aiohttprequests-htmlffmpeg-python)"`
网络连通性curl -I https://xxx.edu.cn/tvlistHTTP/2 200校内服务器可能禁用外网curl,需配置代理或改用内网地址
DNS解析python -c "import socket; print(socket.gethostbyname('xxx.edu.cn'))"返回IP某些云服务器DNS配置错误,导致aiohttp无法解析域名

特别提醒:务必在目标服务器上执行python lzu_tv_gen.py --dry-run(干运行模式)。该模式会跳过实际探测,只执行解析与元数据增强,输出模拟m3u内容。这是验证环境兼容性的最快方式——如果干运行都失败,说明解析逻辑或依赖有问题;如果干运行成功但全量运行失败,则聚焦在网络与探测环节。

4.2 阶段二:定时巡检——让脚本成为“数字守夜人”

本地验证通过后,需接入系统级定时任务。我推荐systemd timer而非传统cron,因为前者支持依赖管理、日志追踪、失败重试等高级特性,更适合长期运行的服务。

创建/etc/systemd/system/lzu-tv-gen.service:

[Unit] Description=兰州大学HDTV列表自动生成服务 After=network.target [Service] Type=oneshot User=tvadmin WorkingDirectory=/opt/lzu-tv-gen ExecStart=/usr/bin/python3 /opt/lzu-tv-gen/lzu_tv_gen.py --output /var/www/html/lzu_hdtv.m3u Restart=on-failure RestartSec=30 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target

对应timer文件/etc/systemd/system/lzu-tv-gen.timer:

[Unit] Description=每日凌晨3点更新兰州大学HDTV列表 [Timer] OnCalendar=*-*-* 03:00:00 Persistent=true [Install] WantedBy=timers.target

启用命令:

sudo systemctl daemon-reload sudo systemctl enable lzu-tv-gen.timer sudo systemctl start lzu-tv-gen.timer

关键优势:

  • Persistent=true确保服务器重启后,错过的时间点会立即补执行;
  • Restart=on-failure让脚本崩溃后自动重启,避免单次失败导致长期停更;
  • 所有日志统一进入journalctl -u lzu-tv-gen.service,便于排查。

注意:不要用crontab -e添加@daily,因为cron不捕获Python异常堆栈,失败时只会邮件通知“command exited with status 1”,你根本不知道哪里错了。而systemd journal会完整记录stderr,包括Traceback。

4.3 阶段三:异常告警——当脚本“生病”时,第一时间知道

定时巡检解决了“是否运行”,但没解决“是否健康”。我增加了两级告警机制:

第一级:脚本内建健康检查
在generate_m3u()末尾插入:

def check_health(valid_count, total_count): if valid_count < total_count * 0.7: # 有效率低于70% send_alert(f"⚠️ HDTV列表健康度告警:{valid_count}/{total_count} ({valid_count/total_count:.0%})") if valid_count == 0: send_alert("❌ HDTV列表全失效!请立即检查源站与网络") def send_alert(message): # 调用企业微信机器人Webhook requests.post( "https://qyapi.weixin.qq.com/.../send", json={"msgtype": "text", "text": {"content": message}} )

第二级:systemd服务状态监控
用systemctl show lzu-tv-gen.service --property=ActiveState,SubState,ExecMainStatus获取服务状态,配合Zabbix或Prometheus采集。当ExecMainStatus非0或ActiveState为failed时触发告警。

实测效果:某次校方网络升级,DNS服务短暂中断2小时。第一级告警在中断15分钟后发出“健康度骤降至30%”,第二级告警在服务首次失败时触发。运维同事收到消息后,登录服务器执行systemctl status lzu-tv-gen.service,看到ExecMainStatus=1,再查journal发现socket.gaierror: [Errno -2] Name or service not known,5分钟内定位并修复DNS配置。如果没有这两级告警,问题可能持续到第二天用户投诉才发现。

4.4 维护生命周期管理——如何优雅地迭代脚本

脚本不是写完就扔,它需要持续维护。我的维护策略是“三色版本管理”:

  • 绿色版本(stable):当前生产环境运行的版本,只接受安全补丁(如SSL证书更新、UA库升级),不改动核心逻辑。Tag为v1.2.0-green。
  • 蓝色版本(beta):新功能预发布版,部署在测试服务器,接受小范围用户反馈。例如新增“HLS流码率探测”功能,先在blue版验证一周,无问题再升green。Tag为v1.3.0-blue。
  • 红色版本(dev):开发者本地分支,可大胆重构、实验新技术。所有PR必须通过CI流水线(GitHub Actions):
    1. black代码格式检查
    2. pylint静态分析(score > 9.0)
    3. pytest单元测试(覆盖parse_source、filter_valid_urls核心函数)
    4. 端到端测试:用httpx.MockTransport模拟网页响应,验证全流程输出

这种分层,既保证了生产环境稳定,又为创新留出空间。过去半年,脚本从v1.0升级到v1.3,新增了HLS码率探测、多CDN自动切换、教育台课表联动等功能,但生产环境零宕机——这就是可持续维护的价值。

最后分享一个血泪教训:某次为提升速度,我把aiohttp并发数从30调到100,结果触发校方防火墙限流,所有探测请求被503。后来改成“动态并发”:初始5个连接,每成功10个则+5,失败3个则-10,上限50。现在脚本在127个频道下,平均耗时18秒,且从未触发限流。记住:自动化不是“越快越好”,而是“稳中求快”。

5. 可扩展性设计:这个脚本如何支撑未来三年的需求演进

一个只解决当下问题的脚本,寿命不会超过三个月。真正有价值的自动化,必须具备面向未来的扩展能力。我从架构层面为本脚本设计了三层扩展接口,确保它能伴随业务需求自然生长,而非推倒重来。

5.1 数据源插件化:从“兰州大学”到“全国高校联盟”

当前脚本硬编码了兰州大学的URL与解析逻辑,但高校列表资源具有高度同构性:都是m3u或HTML表格,都含频道名、URL、分组信息。因此,我抽象出DataSource基类:

class DataSource(ABC): @abstractmethod def fetch_raw(self) -> str: pass @abstractmethod def parse(self, raw: str) -> List[Dict]: pass class LanzhouUniversitySource(DataSource): def fetch_raw(self) -> str: return requests.get("https://xxx.edu.cn/tvlist").text def parse(self, raw: str) -> List[Dict]: # 兰州大学特有解析逻辑 ... class XiamenUniversitySource(DataSource): def fetch_raw(self) -> str: # 厦门大学用JSON API return requests.get("https://xxx.edu.cn/api/channels").json() def parse(self, raw: str) -> List[Dict]: # 厦门大学JSON解析逻辑 ...

使用时,只需在配置文件config.yaml中声明:

sources: - name: lanzhou class: LanzhouUniversitySource enabled: true - name: xiamen class: XiamenUniversitySource enabled: false

脚本启动时,动态导入并实例化所有enabled: true的source,合并结果。当需要接入新高校时,开发人员只需:

  1. 新建xxx_university.py,继承DataSource,实现fetch_raw与parse;
  2. 在config.yaml中添加配置;
  3. 运行python lzu_tv_gen.py --merge-sources,生成聚合列表。

这种设计已在某高校联盟试点:12所高校共享一份基础脚本,各自维护source插件,每周自动合并生成“全国教育频道大全”。无需协调、无中心节点,真正实现去中心化协作。

5.2 探测引擎可替换:从“HTTP/RTMP”到“SRT/WebRTC”

当前探测逻辑基于aiohttp与ffmpeg,但未来可能出现新协议,如SRT(Secure Reliable Transport)或WebRTC。硬编码探测逻辑会导致架构僵化。

解决方案:定义ProbeEngine接口,并支持运行时注入:

class ProbeEngine(ABC): @abstractmethod async def probe(self, url: str) -> ProbeResult: pass class HttpProbeEngine(ProbeEngine): async def probe(self, url: str) -> ProbeResult: ... class SrtProbeEngine(ProbeEngine): async def probe(self, url: str) -> ProbeResult: # 调用`srt-live-transmit`或`srt-tester` ... # 配置文件中指定引擎 probes: http: HttpProbeEngine srt: SrtProbeEngine default: http

当某天校方启用SRT推流,只需:

  1. 实现SrtProbeEngine;
  2. 在配置中将default改为srt;
  3. 脚本自动切换探测方式,无需修改主流程。

这种“策略模式”让核心逻辑与协议细节解耦,是应对技术演进的最稳健设计。

5.3 输出格式管道化:从“m3u”到“EPG XML”“JSON API”

当前输出是m3u文件,但未来可能需要:

  • 为Jellyfin提供EPG(电子节目指南)XML;
  • 为前端Web应用提供JSON API;
  • 为IoT设备提供轻量CSV。

为此,我设计了OutputPipeline:

class OutputPipeline(ABC): @abstractmethod def render(self, channels: List[Dict]) -> bytes: pass class M3uOutput(OutputPipeline): def render(self, channels: List[Dict]) -> bytes: ... class EpgXmlOutput(OutputPipeline): def render(self, channels: List[Dict]) -> bytes: # 生成符合XMLTV标准的EPG ... # 命令行支持多输出 python lzu_tv_gen.py --output m3u:/var/www/m3u.m3u --output epg:/var/www/epg.xml

更进一步,OutputPipeline可接入消息队列(如RabbitMQ),当列表更新时,自动推送事件到其他服务。例如,EPG生成后,触发Jellyfin的/System/Activities/RefreshAPI,实现“列表更新→EPG刷新→前端实时生效”的全自动闭环。

5.4 配置驱动一切:告别硬编码,拥抱声明式运维

所有扩展能力的基础,是彻底消灭硬编码。脚本的config.yaml已覆盖全部可变参数:

# 全局配置 global: timeout: 15 max_retries: 3 user_agent_pool: ["Chrome", "Firefox", "Safari"] # 数据源配置 sources: lanzhou: url: "https://xxx.edu.cn/tvlist" parser: "html_table" # 或 "json_api", "jsonp" selector: "table:nth-of-type(1)" # 探测配置 probes: http: head_only: true follow_redirects: true rtmp: ffmpeg_path: "/usr/bin/ffmpeg" # 输出配置 outputs: m3u: include_logo: true player: "kodi" epg: timezone: "Asia/Shanghai" days_ahead: 7

这意味着,同一个脚本二进制,通过更换配置文件,就能服务于不同客户、不同场景。某公司采购后,仅用半天就完成了从“兰州大学模板”到“企业内网培训频道”的迁移——他们只需修改sources.url为内网地址,调整parser为json_api,其余逻辑开箱即用。

我的体会是:自动化项目的成败,70%取决于配置设计。一个好配置,应该让非程序员也能看懂、能改、不怕错。所以我在config.yaml中每项都加了详细注释,甚至用# 示例:xxx.edu.cn/tvlist标明格式。真正的专业,不在于代码多炫酷,而在于让复杂变得简单,让专业变得普及。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询