前言
这篇文章仅仅记录一下无聊的我做了一件比较无聊的事情:写了个IPTV频道扫描程序。
事先说明: 本文基本没有任何实用价值!
一方面觉得写代码挺麻烦的,另一方面代码写都写出来了,虽然没用,还是分享记录一下中间的曲折过程,算是个素材,瞬间觉得也不亏了。
前因
家里的IPTV本来就是有的,采用的方法是光猫IPTV口直接连接软路由网口,再通过软理由搭建udpxy,这样的方式好处就是脱离IPTV机顶盒,只要连上家里的网络都能观看IPTV。
前两天发现运营商新推出了几个4K超清频道,而且还分SDR以及HDR。对画质有要求的我抱着扫描一下整个IP地址段完整搜索一下看看具体有哪些4K频道的想法,开启了这段无聊的故事。
网上有一些分享的4K频道地址可以直接拿来用,但我猜测会不会有遗漏的,所以才会想要自己扫描。
一些说明
其实网上有很多现成的IPTV直播源,是互联网的链接,这一部分直播源只要导入一个地址就行,好处是省力,不需要自己维护,而且不需要搭建任何服务,开箱即用;坏处是频道固定,无法自定义添加或者删除一些无用频道。
我个人偏向于复杂的组播转单播,即搭建udpxy直接将运营商的组播地址拿来使用。坏处是过程相对复杂,组播地址需要自行找,m3u列表文件也要自己编辑;好处也比较显而易见,频道信号源非常稳定,频道可以自定义,看哪个频道就添加哪个频道。
最影响我的就是我这边地方台,没有与电信的IPTV进行合作,所以想看地方台,需要找到互联网的播放地址后添加到直播源m3u列表中。
也就是直接拿网上的直播源地址下载下来进行编辑,将地方台添加进去后,直播源的地址也是需要自行维护的,比较网上的直播源地址经常变动。
所以,自己使用组播地址是最稳定的方案,一次搞定之后基本不需要维护。
扫描方案
要扫描端口,就基本确定使用Python进行编写,大致框架确定,即:扫描端口,获取返回值。
因此,我写了一个程序,运行之后发现扫描不出来,获取到的频道数量为0。
但是我在单独通过 curl 进行测试是,是能够正常返回数据的:
HTTP/1.1 200 OK
Server: udpxy 1.0-25.0
Content-Type: application/octet-stream
为此,搜索了一些资料,猜测可能的原因是:
-
只有在客户端发送 正确的 User-Agent / Accept / Range 或 持续读取流 时才会返回数据
-
请求速度太快(高并发)、太小 chunk、或 HEAD 请求行为不同, 很可能被拒绝
于是,我尝试在代码中:
-
添加
User-Agent来模拟播放器的行为 -
降低并发,从原来的500将为100,避免请求过多直接屏蔽
-
添加
Range: bytes=0-,让 udpxy 发送真实 TS 数据 -
强制读取 4096 字节,避免 udpxy 误判断请求太短
更改了一整遍代码之后还是不行,扫描后依然结果为 0。
于是只能再次查资料,联想到之前使用 curl 单个测试时可以正常显示,但是在现在的异步脚本一个都扫不出来,可能是线程的问题,而且,udpxy的特点是无论组播组存不存在,它都会立刻返回 HTTP 200 OK(不会 404),只有真正有组播流进来时,才会立刻开始吐 TS 数据包。
想着好吧,再次更改代码,结果还是不行。。。差点就没辙了,想着都到这份上了,死马当活马医,降低判断标准:“只要 1 秒内能收到 ≥ 2 个完整 188 字节 TS 包” 就算存活,以及再次降低并发。
没想到还真的可以。。。但是速度一般般,不是很快。
代码及用法
前提条件: 目前网络配置是光猫ITV口直接连接路由器或者交换机直接播放组播地址,并且搭建了udpxy。
某些路由器可以省去搭建udpxy,比如华硕路由器。
代码:
scan.py
import asyncio
import aiohttp
import random
from tqdm.asyncio import tqdm_asyncio
ALIVE = []
BASE = "http://10.10.10.1:5999/rtp/239.49.{}.{}:{}" # 替换为自己的IPTV单播地址及自己省市对应的IPTV地址段
PORTS = [6000, 8000, 9614, 9802, 9814, 9822] # 替换为自己省市的常见IPTV端口列表
TOTAL = 10 * 256 * len(PORTS)
async def check(session, third, fourth, port, sem, pbar):
async with sem:
url = BASE.format(third, fourth, port)
await asyncio.sleep(random.uniform(0.02, 0.09))
try:
async with session.get(url, timeout=4) as r:
data = await r.content.read(1200)
if len(data) > 100:
ALIVE.append(url)
pbar.set_postfix(found=len(ALIVE), refresh=False)
print(f"\n[+] 存活 → {url}")
except:
pass
finally:
pbar.update(1)
async def main():
print("IPTV单播地址扫描")
sem = asyncio.Semaphore(10)
connector = aiohttp.TCPConnector(limit=15, limit_per_host=15)
timeout = aiohttp.ClientTimeout(total=5)
async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
with tqdm_asyncio(total=TOTAL, desc="扫描进度", colour="cyan",
bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}] 已发现: {postfix}") as pbar:
tasks = []
for third in range(10):
for fourth in range(256):
for port in PORTS:
tasks.append(check(session, third, fourth, port, sem, pbar))
if len(tasks) >= 300:
await asyncio.gather(*tasks)
tasks.clear()
await asyncio.sleep(0.5)
if tasks:
await asyncio.gather(*tasks)
# 保存结果
with open("result.txt", "w", encoding="utf-8") as f:
f.write("\n".join(sorted(ALIVE)) + "\n")
print(f"\n扫描完成!共发现 {len(ALIVE)} 个真实频道")
print("完整列表已保存: result.txt")
asyncio.run(main())
用法:
python3 -m venv iptv
source iptv/bin/activate
pip install aiohttp tqdm
python3 scan.py
无用论
回到开头,为什么说这个程序没什么用?
-
我全程扫了一圈,发现与网上分享的组播地址可以说是一模一样,即使有些比较冷门的频道被扫出来了,但是这类频道本身就几乎不会去看,所以没有用;
-
每个省市的IPTV组播地址以及端口都各不相同,扫描前要确定大致地址及端口,虽然现在全国大多数都固定使用8000端口了,但还有一些小众端口。正常情况下在这一步的时候,其实已经拿到了当前自身省市的组播地址了,再去扫描也最多是查漏补缺,就像是考试已经99分了,还去为了最后仅有的1分耗时耗力;
-
沉没成本比较大,即使硬着头皮去整个扫描一下,得到的与付出的完全不成正比,有这点时间不如做其他的事情。
综上所述,就是再给我一次机会,我是绝对不会去做这种事情的。
好人做到底
在解决问题搜集资料的时候,看到的各省市的组播地址段分享给大家,如果非要尝试的也未尝不可。
| 省份 | 主要组播地址段 | 常用端口 | 备注 |
|---|---|---|---|
| 江苏 | 239.49.0.0 ~ 239.49.9.255 | 8000(主力) 9614/9610/9802/9602等老端口残留 | 全省统一,南京/苏州/无锡通用,140+频道 |
| 上海 | 239.45.0.0 ~ 239.45.255.255 233.18.204.0/24(新增) | 5140(主力) | 2025年2月新增233网段,4K HDR多 |
| 广东 | 239.253.0.0 ~ 239.253.255.255 | 8000(主力) | 南方电信经典段,广州/深圳通用 |
| 浙江 | 239.51.0.0 ~ 239.51.255.255 | 8000(主力) | 杭州主力,温州部分仍混用239.49 |
| 北京 | 239.97.0.0 ~ 239.97.255.255 | 8000 | 北方常见 |
| 山东 | 239.98.0.0 ~ 239.98.255.255 | 8000 | 部分地市混用239.49 |
| 安徽 | 239.94.0.0 ~ 239.94.255.255 | 8000 | 合肥/芜湖通用,2025年8月仍有更新 |
| 福建 | 239.93.0.0 ~ 239.93.255.255 | 5140/8000 | 福州/厦门主力 |
| 四川/重庆 | 239.80.0.0 ~ 239.80.255.255 | 8000 | 西南主力 |
| 湖北 | 239.95.0.0 ~ 239.95.255.255 | 8000 | 武汉通用 |
| 湖南 | 239.96.0.0 ~ 239.96.255.255 | 8000 | 长沙主力 |
| 河南 | 239.99.0.0 ~ 239.99.255.255 | 8000 | 郑州通用 |
| 河北 | 239.91.0.0 ~ 239.91.255.255 | 8000 | 石家庄主力 |
| 山西 | 239.92.0.0 ~ 239.92.255.255 | 8000 | 太原通用 |
| 陕西 | 239.90.0.0 ~ 239.90.255.255 | 8000 | 西安主力 |
| 甘肃/宁夏/青海 | 239.89.0.0 ~ 239.89.255.255 | 8000 | 西北统一 |
| 新疆 | 239.88.0.0 ~ 239.88.255.255 | 8000 | 乌鲁木齐主力 |
| 广西 | 239.254.0.0 ~ 239.254.255.255 | 8000 | 南宁通用 |
| 江西 | 239.107.x.x 或混用239.49 | 8000 | 南昌部分仍用老段 |
最后
这篇文章就是随便写写,反正也是无聊,写了无聊的程序,写了无聊的文章。
这个过程大概用了两天时间,关于代码的优化以及IPTV组播源的信号是怎么判定的,到现在还是一知半解,也算是一个学习的过程。

